123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- /*
- * ====================================================================
- * 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.Base64;
- import java.util.List;
- import java.util.Map;
- 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 org.apache.poi.common.usermodel.GenericRecord;
- import org.apache.poi.util.GenericRecordJsonWriter.AppendableWriter;
- import org.apache.poi.util.GenericRecordJsonWriter.NullOutputStream;
-
- @SuppressWarnings("WeakerAccess")
- 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}]");
-
- @FunctionalInterface
- protected interface GenericRecordHandler {
- /**
- * Handler method
- *
- * @param record the parent record, applied via instance method reference
- * @param name the name of the property
- * @param object the value of the property
- * @return {@code true}, if the element was handled and output produced,
- * The provided methods can be overridden and a implementation can return {@code false},
- * if the element hasn't been written to the stream
- */
- boolean print(GenericRecordXmlWriter record, String name, Object object);
- }
-
- private static final List<Map.Entry<Class, GenericRecordHandler>> 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, GenericRecordHandler 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 "<record/>";
- }
- }
-
- public void setWithComments(boolean withComments) {
- this.withComments = withComments;
- }
-
- @Override
- public void close() throws IOException {
- fw.close();
- }
-
- protected String tabs() {
- return TABS.substring(0, Math.min(indent, TABS.length()));
- }
-
- public void write(GenericRecord record) {
- write("record", record);
- }
-
- protected void write(final String name, GenericRecord record) {
- 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("\"");
- }
-
-
- attributePhase = true;
-
- boolean hasComplex = writeProperties(record);
-
- attributePhase = false;
-
- hasComplex |= writeChildren(record, hasComplex);
-
- if (hasComplex) {
- fw.append(tabs);
- fw.println("</" + name + ">");
- } else {
- fw.println("/>");
- }
- }
-
- protected boolean writeProperties(GenericRecord record) {
- Map<String, Supplier<?>> prop = record.getGenericProperties();
- if (prop == null || prop.isEmpty()) {
- return false;
- }
-
- final int oldChildIndex = childIndex;
- childIndex = 0;
- List<Map.Entry<String,Supplier<?>>> complex = prop.entrySet().stream().flatMap(this::writeProp).collect(Collectors.toList());
-
- attributePhase = false;
- if (!complex.isEmpty()) {
- fw.println(">");
- indent++;
- complex.forEach(this::writeProp);
- indent--;
- }
- childIndex = oldChildIndex;
-
- return !complex.isEmpty();
- }
-
- protected boolean writeChildren(GenericRecord record, boolean hasComplexProperties) {
- List<? extends GenericRecord> list = record.getGenericChildren();
- if (list == null || list.isEmpty()) {
- return false;
- }
- if (!hasComplexProperties) {
- fw.print(">");
- }
-
- indent++;
- fw.println();
- fw.println(tabs()+"<children>");
- indent++;
- final int oldChildIndex = childIndex;
- childIndex = 0;
- list.forEach(l -> {
- writeValue("record", l);
- childIndex++;
- });
- childIndex = oldChildIndex;
- fw.println();
- indent--;
- fw.println(tabs()+"</children>");
- indent--;
-
- return true;
- }
-
- public void writeError(String errorMsg) {
- printObject("error", errorMsg);
- }
-
- protected Stream<Map.Entry<String,Supplier<?>>> writeProp(Map.Entry<String,Supplier<?>> 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();
- }
-
- protected static boolean isComplex(Object obj) {
- return !(
- obj instanceof Number ||
- obj instanceof Boolean ||
- obj instanceof Character ||
- obj instanceof String ||
- obj instanceof Color ||
- obj instanceof Enum);
- }
-
- protected void writeValue(String name, Object value) {
- assert(name != null);
- if (value instanceof GenericRecord) {
- printGenericRecord(name, value);
- } else if (value != null) {
- if (name.endsWith(">")) {
- fw.print("\t");
- }
-
- handler.stream().
- filter(h -> matchInstanceOrArray(h.getKey(), value)).
- findFirst().
- ifPresent(h -> h.getValue().print(this, name, value));
-
- }
- }
-
- protected static boolean matchInstanceOrArray(Class key, Object instance) {
- return key.isInstance(instance) || (Array.class.equals(key) && instance.getClass().isArray());
- }
-
- protected void openName(String name) {
- name = name.replace(">>", ">");
- if (attributePhase) {
- fw.print(" " + name.replace('>',' ').trim() + "=\"");
- } else {
- fw.print(tabs() + "<" + name);
- if (name.endsWith(">")) {
- fw.println();
- }
- }
- }
-
- protected void closeName(String name) {
- name = name.replace(">>", ">");
- if (attributePhase) {
- fw.append("\"");
- } else {
- if (name.endsWith(">")) {
- fw.println(tabs() + "\t</" + name);
- } else {
- fw.println("/>");
- }
- }
- }
-
- protected boolean printNumber(String name, Object o) {
- assert(attributePhase);
-
- openName(name);
- Number n = (Number)o;
- fw.print(n.toString());
- closeName(name);
-
- return true;
- }
-
- protected boolean printBoolean(String name, Object o) {
- assert (attributePhase);
- openName(name);
- fw.write(((Boolean)o).toString());
- closeName(name);
- return true;
- }
-
- protected boolean printList(String name, Object o) {
- assert (!attributePhase);
- openName(name+">");
- int oldChildIndex = childIndex;
- childIndex = 0;
- //noinspection unchecked
- ((List)o).forEach(e -> { writeValue("item>", e); childIndex++; });
- childIndex = oldChildIndex;
- closeName(name+">");
- return true;
- }
-
- protected boolean printArray(String name, Object o) {
- assert (!attributePhase);
- openName(name+">");
- int length = Array.getLength(o);
- final int oldChildIndex = childIndex;
- for (childIndex=0; childIndex<length; childIndex++) {
- writeValue("item>", Array.get(o, childIndex));
- }
- childIndex = oldChildIndex;
- closeName(name+">");
- return true;
- }
-
- protected void printGenericRecord(String name, Object value) {
- write(name, (GenericRecord) value);
- }
-
- protected boolean printAnnotatedFlag(String name, 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;
- }
-
- openName(name);
- fw.print(" flag=\"0x");
- fw.print(trimHex(n.longValue(), len));
- fw.print('"');
- if (withComments) {
- fw.print(" description=\"");
- fw.print(af.getDescription());
- fw.print("\"");
- }
- closeName(name);
- return true;
- }
-
- protected boolean printBytes(String name, Object o) {
- assert (!attributePhase);
- openName(name+">");
- fw.write(Base64.getEncoder().encodeToString((byte[]) o));
- closeName(name+">");
- return true;
- }
-
- protected boolean printPoint(String name, Object o) {
- assert (!attributePhase);
- openName(name);
- Point2D p = (Point2D)o;
- fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\"/>");
- closeName(name);
- return true;
- }
-
- protected boolean printDimension(String name, Object o) {
- assert (!attributePhase);
- openName(name);
- Dimension2D p = (Dimension2D)o;
- fw.println(" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>");
- closeName(name);
- return true;
- }
-
- protected boolean printRectangle(String name, Object o) {
- assert (!attributePhase);
- openName(name);
- Rectangle2D p = (Rectangle2D)o;
- fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>");
- closeName(name);
- return true;
- }
-
- protected boolean printPath(String name, Object o) {
- assert (!attributePhase);
-
- openName(name+">");
-
- final PathIterator iter = ((Path2D)o).getPathIterator(null);
- final double[] pnts = new double[6];
-
- indent += 2;
- String t = tabs();
- indent -= 2;
-
- while (!iter.isDone()) {
- fw.print(t);
- final int segType = iter.currentSegment(pnts);
- fw.print("<pathelement ");
- switch (segType) {
- case PathIterator.SEG_MOVETO:
- fw.print("type=\"move\" x=\""+pnts[0]+"\" y=\""+pnts[1]+"\"");
- break;
- case PathIterator.SEG_LINETO:
- fw.print("type=\"lineto\" x=\""+pnts[0]+"\" y=\""+pnts[1]+"\"");
- break;
- case PathIterator.SEG_QUADTO:
- fw.print("type=\"quad\" x1=\""+pnts[0]+"\" y1=\""+pnts[1]+"\" x2=\""+pnts[2]+"\" y2=\""+pnts[3]+"\"");
- break;
- case PathIterator.SEG_CUBICTO:
- fw.print("type=\"cubic\" x1=\""+pnts[0]+"\" y1=\""+pnts[1]+"\" x2=\""+pnts[2]+"\" y2=\""+pnts[3]+"\" x3=\""+pnts[4]+"\" y3=\""+pnts[5]+"\"");
- break;
- case PathIterator.SEG_CLOSE:
- fw.print("type=\"close\"");
- break;
- }
- fw.println("/>");
- iter.next();
- }
-
- closeName(name+">");
- return true;
- }
-
- protected boolean printObject(String name, Object o) {
- openName(name+">");
- final String str = o.toString();
- final Matcher m = ESC_CHARS.matcher(str);
- int pos = 0;
- while (m.find()) {
- fw.write(str, pos, m.start());
- String match = m.group();
- switch (match) {
- case "<":
- fw.write("<");
- break;
- case ">":
- fw.write(">");
- break;
- case "&":
- fw.write("&");
- break;
- case "\'":
- fw.write("'");
- break;
- case "\"":
- fw.write(""");
- break;
- default:
- fw.write("&#x");
- fw.write(Long.toHexString(match.codePointAt(0)));
- fw.write(";");
- break;
- }
- pos = m.end();
- }
- fw.append(str, pos, str.length());
- closeName(name+">");
- return true;
- }
-
- protected boolean printAffineTransform(String name, Object o) {
- assert (!attributePhase);
- openName(name);
- AffineTransform xForm = (AffineTransform)o;
- fw.write("<"+name+
- " scaleX=\""+xForm.getScaleX()+"\" "+
- "shearX=\""+xForm.getShearX()+"\" "+
- "transX=\""+xForm.getTranslateX()+"\" "+
- "scaleY=\""+xForm.getScaleY()+"\" "+
- "shearY=\""+xForm.getShearY()+"\" "+
- "transY=\""+xForm.getTranslateY()+"\"/>");
- closeName(name);
- return true;
- }
-
- protected boolean printColor(String name, Object o) {
- assert (attributePhase);
- openName(name);
- final int rgb = ((Color)o).getRGB();
- fw.print("0x"+trimHex(rgb, 8));
- closeName(name);
- return true;
- }
-
- protected boolean printBufferedImage(String name, Object o) {
- assert (!attributePhase);
- openName(name);
- BufferedImage bi = (BufferedImage)o;
- fw.println(" width=\""+bi.getWidth()+"\" height=\""+bi.getHeight()+"\" bands=\""+bi.getColorModel().getNumComponents()+"\"");
- closeName(name);
- return true;
- }
-
- protected 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);
- }
-
- }
|