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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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. @SuppressWarnings("java:S1452")
  204. protected Stream<Map.Entry<String,Supplier<?>>> writeProp(Map.Entry<String,Supplier<?>> me) {
  205. Object obj = me.getValue().get();
  206. if (obj == null) {
  207. return Stream.empty();
  208. }
  209. final boolean isComplex = isComplex(obj);
  210. if (attributePhase == isComplex) {
  211. return isComplex ? Stream.of(new AbstractMap.SimpleEntry<>(me.getKey(), () -> obj)) : Stream.empty();
  212. }
  213. final int oldChildIndex = childIndex;
  214. childIndex = 0;
  215. writeValue(me.getKey(), obj);
  216. childIndex = oldChildIndex;
  217. return Stream.empty();
  218. }
  219. protected static boolean isComplex(Object obj) {
  220. return !(
  221. obj instanceof Number ||
  222. obj instanceof Boolean ||
  223. obj instanceof Character ||
  224. obj instanceof String ||
  225. obj instanceof Color ||
  226. obj instanceof Enum);
  227. }
  228. protected void writeValue(String name, Object value) {
  229. assert(name != null);
  230. if (value instanceof GenericRecord) {
  231. printGenericRecord(name, value);
  232. } else if (value != null) {
  233. if (name.endsWith(">")) {
  234. fw.print("\t");
  235. }
  236. handler.stream().
  237. filter(h -> matchInstanceOrArray(h.getKey(), value)).
  238. findFirst().
  239. ifPresent(h -> h.getValue().print(this, name, value));
  240. }
  241. }
  242. protected static boolean matchInstanceOrArray(Class<?> key, Object instance) {
  243. return key.isInstance(instance) || (Array.class.equals(key) && instance.getClass().isArray());
  244. }
  245. protected void openName(String name) {
  246. name = name.replace(">>", ">");
  247. if (attributePhase) {
  248. fw.print(" " + name.replace('>',' ').trim() + "=\"");
  249. } else {
  250. fw.print(tabs() + "<" + name);
  251. if (name.endsWith(">")) {
  252. fw.println();
  253. }
  254. }
  255. }
  256. protected void closeName(String name) {
  257. name = name.replace(">>", ">");
  258. if (attributePhase) {
  259. fw.append("\"");
  260. } else {
  261. if (name.endsWith(">")) {
  262. fw.println(tabs() + "\t</" + name);
  263. } else {
  264. fw.println("/>");
  265. }
  266. }
  267. }
  268. protected boolean printNumber(String name, Object o) {
  269. assert(attributePhase);
  270. openName(name);
  271. Number n = (Number)o;
  272. fw.print(n.toString());
  273. closeName(name);
  274. return true;
  275. }
  276. protected boolean printBoolean(String name, Object o) {
  277. assert (attributePhase);
  278. openName(name);
  279. fw.write(((Boolean)o).toString());
  280. closeName(name);
  281. return true;
  282. }
  283. protected boolean printList(String name, Object o) {
  284. assert (!attributePhase);
  285. openName(name+">");
  286. int oldChildIndex = childIndex;
  287. childIndex = 0;
  288. ((List<?>)o).forEach(e -> { writeValue("item>", e); childIndex++; });
  289. childIndex = oldChildIndex;
  290. closeName(name+">");
  291. return true;
  292. }
  293. protected boolean printArray(String name, Object o) {
  294. assert (!attributePhase);
  295. openName(name+">");
  296. int length = Array.getLength(o);
  297. final int oldChildIndex = childIndex;
  298. for (childIndex=0; childIndex<length; childIndex++) {
  299. writeValue("item>", Array.get(o, childIndex));
  300. }
  301. childIndex = oldChildIndex;
  302. closeName(name+">");
  303. return true;
  304. }
  305. protected void printGenericRecord(String name, Object value) {
  306. write(name, (GenericRecord) value);
  307. }
  308. protected boolean printAnnotatedFlag(String name, Object o) {
  309. assert (!attributePhase);
  310. GenericRecordUtil.AnnotatedFlag af = (GenericRecordUtil.AnnotatedFlag) o;
  311. Number n = af.getValue().get();
  312. int len;
  313. if (n instanceof Byte) {
  314. len = 2;
  315. } else if (n instanceof Short) {
  316. len = 4;
  317. } else if (n instanceof Integer) {
  318. len = 8;
  319. } else {
  320. len = 16;
  321. }
  322. openName(name);
  323. fw.print(" flag=\"0x");
  324. fw.print(trimHex(n.longValue(), len));
  325. fw.print('"');
  326. if (withComments) {
  327. fw.print(" description=\"");
  328. fw.print(af.getDescription());
  329. fw.print("\"");
  330. }
  331. closeName(name);
  332. return true;
  333. }
  334. protected boolean printBytes(String name, Object o) {
  335. assert (!attributePhase);
  336. openName(name+">");
  337. fw.write(Base64.getEncoder().encodeToString((byte[]) o));
  338. closeName(name+">");
  339. return true;
  340. }
  341. protected boolean printPoint(String name, Object o) {
  342. assert (!attributePhase);
  343. openName(name);
  344. Point2D p = (Point2D)o;
  345. fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\"/>");
  346. closeName(name);
  347. return true;
  348. }
  349. protected boolean printDimension(String name, Object o) {
  350. assert (!attributePhase);
  351. openName(name);
  352. Dimension2D p = (Dimension2D)o;
  353. fw.println(" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>");
  354. closeName(name);
  355. return true;
  356. }
  357. protected boolean printRectangle(String name, Object o) {
  358. assert (!attributePhase);
  359. openName(name);
  360. Rectangle2D p = (Rectangle2D)o;
  361. fw.println(" x=\""+p.getX()+"\" y=\""+p.getY()+"\" width=\""+p.getWidth()+"\" height=\""+p.getHeight()+"\"/>");
  362. closeName(name);
  363. return true;
  364. }
  365. protected boolean printPath(String name, Object o) {
  366. assert (!attributePhase);
  367. openName(name+">");
  368. final PathIterator iter = ((Path2D)o).getPathIterator(null);
  369. final double[] pnts = new double[6];
  370. indent += 2;
  371. String t = tabs();
  372. indent -= 2;
  373. while (!iter.isDone()) {
  374. fw.print(t);
  375. final int segType = iter.currentSegment(pnts);
  376. fw.print("<pathelement ");
  377. switch (segType) {
  378. case PathIterator.SEG_MOVETO:
  379. fw.print("type=\"move\" x=\""+pnts[0]+"\" y=\""+pnts[1]+"\"");
  380. break;
  381. case PathIterator.SEG_LINETO:
  382. fw.print("type=\"lineto\" x=\""+pnts[0]+"\" y=\""+pnts[1]+"\"");
  383. break;
  384. case PathIterator.SEG_QUADTO:
  385. fw.print("type=\"quad\" x1=\""+pnts[0]+"\" y1=\""+pnts[1]+"\" x2=\""+pnts[2]+"\" y2=\""+pnts[3]+"\"");
  386. break;
  387. case PathIterator.SEG_CUBICTO:
  388. fw.print("type=\"cubic\" x1=\""+pnts[0]+"\" y1=\""+pnts[1]+"\" x2=\""+pnts[2]+"\" y2=\""+pnts[3]+"\" x3=\""+pnts[4]+"\" y3=\""+pnts[5]+"\"");
  389. break;
  390. case PathIterator.SEG_CLOSE:
  391. fw.print("type=\"close\"");
  392. break;
  393. }
  394. fw.println("/>");
  395. iter.next();
  396. }
  397. closeName(name+">");
  398. return true;
  399. }
  400. protected boolean printObject(String name, Object o) {
  401. openName(name+">");
  402. final String str = o.toString();
  403. final Matcher m = ESC_CHARS.matcher(str);
  404. int pos = 0;
  405. while (m.find()) {
  406. fw.write(str, pos, m.start());
  407. String match = m.group();
  408. switch (match) {
  409. case "<":
  410. fw.write("&lt;");
  411. break;
  412. case ">":
  413. fw.write("&gt;");
  414. break;
  415. case "&":
  416. fw.write("&amp;");
  417. break;
  418. case "'":
  419. fw.write("&apos;");
  420. break;
  421. case "\"":
  422. fw.write("&quot;");
  423. break;
  424. default:
  425. fw.write("&#x");
  426. fw.write(Long.toHexString(match.codePointAt(0)));
  427. fw.write(";");
  428. break;
  429. }
  430. pos = m.end();
  431. }
  432. fw.append(str, pos, str.length());
  433. closeName(name+">");
  434. return true;
  435. }
  436. protected boolean printAffineTransform(String name, Object o) {
  437. assert (!attributePhase);
  438. openName(name);
  439. AffineTransform xForm = (AffineTransform)o;
  440. fw.write("<"+name+
  441. " scaleX=\""+xForm.getScaleX()+"\" "+
  442. "shearX=\""+xForm.getShearX()+"\" "+
  443. "transX=\""+xForm.getTranslateX()+"\" "+
  444. "scaleY=\""+xForm.getScaleY()+"\" "+
  445. "shearY=\""+xForm.getShearY()+"\" "+
  446. "transY=\""+xForm.getTranslateY()+"\"/>");
  447. closeName(name);
  448. return true;
  449. }
  450. protected boolean printColor(String name, Object o) {
  451. assert (attributePhase);
  452. openName(name);
  453. final int rgb = ((Color)o).getRGB();
  454. fw.print("0x"+trimHex(rgb, 8));
  455. closeName(name);
  456. return true;
  457. }
  458. protected boolean printBufferedImage(String name, Object o) {
  459. assert (!attributePhase);
  460. openName(name);
  461. BufferedImage bi = (BufferedImage)o;
  462. fw.println(" width=\""+bi.getWidth()+"\" height=\""+bi.getHeight()+"\" bands=\""+bi.getColorModel().getNumComponents()+"\"");
  463. closeName(name);
  464. return true;
  465. }
  466. protected String trimHex(final long l, final int size) {
  467. final String b = Long.toHexString(l);
  468. int len = b.length();
  469. return ZEROS.substring(0, Math.max(0,size-len)) + b.substring(Math.max(0,len-size), len);
  470. }
  471. }