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.

PresetParser.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  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.sl.draw.geom;
  20. import java.util.HashMap;
  21. import java.util.Map;
  22. import java.util.function.BiConsumer;
  23. import javax.xml.stream.XMLStreamConstants;
  24. import javax.xml.stream.XMLStreamException;
  25. import javax.xml.stream.XMLStreamReader;
  26. import org.apache.logging.log4j.LogManager;
  27. import org.apache.logging.log4j.Logger;
  28. import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier;
  29. import org.apache.poi.util.Internal;
  30. @Internal
  31. class PresetParser {
  32. enum Mode {
  33. FILE(PresetParser::updateFile),
  34. SHAPE_LST(PresetParser::updateShapeList),
  35. SHAPE(PresetParser::updateShape),
  36. GUIDE_LST(PresetParser::updateGuideList),
  37. AH_LST(PresetParser::updateAhList),
  38. CXN_LST(PresetParser::updateCxnList),
  39. PATH_LST(PresetParser::updatePathLst),
  40. PATH(PresetParser::updatePath);
  41. interface Handler {
  42. void update(PresetParser parser, XMLStreamReader sr) throws XMLStreamException;
  43. }
  44. final Handler handler;
  45. Mode(Handler handler) {
  46. this.handler = handler;
  47. }
  48. }
  49. private static final Logger LOG = LogManager.getLogger(PresetParser.class);
  50. private Mode mode;
  51. private final Map<String, CustomGeometry> geom = new HashMap<>();
  52. private CustomGeometry customGeometry;
  53. private boolean useAdjustValue;
  54. private Path path;
  55. PresetParser(Mode mode) {
  56. this.mode = mode;
  57. if (mode == Mode.SHAPE) {
  58. customGeometry = new CustomGeometry();
  59. geom.put("custom", customGeometry);
  60. }
  61. }
  62. void parse(XMLStreamReader sr) throws XMLStreamException {
  63. while (sr.hasNext()) {
  64. switch (sr.next()) {
  65. case XMLStreamConstants.START_ELEMENT:
  66. mode.handler.update(this, sr);
  67. break;
  68. case XMLStreamConstants.END_ELEMENT:
  69. endContext();
  70. break;
  71. case XMLStreamConstants.END_DOCUMENT:
  72. return;
  73. default:
  74. break;
  75. }
  76. }
  77. }
  78. Map<String, CustomGeometry> getGeom() {
  79. return geom;
  80. }
  81. private void updateFile(XMLStreamReader sr) {
  82. final String name = sr.getLocalName();
  83. assert("presetShapeDefinitons".equals(name));
  84. mode = Mode.SHAPE_LST;
  85. }
  86. private void updateShapeList(XMLStreamReader sr) {
  87. final String name = sr.getLocalName();
  88. customGeometry = new CustomGeometry();
  89. if (geom.containsKey(name)) {
  90. LOG.atWarn().log("Duplicate definition of {}", name);
  91. }
  92. geom.put(name, customGeometry);
  93. mode = Mode.SHAPE;
  94. }
  95. private void updateShape(XMLStreamReader sr) throws XMLStreamException {
  96. final String name = sr.getLocalName();
  97. switch (name) {
  98. case "avLst":
  99. useAdjustValue = true;
  100. mode = Mode.GUIDE_LST;
  101. break;
  102. case "gdLst":
  103. useAdjustValue = false;
  104. mode = Mode.GUIDE_LST;
  105. break;
  106. case "ahLst":
  107. mode = Mode.AH_LST;
  108. break;
  109. case "cxnLst":
  110. mode = Mode.CXN_LST;
  111. break;
  112. case "rect":
  113. addRectangle(sr);
  114. break;
  115. case "pathLst":
  116. mode = Mode.PATH_LST;
  117. break;
  118. }
  119. }
  120. private void updateGuideList(XMLStreamReader sr) throws XMLStreamException {
  121. final String name = sr.getLocalName();
  122. assert("gd".equals(name));
  123. final Guide gd;
  124. if (useAdjustValue) {
  125. customGeometry.addAdjustGuide((AdjustValue)(gd = new AdjustValue()));
  126. } else {
  127. customGeometry.addGeomGuide(gd = new Guide());
  128. }
  129. parseAttributes(sr, (key,val) -> {
  130. switch (key) {
  131. case "name":
  132. // CollapsedStringAdapter
  133. gd.setName(collapseString(val));
  134. break;
  135. case "fmla":
  136. gd.setFmla(val);
  137. break;
  138. }
  139. });
  140. int tag = nextTag(sr);
  141. assert(tag == XMLStreamConstants.END_ELEMENT);
  142. }
  143. private void updateAhList(XMLStreamReader sr) throws XMLStreamException {
  144. String name = sr.getLocalName();
  145. switch (name) {
  146. case "ahXY":
  147. addXY(sr);
  148. break;
  149. case "ahPolar":
  150. addPolar(sr);
  151. break;
  152. }
  153. }
  154. private void addXY(XMLStreamReader sr) throws XMLStreamException {
  155. XYAdjustHandle ahXY = new XYAdjustHandle();
  156. customGeometry.addAdjustHandle(ahXY);
  157. parseAttributes(sr, (key, val) -> {
  158. switch (key) {
  159. case "gdRefX":
  160. ahXY.setGdRefX(collapseString(val));
  161. break;
  162. case "minX":
  163. ahXY.setMinX(val);
  164. break;
  165. case "maxX":
  166. ahXY.setMaxX(val);
  167. break;
  168. case "gdRefY":
  169. ahXY.setGdRefY(collapseString(val));
  170. break;
  171. case "minY":
  172. ahXY.setMinY(val);
  173. break;
  174. case "maxY":
  175. ahXY.setMaxY(val);
  176. break;
  177. }
  178. });
  179. ahXY.setPos(parsePosPoint(sr));
  180. }
  181. private void addPolar(XMLStreamReader sr) throws XMLStreamException {
  182. PolarAdjustHandle ahPolar = new PolarAdjustHandle();
  183. customGeometry.addAdjustHandle(ahPolar);
  184. parseAttributes(sr, (key, val) -> {
  185. switch (key) {
  186. case "gdRefR":
  187. ahPolar.setGdRefR(collapseString(val));
  188. break;
  189. case "minR":
  190. ahPolar.setMinR(val);
  191. break;
  192. case "maxR":
  193. ahPolar.setMaxR(val);
  194. break;
  195. case "gdRefAng":
  196. ahPolar.setGdRefAng(collapseString(val));
  197. break;
  198. case "minAng":
  199. ahPolar.setMinAng(val);
  200. break;
  201. case "maxAng":
  202. ahPolar.setMaxAng(val);
  203. break;
  204. }
  205. });
  206. ahPolar.setPos(parsePosPoint(sr));
  207. }
  208. private void updateCxnList(XMLStreamReader sr) throws XMLStreamException {
  209. String name = sr.getLocalName();
  210. assert("cxn".equals(name));
  211. ConnectionSite cxn = new ConnectionSite();
  212. customGeometry.addConnectionSite(cxn);
  213. parseAttributes(sr, (key, val) -> {
  214. if ("ang".equals(key)) {
  215. cxn.setAng(val);
  216. }
  217. });
  218. cxn.setPos(parsePosPoint(sr));
  219. }
  220. private void updatePathLst(XMLStreamReader sr) {
  221. String name = sr.getLocalName();
  222. assert("path".equals(name));
  223. path = new Path();
  224. customGeometry.addPath(path);
  225. parseAttributes(sr, (key, val) -> {
  226. switch (key) {
  227. case "w":
  228. path.setW(Long.parseLong(val));
  229. break;
  230. case "h":
  231. path.setH(Long.parseLong(val));
  232. break;
  233. case "fill":
  234. path.setFill(mapFill(val));
  235. break;
  236. case "stroke":
  237. path.setStroke(Boolean.parseBoolean(val));
  238. break;
  239. case "extrusionOk":
  240. path.setExtrusionOk(Boolean.parseBoolean(val));
  241. break;
  242. }
  243. });
  244. mode = Mode.PATH;
  245. }
  246. private static PaintModifier mapFill(String fill) {
  247. switch (fill) {
  248. default:
  249. case "none":
  250. return PaintModifier.NONE;
  251. case "norm":
  252. return PaintModifier.NORM;
  253. case "lighten":
  254. return PaintModifier.LIGHTEN;
  255. case "lightenLess":
  256. return PaintModifier.LIGHTEN_LESS;
  257. case "darken":
  258. return PaintModifier.DARKEN;
  259. case "darkenLess":
  260. return PaintModifier.DARKEN_LESS;
  261. }
  262. }
  263. private void updatePath(XMLStreamReader sr) throws XMLStreamException {
  264. String name = sr.getLocalName();
  265. switch (name) {
  266. case "close":
  267. closePath(sr);
  268. break;
  269. case "moveTo":
  270. moveTo(sr);
  271. break;
  272. case "lnTo":
  273. lineTo(sr);
  274. break;
  275. case "arcTo":
  276. arcTo(sr);
  277. break;
  278. case "quadBezTo":
  279. quadBezTo(sr);
  280. break;
  281. case "cubicBezTo":
  282. cubicBezTo(sr);
  283. break;
  284. }
  285. }
  286. private void closePath(XMLStreamReader sr) throws XMLStreamException {
  287. path.addCommand(new ClosePathCommand());
  288. int tag = nextTag(sr);
  289. assert(tag == XMLStreamConstants.END_ELEMENT);
  290. }
  291. private void moveTo(XMLStreamReader sr) throws XMLStreamException {
  292. MoveToCommand cmd = new MoveToCommand();
  293. path.addCommand(cmd);
  294. AdjustPoint pt = parsePtPoint(sr, true);
  295. assert(pt != null);
  296. cmd.setPt(pt);
  297. }
  298. private void lineTo(XMLStreamReader sr) throws XMLStreamException {
  299. LineToCommand cmd = new LineToCommand();
  300. path.addCommand(cmd);
  301. AdjustPoint pt = parsePtPoint(sr, true);
  302. assert(pt != null);
  303. cmd.setPt(pt);
  304. }
  305. private void arcTo(XMLStreamReader sr) throws XMLStreamException {
  306. ArcToCommand cmd = new ArcToCommand();
  307. path.addCommand(cmd);
  308. parseAttributes(sr, (key, val) -> {
  309. switch (key) {
  310. case "wR":
  311. cmd.setWR(val);
  312. break;
  313. case "hR":
  314. cmd.setHR(val);
  315. break;
  316. case "stAng":
  317. cmd.setStAng(val);
  318. break;
  319. case "swAng":
  320. cmd.setSwAng(val);
  321. break;
  322. }
  323. });
  324. int tag = nextTag(sr);
  325. assert (tag == XMLStreamConstants.END_ELEMENT);
  326. }
  327. private void quadBezTo(XMLStreamReader sr) throws XMLStreamException {
  328. QuadToCommand cmd = new QuadToCommand();
  329. path.addCommand(cmd);
  330. AdjustPoint pt1 = parsePtPoint(sr, false);
  331. AdjustPoint pt2 = parsePtPoint(sr, true);
  332. assert (pt1 != null && pt2 != null);
  333. cmd.setPt1(pt1);
  334. cmd.setPt2(pt2);
  335. }
  336. private void cubicBezTo(XMLStreamReader sr) throws XMLStreamException {
  337. CurveToCommand cmd = new CurveToCommand();
  338. path.addCommand(cmd);
  339. AdjustPoint pt1 = parsePtPoint(sr, false);
  340. AdjustPoint pt2 = parsePtPoint(sr, false);
  341. AdjustPoint pt3 = parsePtPoint(sr, true);
  342. assert (pt1 != null && pt2 != null && pt3 != null);
  343. cmd.setPt1(pt1);
  344. cmd.setPt2(pt2);
  345. cmd.setPt3(pt3);
  346. }
  347. private void addRectangle(XMLStreamReader sr) throws XMLStreamException {
  348. String[] ltrb = new String[4];
  349. parseAttributes(sr, (key,val) -> {
  350. switch (key) {
  351. case "l":
  352. ltrb[0] = val;
  353. break;
  354. case "t":
  355. ltrb[1] = val;
  356. break;
  357. case "r":
  358. ltrb[2] = val;
  359. break;
  360. case "b":
  361. ltrb[3] = val;
  362. break;
  363. }
  364. });
  365. customGeometry.setTextBounds(ltrb[0],ltrb[1],ltrb[2],ltrb[3]);
  366. int tag = nextTag(sr);
  367. assert(tag == XMLStreamConstants.END_ELEMENT);
  368. }
  369. private void endContext() {
  370. switch (mode) {
  371. case FILE:
  372. case SHAPE_LST:
  373. mode = Mode.FILE;
  374. break;
  375. case SHAPE:
  376. mode = Mode.SHAPE_LST;
  377. break;
  378. case CXN_LST:
  379. case AH_LST:
  380. case GUIDE_LST:
  381. case PATH_LST:
  382. useAdjustValue = false;
  383. path = null;
  384. mode = Mode.SHAPE;
  385. break;
  386. case PATH:
  387. path = null;
  388. mode = Mode.PATH_LST;
  389. break;
  390. }
  391. }
  392. private AdjustPoint parsePosPoint(XMLStreamReader sr) throws XMLStreamException {
  393. return parseAdjPoint(sr, true, "pos");
  394. }
  395. private AdjustPoint parsePtPoint(XMLStreamReader sr, boolean closeOuter) throws XMLStreamException {
  396. return parseAdjPoint(sr, closeOuter, "pt");
  397. }
  398. private AdjustPoint parseAdjPoint(XMLStreamReader sr, boolean closeOuter, String name) throws XMLStreamException {
  399. int tag = nextTag(sr);
  400. if (tag == XMLStreamConstants.END_ELEMENT) {
  401. return null;
  402. }
  403. assert (name.equals(sr.getLocalName()));
  404. AdjustPoint pos = new AdjustPoint();
  405. parseAttributes(sr, (key, val) -> {
  406. switch (key) {
  407. case "x":
  408. pos.setX(val);
  409. break;
  410. case "y":
  411. pos.setY(val);
  412. break;
  413. }
  414. });
  415. tag = nextTag(sr);
  416. assert(tag == XMLStreamConstants.END_ELEMENT);
  417. if (closeOuter) {
  418. tag = nextTag(sr);
  419. assert(tag == XMLStreamConstants.END_ELEMENT);
  420. }
  421. return pos;
  422. }
  423. private void parseAttributes(XMLStreamReader sr, BiConsumer<String,String> c) {
  424. for (int i=0; i<sr.getAttributeCount(); i++) {
  425. c.accept(sr.getAttributeLocalName(i), sr.getAttributeValue(i));
  426. }
  427. }
  428. /**
  429. * Reimplement {@link XMLStreamReader#nextTag()} because of differences of XmlBeans and Xerces XMLStreamReader.
  430. * XmlBeans doesn't return the END_ELEMENT on nextTag()
  431. *
  432. * @param sr the stream reader
  433. * @return the next tag type (START_ELEMENT, END_ELEMENT, END_DOCUMENT)
  434. * @throws XMLStreamException if reading the next tag fails
  435. */
  436. private static int nextTag(XMLStreamReader sr) throws XMLStreamException {
  437. int tag;
  438. do {
  439. tag = sr.next();
  440. } while (
  441. tag != XMLStreamConstants.START_ELEMENT &&
  442. tag != XMLStreamConstants.END_ELEMENT &&
  443. tag != XMLStreamConstants.END_DOCUMENT
  444. );
  445. return tag;
  446. }
  447. /**
  448. * Removes leading and trailing whitespaces of the string given as the parameter, then truncate any
  449. * sequence of tab, CR, LF, and SP by a single whitespace character ' '.
  450. */
  451. private static String collapseString(String text) {
  452. if (text==null) {
  453. return null;
  454. }
  455. int len = text.length();
  456. // most of the texts are already in the collapsed form. so look for the first whitespace in the hope that we
  457. // will never see it.
  458. int s;
  459. for (s=0; s<len; s++) {
  460. if (isWhiteSpace(text.charAt(s))) {
  461. break;
  462. }
  463. }
  464. if (s == len) {
  465. // the input happens to be already collapsed.
  466. return text;
  467. }
  468. // we now know that the input contains spaces. let's sit down and do the collapsing normally.
  469. // allocate enough size to avoid re-allocation
  470. StringBuilder result = new StringBuilder(len);
  471. if (s != 0) {
  472. for(int i=0; i<s; i++) {
  473. result.append(text.charAt(i));
  474. }
  475. result.append(' ');
  476. }
  477. boolean inStripMode = true;
  478. for (int i = s+1; i < len; i++) {
  479. char ch = text.charAt(i);
  480. boolean b = isWhiteSpace(ch);
  481. if (inStripMode && b) {
  482. // skip this character
  483. continue;
  484. }
  485. inStripMode = b;
  486. result.append(inStripMode ? ' ' : ch);
  487. }
  488. // remove trailing whitespaces
  489. len = result.length();
  490. if (len > 0 && result.charAt(len - 1) == ' ') {
  491. result.setLength(len - 1);
  492. }
  493. // whitespaces are already collapsed, so all we have to do is
  494. // to remove the last one character if it's a whitespace.
  495. return result.toString();
  496. }
  497. /** returns true if the specified char is a white space character. */
  498. private static boolean isWhiteSpace(char ch) {
  499. return ch == 0x9 || ch == 0xA || ch == 0xD || ch == 0x20;
  500. }
  501. }