From 69f493ee3a7412dfb45b55a729b57465b8696780 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sat, 12 Oct 2019 16:34:29 +0000 Subject: [PATCH] #63745 - Refactor EscherRecord.ToXml git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1868353 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/util/GenericRecordJsonWriter.java | 60 ++- .../poi/util/GenericRecordXmlWriter.java | 480 ++++++++++++++++++ 2 files changed, 514 insertions(+), 26 deletions(-) create mode 100644 src/java/org/apache/poi/util/GenericRecordXmlWriter.java diff --git a/src/java/org/apache/poi/util/GenericRecordJsonWriter.java b/src/java/org/apache/poi/util/GenericRecordJsonWriter.java index 1598d58ae3..b70fe362a7 100644 --- a/src/java/org/apache/poi/util/GenericRecordJsonWriter.java +++ b/src/java/org/apache/poi/util/GenericRecordJsonWriter.java @@ -96,27 +96,7 @@ public class GenericRecordJsonWriter implements Closeable { } public GenericRecordJsonWriter(Appendable buffer) { - fw = new PrintWriter(new Writer(){ - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - buffer.append(String.valueOf(cbuf), off, len); - } - - @Override - public void flush() throws IOException { - if (buffer instanceof Flushable) { - ((Flushable)buffer).flush(); - } - } - - @Override - public void close() throws IOException { - flush(); - if (buffer instanceof Closeable) { - ((Closeable)buffer).close(); - } - } - }); + fw = new PrintWriter(new AppendableWriter(buffer)); } public static String marshal(GenericRecord record) { @@ -267,10 +247,9 @@ public class GenericRecordJsonWriter implements Closeable { private void printList(Object o) { fw.println('['); - final int[] c = new int[1]; - //noinspection unchecked int oldChildIndex = childIndex; childIndex = 0; + //noinspection unchecked ((List)o).forEach(e -> { writeValue(e); childIndex++; }); childIndex = oldChildIndex; fw.write(']'); @@ -430,14 +409,14 @@ public class GenericRecordJsonWriter implements Closeable { fw.write(']'); } - private String trimHex(final long l, final int size) { + static String trimHex(final long l, final int size) { final String b = Long.toHexString(l); int len = b.length(); return ZEROS.substring(0, Math.max(0,size-len)) + b.substring(Math.max(0,len-size), len); } - private static class NullOutputStream extends OutputStream { - private NullOutputStream() { + static class NullOutputStream extends OutputStream { + NullOutputStream() { } @Override @@ -452,4 +431,33 @@ public class GenericRecordJsonWriter implements Closeable { public void write(byte[] b) { } } + + static class AppendableWriter extends Writer { + private Appendable buffer; + + AppendableWriter(Appendable buffer) { + super(buffer); + this.buffer = buffer; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + buffer.append(String.valueOf(cbuf), off, len); + } + + @Override + public void flush() throws IOException { + if (buffer instanceof Flushable) { + ((Flushable)buffer).flush(); + } + } + + @Override + public void close() throws IOException { + flush(); + if (buffer instanceof Closeable) { + ((Closeable)buffer).close(); + } + } + } } diff --git a/src/java/org/apache/poi/util/GenericRecordXmlWriter.java b/src/java/org/apache/poi/util/GenericRecordXmlWriter.java new file mode 100644 index 0000000000..44ca83f14c --- /dev/null +++ b/src/java/org/apache/poi/util/GenericRecordXmlWriter.java @@ -0,0 +1,480 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.util; + +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.nio.charset.StandardCharsets; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.xml.bind.DatatypeConverter; + +import org.apache.poi.common.usermodel.GenericRecord; +import org.apache.poi.util.GenericRecordJsonWriter.AppendableWriter; +import org.apache.poi.util.GenericRecordJsonWriter.NullOutputStream; + +public class GenericRecordXmlWriter implements Closeable { + private static final String TABS; + private static final String ZEROS = "0000000000000000"; + private static final Pattern ESC_CHARS = Pattern.compile("[<>&'\"\\p{Cntrl}]"); + + private static final List>> handler = new ArrayList<>(); + + static { + char[] t = new char[255]; + Arrays.fill(t, '\t'); + TABS = new String(t); + handler(String.class, GenericRecordXmlWriter::printObject); + handler(Number.class, GenericRecordXmlWriter::printNumber); + handler(Boolean.class, GenericRecordXmlWriter::printBoolean); + handler(List.class, GenericRecordXmlWriter::printList); + // handler(GenericRecord.class, GenericRecordXmlWriter::printGenericRecord); + handler(GenericRecordUtil.AnnotatedFlag.class, GenericRecordXmlWriter::printAnnotatedFlag); + handler(byte[].class, GenericRecordXmlWriter::printBytes); + handler(Point2D.class, GenericRecordXmlWriter::printPoint); + handler(Dimension2D.class, GenericRecordXmlWriter::printDimension); + handler(Rectangle2D.class, GenericRecordXmlWriter::printRectangle); + handler(Path2D.class, GenericRecordXmlWriter::printPath); + handler(AffineTransform.class, GenericRecordXmlWriter::printAffineTransform); + handler(Color.class, GenericRecordXmlWriter::printColor); + handler(BufferedImage.class, GenericRecordXmlWriter::printBufferedImage); + handler(Array.class, GenericRecordXmlWriter::printArray); + handler(Object.class, GenericRecordXmlWriter::printObject); + } + + private static void handler(Class c, BiConsumer printer) { + handler.add(new AbstractMap.SimpleEntry<>(c, printer)); + } + + private final PrintWriter fw; + private int indent = 0; + private boolean withComments = true; + private int childIndex = 0; + private boolean attributePhase = true; + + public GenericRecordXmlWriter(File fileName) throws IOException { + OutputStream os = ("null".equals(fileName.getName())) ? new NullOutputStream() : new FileOutputStream(fileName); + fw = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8)); + } + + public GenericRecordXmlWriter(Appendable buffer) { + fw = new PrintWriter(new AppendableWriter(buffer)); + } + + public static String marshal(GenericRecord record) { + return marshal(record, true); + } + + public static String marshal(GenericRecord record, boolean withComments) { + final StringBuilder sb = new StringBuilder(); + try (GenericRecordXmlWriter w = new GenericRecordXmlWriter(sb)) { + w.setWithComments(withComments); + w.write(record); + return sb.toString(); + } catch (IOException e) { + return ""; + } + } + + public void setWithComments(boolean withComments) { + this.withComments = withComments; + } + + @Override + public void close() throws IOException { + fw.close(); + } + + private String tabs() { + return TABS.substring(0, Math.min(indent, TABS.length())); + } + + public void write(GenericRecord record) { + write(record, "record"); + } + + private void write(GenericRecord record, final String name) { + final String tabs = tabs(); + Enum type = record.getGenericRecordType(); + String recordName = (type != null) ? type.name() : record.getClass().getSimpleName(); + fw.append(tabs); + fw.append("<"+name+" type=\""); + fw.append(recordName); + fw.append("\""); + if (childIndex > 0) { + fw.append(" index=\""); + fw.print(childIndex); + fw.append("\""); + } + + boolean hasChildren = false; + + Map> prop = record.getGenericProperties(); + if (prop != null) { + final int oldChildIndex = childIndex; + childIndex = 0; + attributePhase = true; + List>> complex = prop.entrySet().stream().flatMap(this::writeProp).collect(Collectors.toList()); + attributePhase = false; + if (!complex.isEmpty()) { + hasChildren = true; + fw.println(">"); + indent++; + complex.forEach(this::writeProp); + indent--; + } + childIndex = oldChildIndex; + } else { + fw.print(">"); + } + + attributePhase = false; + + List list = record.getGenericChildren(); + if (list != null && !list.isEmpty()) { + hasChildren = true; + indent++; + fw.println(); + fw.append(tabs()); + fw.println(""); + indent++; + final int oldChildIndex = childIndex; + childIndex = 0; + list.forEach(l -> { writeValue("record", l); childIndex++; }); + childIndex = oldChildIndex; + fw.println(); + indent--; + fw.append(tabs()); + fw.println(""); + indent--; + } + + if (hasChildren) { + fw.append(tabs); + fw.println(""); + } else { + fw.println("/>"); + } + } + + public void writeError(String errorMsg) { + fw.append(""); + printObject(errorMsg); + fw.append(""); + } + + private Stream>> writeProp(Map.Entry> me) { + Object obj = me.getValue().get(); + if (obj == null) { + return Stream.empty(); + } + + final boolean isComplex = isComplex(obj); + if (attributePhase == isComplex) { + return isComplex ? Stream.of(new AbstractMap.SimpleEntry<>(me.getKey(), () -> obj)) : Stream.empty(); + } + + final int oldChildIndex = childIndex; + childIndex = 0; + writeValue(me.getKey(), obj); + childIndex = oldChildIndex; + + return Stream.empty(); + } + + private static boolean isComplex(Object obj) { + return !( + obj instanceof Number || + obj instanceof Boolean || + obj instanceof Character || + obj instanceof String || + obj instanceof Color || + obj instanceof Enum); + } + + private void writeValue(String key, Object o) { + assert(key != null); + if (o instanceof GenericRecord) { + printGenericRecord((GenericRecord)o, key); + } else if (o != null) { + if (key.endsWith(">")) { + fw.print("\t"); + } + + fw.print(attributePhase ? " " + key + "=\"" : tabs()+"<" + key); + if (key.endsWith(">")) { + fw.println(); + } + + handler.stream(). + filter(h -> matchInstanceOrArray(h.getKey(), o)). + findFirst(). + ifPresent(h -> h.getValue().accept(this, o)); + + if (attributePhase) { + fw.append("\""); + } + + if (key.endsWith(">")) { + fw.println(tabs()+"\t"); + } + } + } + + private static boolean matchInstanceOrArray(Class key, Object instance) { + return key.isInstance(instance) || (Array.class.equals(key) && instance.getClass().isArray()); + } + private void printNumber(Object o) { + assert(attributePhase); + Number n = (Number)o; + fw.print(n.toString()); + + if (attributePhase) { + return; + } + + final int size; + if (n instanceof Byte) { + size = 2; + } else if (n instanceof Short) { + size = 4; + } else if (n instanceof Integer) { + size = 8; + } else if (n instanceof Long) { + size = 16; + } else { + size = -1; + } + + long l = n.longValue(); + if (withComments && size > 0 && (l < 0 || l > 9)) { + fw.write(" /* 0x"); + fw.write(trimHex(l, size)); + fw.write(" */"); + } + } + + private void printBoolean(Object o) { + fw.write(((Boolean)o).toString()); + } + + private void printList(Object o) { + assert (!attributePhase); + fw.println(">"); + int oldChildIndex = childIndex; + childIndex = 0; + //noinspection unchecked + ((List)o).forEach(e -> { writeValue("item>", e); childIndex++; }); + childIndex = oldChildIndex; + } + + private void printArray(Object o) { + assert (!attributePhase); + fw.println(">"); + int length = Array.getLength(o); + final int oldChildIndex = childIndex; + for (childIndex=0; childIndex", Array.get(o, childIndex)); + } + childIndex = oldChildIndex; + } + + private void printGenericRecord(Object o, String name) { + write((GenericRecord) o, name); + } + + private void printAnnotatedFlag(Object o) { + assert (!attributePhase); + GenericRecordUtil.AnnotatedFlag af = (GenericRecordUtil.AnnotatedFlag) o; + Number n = af.getValue().get(); + int len; + if (n instanceof Byte) { + len = 2; + } else if (n instanceof Short) { + len = 4; + } else if (n instanceof Integer) { + len = 8; + } else { + len = 16; + } + + fw.print(" flag=\"0x"); + fw.print(trimHex(n.longValue(), len)); + fw.print('"'); + if (withComments) { + fw.print(" description=\""); + fw.print(af.getDescription()); + fw.print("\""); + } + fw.println("/>"); + } + + private void printBytes(Object o) { + assert (!attributePhase); + fw.write(">"); + fw.write(DatatypeConverter.printBase64Binary((byte[]) o)); + } + + private void printPoint(Object o) { + assert (!attributePhase); + Point2D p = (Point2D)o; + fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\"/>"); + } + + private void printDimension(Object o) { + assert (!attributePhase); + Dimension2D p = (Dimension2D)o; + fw.println(" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>"); + } + + private void printRectangle(Object o) { + assert (!attributePhase); + Rectangle2D p = (Rectangle2D)o; + fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>"); + } + + private void printPath(Object o) { + assert (!attributePhase); + final PathIterator iter = ((Path2D)o).getPathIterator(null); + final double[] pnts = new double[6]; + + indent += 2; + String t = tabs(); + indent -= 2; + + boolean isNext = false; + while (!iter.isDone()) { + fw.print(t); + isNext = true; + final int segType = iter.currentSegment(pnts); + fw.print(""); + iter.next(); + } + + } + + private void printObject(Object o) { + final Matcher m = ESC_CHARS.matcher(o.toString()); + final StringBuffer sb = new StringBuffer(); + while (m.find()) { + String repl; + String match = m.group(); + switch (match) { + case "<": + repl = "<"; + break; + case ">": + repl = ">"; + break; + case "&": + repl = "&"; + break; + case "\'": + repl = "'"; + break; + case "\"": + repl = """; + break; + default: + repl = "&#x" + Long.toHexString(match.codePointAt(0)) + ";"; + break; + } + m.appendReplacement(sb, repl); + } + m.appendTail(sb); + fw.write(sb.toString()); + } + + private void printAffineTransform(Object o) { + assert (!attributePhase); + AffineTransform xForm = (AffineTransform)o; + fw.write( + " scaleX=\""+xForm.getScaleX()+"\" "+ + "shearX=\""+xForm.getShearX()+"\" "+ + "transX=\""+xForm.getTranslateX()+"\" "+ + "scaleY=\""+xForm.getScaleY()+"\" "+ + "shearY=\""+xForm.getShearY()+"\" "+ + "transY=\""+xForm.getTranslateY()+"\"/>"); + } + + private void printColor(Object o) { + assert (attributePhase); + final int rgb = ((Color)o).getRGB(); + fw.print("0x"); + fw.print(trimHex(rgb, 8)); + } + + private void printBufferedImage(Object o) { + assert (!attributePhase); + BufferedImage bi = (BufferedImage)o; + fw.println(" width=\""+bi.getWidth()+"\" height=\""+bi.getHeight()+"\" bands=\""+bi.getColorModel().getNumComponents()+"\"/>"); + } + + private String trimHex(final long l, final int size) { + final String b = Long.toHexString(l); + int len = b.length(); + return ZEROS.substring(0, Math.max(0,size-len)) + b.substring(Math.max(0,len-size), len); + } + +} -- 2.39.5