You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GenericRecordXmlWriter.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.util;
  20. import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
  21. import java.awt.Color;
  22. import java.awt.geom.AffineTransform;
  23. import java.awt.geom.Dimension2D;
  24. import java.awt.geom.Path2D;
  25. import java.awt.geom.PathIterator;
  26. import java.awt.geom.Point2D;
  27. import java.awt.geom.Rectangle2D;
  28. import java.awt.image.BufferedImage;
  29. import java.io.Closeable;
  30. import java.io.File;
  31. import java.io.FileOutputStream;
  32. import java.io.IOException;
  33. import java.io.OutputStream;
  34. import java.io.OutputStreamWriter;
  35. import java.io.PrintWriter;
  36. import java.lang.reflect.Array;
  37. import java.nio.charset.StandardCharsets;
  38. import java.util.AbstractMap;
  39. import java.util.ArrayList;
  40. import java.util.Arrays;
  41. import java.util.Base64;
  42. import java.util.List;
  43. import java.util.Map;
  44. import java.util.function.Supplier;
  45. import java.util.regex.Matcher;
  46. import java.util.regex.Pattern;
  47. import java.util.stream.Collectors;
  48. import java.util.stream.Stream;
  49. import org.apache.poi.common.usermodel.GenericRecord;
  50. import org.apache.poi.util.GenericRecordJsonWriter.AppendableWriter;
  51. @SuppressWarnings("WeakerAccess")
  52. public class GenericRecordXmlWriter implements Closeable {
  53. private static final String TABS;
  54. private static final String ZEROS = "0000000000000000";
  55. private static final Pattern ESC_CHARS = Pattern.compile("[<>&'\"\\p{Cntrl}]");
  56. @FunctionalInterface
  57. protected interface GenericRecordHandler {
  58. /**
  59. * Handler method
  60. *
  61. * @param record the parent record, applied via instance method reference
  62. * @param name the name of the property
  63. * @param object the value of the property
  64. * @return {@code true}, if the element was handled and output produced,
  65. * The provided methods can be overridden and a implementation can return {@code false},
  66. * if the element hasn't been written to the stream
  67. */
  68. boolean print(GenericRecordXmlWriter record, String name, Object object);
  69. }
  70. private static final List<Map.Entry<Class<?>, GenericRecordHandler>> handler = new ArrayList<>();
  71. static {
  72. char[] t = new char[255];
  73. Arrays.fill(t, '\t');
  74. TABS = new String(t);
  75. handler(String.class, GenericRecordXmlWriter::printObject);
  76. handler(Number.class, GenericRecordXmlWriter::printNumber);
  77. handler(Boolean.class, GenericRecordXmlWriter::printBoolean);
  78. handler(List.class, GenericRecordXmlWriter::printList);
  79. // handler(GenericRecord.class, GenericRecordXmlWriter::printGenericRecord);
  80. handler(GenericRecordUtil.AnnotatedFlag.class, GenericRecordXmlWriter::printAnnotatedFlag);
  81. handler(byte[].class, GenericRecordXmlWriter::printBytes);
  82. handler(Point2D.class, GenericRecordXmlWriter::printPoint);
  83. handler(Dimension2D.class, GenericRecordXmlWriter::printDimension);
  84. handler(Rectangle2D.class, GenericRecordXmlWriter::printRectangle);
  85. handler(Path2D.class, GenericRecordXmlWriter::printPath);
  86. handler(AffineTransform.class, GenericRecordXmlWriter::printAffineTransform);
  87. handler(Color.class, GenericRecordXmlWriter::printColor);
  88. handler(BufferedImage.class, GenericRecordXmlWriter::printBufferedImage);
  89. handler(Array.class, GenericRecordXmlWriter::printArray);
  90. handler(Object.class, GenericRecordXmlWriter::printObject);
  91. }
  92. private static void handler(Class<?> c, GenericRecordHandler printer) {
  93. handler.add(new AbstractMap.SimpleEntry<>(c, printer));
  94. }
  95. private final PrintWriter fw;
  96. private int indent = 0;
  97. private boolean withComments = true;
  98. private int childIndex = 0;
  99. private boolean attributePhase = true;
  100. public GenericRecordXmlWriter(File fileName) throws IOException {
  101. OutputStream os = ("null".equals(fileName.getName())) ? NULL_OUTPUT_STREAM : new FileOutputStream(fileName);
  102. fw = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
  103. }
  104. public GenericRecordXmlWriter(Appendable buffer) {
  105. fw = new PrintWriter(new AppendableWriter(buffer));
  106. }
  107. public static String marshal(GenericRecord record) {
  108. return marshal(record, true);
  109. }
  110. public static String marshal(GenericRecord record, boolean withComments) {
  111. final StringBuilder sb = new StringBuilder();
  112. try (GenericRecordXmlWriter w = new GenericRecordXmlWriter(sb)) {
  113. w.setWithComments(withComments);
  114. w.write(record);
  115. return sb.toString();
  116. } catch (IOException e) {
  117. return "<record/>";
  118. }
  119. }
  120. public void setWithComments(boolean withComments) {
  121. this.withComments = withComments;
  122. }
  123. @Override
  124. public void close() throws IOException {
  125. fw.close();
  126. }
  127. protected String tabs() {
  128. return TABS.substring(0, Math.min(indent, TABS.length()));
  129. }
  130. public void write(GenericRecord record) {
  131. write("record", record);
  132. }
  133. protected void write(final String name, GenericRecord record) {
  134. final String tabs = tabs();
  135. Enum<?> type = record.getGenericRecordType();
  136. String recordName = (type != null) ? type.name() : record.getClass().getSimpleName();
  137. fw.append(tabs);
  138. fw.append("<").append(name).append(" type=\"");
  139. fw.append(recordName);
  140. fw.append("\"");
  141. if (childIndex > 0) {
  142. fw.append(" index=\"");
  143. fw.print(childIndex);
  144. fw.append("\"");
  145. }
  146. attributePhase = true;
  147. boolean hasComplex = writeProperties(record);
  148. attributePhase = false;
  149. hasComplex |= writeChildren(record, hasComplex);
  150. if (hasComplex) {
  151. fw.append(tabs);
  152. fw.println("</" + name + ">");
  153. } else {
  154. fw.println("/>");
  155. }
  156. }
  157. protected boolean writeProperties(GenericRecord record) {
  158. Map<String, Supplier<?>> prop = record.getGenericProperties();
  159. if (prop == null || prop.isEmpty()) {
  160. return false;
  161. }
  162. final int oldChildIndex = childIndex;
  163. childIndex = 0;
  164. List<Map.Entry<String,Supplier<?>>> complex = prop.entrySet().stream().flatMap(this::writeProp).collect(Collectors.toList());
  165. attributePhase = false;
  166. if (!complex.isEmpty()) {
  167. fw.println(">");
  168. indent++;
  169. complex.forEach(this::writeProp);
  170. indent--;
  171. }
  172. childIndex = oldChildIndex;
  173. return !complex.isEmpty();
  174. }
  175. protected boolean writeChildren(GenericRecord record, boolean hasComplexProperties) {
  176. List<? extends GenericRecord> list = record.getGenericChildren();
  177. if (list == null || list.isEmpty()) {
  178. return false;
  179. }
  180. if (!hasComplexProperties) {
  181. fw.print(">");
  182. }
  183. indent++;
  184. fw.println();
  185. fw.println(tabs()+"<children>");
  186. indent++;
  187. final int oldChildIndex = childIndex;
  188. childIndex = 0;
  189. list.forEach(l -> {
  190. writeValue("record", l);
  191. childIndex++;
  192. });
  193. childIndex = oldChildIndex;
  194. fw.println();
  195. indent--;
  196. fw.println(tabs()+"</children>");
  197. indent--;
  198. return true;
  199. }
  200. public void writeError(String errorMsg) {
  201. printObject("error", errorMsg);
  202. }
  203. protected Stream<Map.Entry<String,Supplier<?>>> writeProp(Map.Entry<String,Supplier<?>> me) {
  204. Object obj = me.getValue().get();
  205. if (obj == null) {
  206. return Stream.empty();
  207. }
  208. final boolean isComplex = isComplex(obj);
  209. if (attributePhase == isComplex) {
  210. return isComplex ? Stream.of(new AbstractMap.SimpleEntry<>(me.getKey(), () -> obj)) : Stream.empty();
  211. }
  212. final int oldChildIndex = childIndex;
  213. childIndex = 0;
  214. writeValue(me.getKey(), obj);
  215. childIndex = oldChildIndex;
  216. return Stream.empty();
  217. }
  218. protected static boolean isComplex(Object obj) {
  219. return !(
  220. obj instanceof Number ||
  221. obj instanceof Boolean ||
  222. obj instanceof Character ||
  223. obj instanceof String ||
  224. obj instanceof Color ||
  225. obj instanceof Enum);
  226. }
  227. protected void writeValue(String name, Object value) {
  228. assert(name != null);
  229. if (value instanceof GenericRecord) {
  230. printGenericRecord(name, value);
  231. } else if (value != null) {
  232. if (name.endsWith(">")) {
  233. fw.print("\t");
  234. }
  235. handler.stream().
  236. filter(h -> matchInstanceOrArray(h.getKey(), value)).
  237. findFirst().
  238. ifPresent(h -> h.getValue().print(this, name, value));
  239. }
  240. }
  241. protected static boolean matchInstanceOrArray(Class<?> key, Object instance) {
  242. return key.isInstance(instance) || (Array.class.equals(key) && instance.getClass().isArray());
  243. }
  244. protected void openName(String name) {
  245. name = name.replace(">>", ">");
  246. if (attributePhase) {
  247. fw.print(" " + name.replace('>',' ').trim() + "=\"");
  248. } else {
  249. fw.print(tabs() + "<" + name);
  250. if (name.endsWith(">")) {
  251. fw.println();
  252. }
  253. }
  254. }
  255. protected void closeName(String name) {
  256. name = name.replace(">>", ">");
  257. if (attributePhase) {
  258. fw.append("\"");
  259. } else {
  260. if (name.endsWith(">")) {
  261. fw.println(tabs() + "\t</" + name);
  262. } else {
  263. fw.println("/>");
  264. }
  265. }
  266. }
  267. protected boolean printNumber(String name, Object o) {
  268. assert(attributePhase);
  269. openName(name);
  270. Number n = (Number)o;
  271. fw.print(n.toString());
  272. closeName(name);
  273. return true;
  274. }
  275. protected boolean printBoolean(String name, Object o) {
  276. assert (attributePhase);
  277. openName(name);
  278. fw.write(((Boolean)o).toString());
  279. closeName(name);
  280. return true;
  281. }
  282. protected boolean printList(String name, Object o) {
  283. assert (!attributePhase);
  284. openName(name+">");
  285. int oldChildIndex = childIndex;
  286. childIndex = 0;
  287. ((List<?>)o).forEach(e -> { writeValue("item>", e); childIndex++; });
  288. childIndex = oldChildIndex;
  289. closeName(name+">");
  290. return true;
  291. }
  292. protected boolean printArray(String name, Object o) {
  293. assert (!attributePhase);
  294. openName(name+">");
  295. int length = Array.getLength(o);
  296. final int oldChildIndex = childIndex;
  297. for (childIndex=0; childIndex<length; childIndex++) {
  298. writeValue("item>", Array.get(o, childIndex));
  299. }
  300. childIndex = oldChildIndex;
  301. closeName(name+">");
  302. return true;
  303. }
  304. protected void printGenericRecord(String name, Object value) {
  305. write(name, (GenericRecord) value);
  306. }
  307. protected boolean printAnnotatedFlag(String name, Object o) {
  308. assert (!attributePhase);
  309. GenericRecordUtil.AnnotatedFlag af = (GenericRecordUtil.AnnotatedFlag) o;
  310. Number n = af.getValue().get();
  311. int len;
  312. if (n instanceof Byte) {
  313. len = 2;
  314. } else if (n instanceof Short) {
  315. len = 4;
  316. } else if (n instanceof Integer) {
  317. len = 8;
  318. } else {
  319. len = 16;
  320. }
  321. openName(name);
  322. fw.print(" flag=\"0x");
  323. fw.print(trimHex(n.longValue(), len));
  324. fw.print('"');
  325. if (withComments) {
  326. fw.print(" description=\"");
  327. fw.print(af.getDescription());
  328. fw.print("\"");
  329. }
  330. closeName(name);
  331. return true;
  332. }
  333. protected boolean printBytes(String name, Object o) {
  334. assert (!attributePhase);
  335. openName(name+">");
  336. fw.write(Base64.getEncoder().encodeToString((byte[]) o));
  337. closeName(name+">");
  338. return true;
  339. }
  340. protected boolean printPoint(String name, Object o) {
  341. assert (!attributePhase);
  342. openName(name);
  343. Point2D p = (Point2D)o;
  344. fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\"/>");
  345. closeName(name);
  346. return true;
  347. }
  348. protected boolean printDimension(String name, Object o) {
  349. assert (!attributePhase);
  350. openName(name);
  351. Dimension2D p = (Dimension2D)o;
  352. fw.println(" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>");
  353. closeName(name);
  354. return true;
  355. }
  356. protected boolean printRectangle(String name, Object o) {
  357. assert (!attributePhase);
  358. openName(name);
  359. Rectangle2D p = (Rectangle2D)o;
  360. fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>");
  361. closeName(name);
  362. return true;
  363. }
  364. protected boolean printPath(String name, Object o) {
  365. assert (!attributePhase);
  366. openName(name+">");
  367. final PathIterator iter = ((Path2D)o).getPathIterator(null);
  368. final double[] pnts = new double[6];
  369. indent += 2;
  370. String t = tabs();
  371. indent -= 2;
  372. while (!iter.isDone()) {
  373. fw.print(t);
  374. final int segType = iter.currentSegment(pnts);
  375. fw.print("<pathelement ");
  376. switch (segType) {
  377. case PathIterator.SEG_MOVETO:
  378. fw.print("type=\"move\" x=\""+pnts[0]+"\" y=\""+pnts[1]+"\"");
  379. break;
  380. case PathIterator.SEG_LINETO:
  381. fw.print("type=\"lineto\" x=\""+pnts[0]+"\" y=\""+pnts[1]+"\"");
  382. break;
  383. case PathIterator.SEG_QUADTO:
  384. fw.print("type=\"quad\" x1=\""+pnts[0]+"\" y1=\""+pnts[1]+"\" x2=\""+pnts[2]+"\" y2=\""+pnts[3]+"\"");
  385. break;
  386. case PathIterator.SEG_CUBICTO:
  387. fw.print("type=\"cubic\" x1=\""+pnts[0]+"\" y1=\""+pnts[1]+"\" x2=\""+pnts[2]+"\" y2=\""+pnts[3]+"\" x3=\""+pnts[4]+"\" y3=\""+pnts[5]+"\"");
  388. break;
  389. case PathIterator.SEG_CLOSE:
  390. fw.print("type=\"close\"");
  391. break;
  392. }
  393. fw.println("/>");
  394. iter.next();
  395. }
  396. closeName(name+">");
  397. return true;
  398. }
  399. protected boolean printObject(String name, Object o) {
  400. openName(name+">");
  401. final String str = o.toString();
  402. final Matcher m = ESC_CHARS.matcher(str);
  403. int pos = 0;
  404. while (m.find()) {
  405. fw.write(str, pos, m.start());
  406. String match = m.group();
  407. switch (match) {
  408. case "<":
  409. fw.write("&lt;");
  410. break;
  411. case ">":
  412. fw.write("&gt;");
  413. break;
  414. case "&":
  415. fw.write("&amp;");
  416. break;
  417. case "'":
  418. fw.write("&apos;");
  419. break;
  420. case "\"":
  421. fw.write("&quot;");
  422. break;
  423. default:
  424. fw.write("&#x");
  425. fw.write(Long.toHexString(match.codePointAt(0)));
  426. fw.write(";");
  427. break;
  428. }
  429. pos = m.end();
  430. }
  431. fw.append(str, pos, str.length());
  432. closeName(name+">");
  433. return true;
  434. }
  435. protected boolean printAffineTransform(String name, Object o) {
  436. assert (!attributePhase);
  437. openName(name);
  438. AffineTransform xForm = (AffineTransform)o;
  439. fw.write("<"+name+
  440. " scaleX=\""+xForm.getScaleX()+"\" "+
  441. "shearX=\""+xForm.getShearX()+"\" "+
  442. "transX=\""+xForm.getTranslateX()+"\" "+
  443. "scaleY=\""+xForm.getScaleY()+"\" "+
  444. "shearY=\""+xForm.getShearY()+"\" "+
  445. "transY=\""+xForm.getTranslateY()+"\"/>");
  446. closeName(name);
  447. return true;
  448. }
  449. protected boolean printColor(String name, Object o) {
  450. assert (attributePhase);
  451. openName(name);
  452. final int rgb = ((Color)o).getRGB();
  453. fw.print("0x"+trimHex(rgb, 8));
  454. closeName(name);
  455. return true;
  456. }
  457. protected boolean printBufferedImage(String name, Object o) {
  458. assert (!attributePhase);
  459. openName(name);
  460. BufferedImage bi = (BufferedImage)o;
  461. fw.println(" width=\""+bi.getWidth()+"\" height=\""+bi.getHeight()+"\" bands=\""+bi.getColorModel().getNumComponents()+"\"");
  462. closeName(name);
  463. return true;
  464. }
  465. protected String trimHex(final long l, final int size) {
  466. final String b = Long.toHexString(l);
  467. int len = b.length();
  468. return ZEROS.substring(0, Math.max(0,size-len)) + b.substring(Math.max(0,len-size), len);
  469. }
  470. }