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.

PropertyParser.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. * $Id$
  3. * ============================================================================
  4. * The Apache Software License, Version 1.1
  5. * ============================================================================
  6. *
  7. * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  8. *
  9. * Redistribution and use in source and binary forms, with or without modifica-
  10. * tion, are permitted provided that the following conditions are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright notice,
  13. * this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright notice,
  16. * this list of conditions and the following disclaimer in the documentation
  17. * and/or other materials provided with the distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if any, must
  20. * include the following acknowledgment: "This product includes software
  21. * developed by the Apache Software Foundation (http://www.apache.org/)."
  22. * Alternately, this acknowledgment may appear in the software itself, if
  23. * and wherever such third-party acknowledgments normally appear.
  24. *
  25. * 4. The names "FOP" and "Apache Software Foundation" must not be used to
  26. * endorse or promote products derived from this software without prior
  27. * written permission. For written permission, please contact
  28. * apache@apache.org.
  29. *
  30. * 5. Products derived from this software may not be called "Apache", nor may
  31. * "Apache" appear in their name, without prior written permission of the
  32. * Apache Software Foundation.
  33. *
  34. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
  35. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  36. * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  37. * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  38. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
  39. * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  40. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  41. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  42. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  43. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. * ============================================================================
  45. *
  46. * This software consists of voluntary contributions made by many individuals
  47. * on behalf of the Apache Software Foundation and was originally created by
  48. * James Tauber <jtauber@jtauber.com>. For more information on the Apache
  49. * Software Foundation, please see <http://www.apache.org/>.
  50. */
  51. package org.apache.fop.fo.expr;
  52. import org.apache.fop.fo.Property;
  53. import org.apache.fop.fo.ListProperty;
  54. import org.apache.fop.fo.LengthProperty;
  55. import org.apache.fop.fo.NumberProperty;
  56. import org.apache.fop.fo.StringProperty;
  57. import org.apache.fop.fo.ColorTypeProperty;
  58. import org.apache.fop.datatypes.PercentBase;
  59. import org.apache.fop.datatypes.PercentLength;
  60. import org.apache.fop.datatypes.Length;
  61. import org.apache.fop.datatypes.FixedLength;
  62. import org.apache.fop.datatypes.ColorType;
  63. import java.util.HashMap;
  64. /**
  65. * Class to parse XSL FO property expression.
  66. * This class is heavily based on the epxression parser in James Clark's
  67. * XT, an XSLT processor.
  68. */
  69. public class PropertyParser extends PropertyTokenizer {
  70. private PropertyInfo propInfo; // Maker and propertyList related info
  71. private static final String RELUNIT = "em";
  72. private static final Numeric negOne = new Numeric(new Double(-1.0));
  73. private static final HashMap functionTable = new HashMap();
  74. static {
  75. // Initialize the HashMap of XSL-defined functions
  76. functionTable.put("ceiling", new CeilingFunction());
  77. functionTable.put("floor", new FloorFunction());
  78. functionTable.put("round", new RoundFunction());
  79. functionTable.put("min", new MinFunction());
  80. functionTable.put("max", new MaxFunction());
  81. functionTable.put("abs", new AbsFunction());
  82. functionTable.put("rgb", new RGBColorFunction());
  83. functionTable.put("from-table-column", new FromTableColumnFunction());
  84. functionTable.put("inherited-property-value",
  85. new InheritedPropFunction());
  86. functionTable.put("from-parent", new FromParentFunction());
  87. functionTable.put("from-nearest-specified-value",
  88. new NearestSpecPropFunction());
  89. functionTable.put("proportional-column-width",
  90. new PPColWidthFunction());
  91. functionTable.put("label-end", new LabelEndFunction());
  92. functionTable.put("body-start", new BodyStartFunction());
  93. // NOTE: used from code generated for corresponding properties
  94. functionTable.put("_fop-property-value", new FopPropValFunction());
  95. /**
  96. * * NOT YET IMPLEMENTED!!!
  97. * functionTable.put("rgb-icc", new RgbICCFunction());
  98. * functionTable.put("system-color", new SystemColorFunction());
  99. * functionTable.put("system-font", new SystemFontFunction());
  100. *
  101. * functionTable.put("merge-property-values", new MergePropsFunction());
  102. */
  103. }
  104. /**
  105. * Public entrypoint to the Property expression parser.
  106. * @param expr The specified value (attribute on the xml element).
  107. * @param propInfo A PropertyInfo object representing the context in
  108. * which the property expression is to be evaluated.
  109. * @return A Property object holding the parsed result.
  110. * @throws PropertyException If the "expr" cannot be parsed as a Property.
  111. */
  112. public static Property parse(String expr, PropertyInfo propInfo)
  113. throws PropertyException {
  114. return new PropertyParser(expr, propInfo).parseProperty();
  115. }
  116. /**
  117. * Private constructor. Called by the static parse() method.
  118. * @param propExpr The specified value (attribute on the xml element).
  119. * @param propInfo A PropertyInfo object representing the context in
  120. * which the property expression is to be evaluated.
  121. */
  122. private PropertyParser(String propExpr, PropertyInfo pInfo) {
  123. super(propExpr);
  124. this.propInfo = pInfo;
  125. }
  126. /**
  127. * Parse the property expression described in the instance variables.
  128. * Note: If the property expression String is empty, a StringProperty
  129. * object holding an empty String is returned.
  130. * @return A Property object holding the parsed result.
  131. * @throws PropertyException If the "expr" cannot be parsed as a Property.
  132. */
  133. private Property parseProperty() throws PropertyException {
  134. next();
  135. if (currentToken == TOK_EOF) {
  136. // if prop value is empty string, force to StringProperty
  137. return new StringProperty("");
  138. }
  139. ListProperty propList = null;
  140. while (true) {
  141. Property prop = parseAdditiveExpr();
  142. if (currentToken == TOK_EOF) {
  143. if (propList != null) {
  144. propList.addProperty(prop);
  145. return propList;
  146. } else
  147. return prop;
  148. } else {
  149. if (propList == null) {
  150. propList = new ListProperty(prop);
  151. } else {
  152. propList.addProperty(prop);
  153. }
  154. }
  155. // throw new PropertyException("unexpected token");
  156. }
  157. // return prop;
  158. }
  159. /**
  160. * Try to parse an addition or subtraction expression and return the
  161. * resulting Property.
  162. */
  163. private Property parseAdditiveExpr() throws PropertyException {
  164. // Evaluate and put result on the operand stack
  165. Property prop = parseMultiplicativeExpr();
  166. loop:
  167. for (; ; ) {
  168. switch (currentToken) {
  169. case TOK_PLUS:
  170. next();
  171. prop = evalAddition(prop.getNumeric(),
  172. parseMultiplicativeExpr().getNumeric());
  173. break;
  174. case TOK_MINUS:
  175. next();
  176. prop =
  177. evalSubtraction(prop.getNumeric(),
  178. parseMultiplicativeExpr().getNumeric());
  179. break;
  180. default:
  181. break loop;
  182. }
  183. }
  184. return prop;
  185. }
  186. /**
  187. * Try to parse a multiply, divide or modulo expression and return
  188. * the resulting Property.
  189. */
  190. private Property parseMultiplicativeExpr() throws PropertyException {
  191. Property prop = parseUnaryExpr();
  192. loop:
  193. for (; ; ) {
  194. switch (currentToken) {
  195. case TOK_DIV:
  196. next();
  197. prop = evalDivide(prop.getNumeric(),
  198. parseUnaryExpr().getNumeric());
  199. break;
  200. case TOK_MOD:
  201. next();
  202. prop = evalModulo(prop.getNumber(),
  203. parseUnaryExpr().getNumber());
  204. break;
  205. case TOK_MULTIPLY:
  206. next();
  207. prop = evalMultiply(prop.getNumeric(),
  208. parseUnaryExpr().getNumeric());
  209. break;
  210. default:
  211. break loop;
  212. }
  213. }
  214. return prop;
  215. }
  216. /**
  217. * Try to parse a unary minus expression and return the
  218. * resulting Property.
  219. */
  220. private Property parseUnaryExpr() throws PropertyException {
  221. if (currentToken == TOK_MINUS) {
  222. next();
  223. return evalNegate(parseUnaryExpr().getNumeric());
  224. }
  225. return parsePrimaryExpr();
  226. }
  227. /**
  228. * Checks that the current token is a right parenthesis
  229. * and throws an exception if this isn't the case.
  230. */
  231. private final void expectRpar() throws PropertyException {
  232. if (currentToken != TOK_RPAR)
  233. throw new PropertyException("expected )");
  234. next();
  235. }
  236. /**
  237. * Try to parse a primary expression and return the
  238. * resulting Property.
  239. * A primary expression is either a parenthesized expression or an
  240. * expression representing a primitive Property datatype, such as a
  241. * string literal, an NCname, a number or a unit expression, or a
  242. * function call expression.
  243. */
  244. private Property parsePrimaryExpr() throws PropertyException {
  245. Property prop;
  246. switch (currentToken) {
  247. case TOK_LPAR:
  248. next();
  249. prop = parseAdditiveExpr();
  250. expectRpar();
  251. return prop;
  252. case TOK_LITERAL:
  253. prop = new StringProperty(currentTokenValue);
  254. break;
  255. case TOK_NCNAME:
  256. // Interpret this in context of the property or do it later?
  257. prop = new NCnameProperty(currentTokenValue);
  258. break;
  259. case TOK_FLOAT:
  260. prop = new NumberProperty(new Double(currentTokenValue));
  261. break;
  262. case TOK_INTEGER:
  263. prop = new NumberProperty(new Integer(currentTokenValue));
  264. break;
  265. case TOK_PERCENT:
  266. /*
  267. * Get the length base value object from the Maker. If null, then
  268. * this property can't have % values. Treat it as a real number.
  269. */
  270. double pcval =
  271. new Double(currentTokenValue.substring(0, currentTokenValue.length() - 1)).doubleValue()
  272. / 100.0;
  273. // LengthBase lbase = this.propInfo.getPercentLengthBase();
  274. PercentBase pcBase = this.propInfo.getPercentBase();
  275. if (pcBase != null) {
  276. if (pcBase.getDimension() == 0) {
  277. prop = new NumberProperty(pcval * pcBase.getBaseValue());
  278. } else if (pcBase.getDimension() == 1) {
  279. prop = new LengthProperty(new PercentLength(pcval,
  280. pcBase));
  281. } else {
  282. throw new PropertyException("Illegal percent dimension value");
  283. }
  284. } else {
  285. // WARNING? Interpret as a decimal fraction, eg. 50% = .5
  286. prop = new NumberProperty(pcval);
  287. }
  288. break;
  289. case TOK_NUMERIC:
  290. // A number plus a valid unit name.
  291. int numLen = currentTokenValue.length() - currentUnitLength;
  292. String unitPart = currentTokenValue.substring(numLen);
  293. Double numPart = new Double(currentTokenValue.substring(0,
  294. numLen));
  295. Length length = null;
  296. if (unitPart.equals(RELUNIT)) {
  297. length = new FixedLength(numPart.doubleValue(),
  298. propInfo.currentFontSize());
  299. } else
  300. length = new FixedLength(numPart.doubleValue(), unitPart);
  301. if (length == null) {
  302. throw new PropertyException("unrecognized unit name: "
  303. + currentTokenValue);
  304. } else
  305. prop = new LengthProperty(length);
  306. break;
  307. case TOK_COLORSPEC:
  308. prop = new ColorTypeProperty(new ColorType(currentTokenValue));
  309. break;
  310. case TOK_FUNCTION_LPAR: {
  311. Function function =
  312. (Function)functionTable.get(currentTokenValue);
  313. if (function == null) {
  314. throw new PropertyException("no such function: "
  315. + currentTokenValue);
  316. }
  317. next();
  318. // Push new function (for function context: getPercentBase())
  319. propInfo.pushFunction(function);
  320. prop = function.eval(parseArgs(function.nbArgs()), propInfo);
  321. propInfo.popFunction();
  322. return prop;
  323. }
  324. default:
  325. throw new PropertyException("syntax error");
  326. }
  327. next();
  328. return prop;
  329. }
  330. /**
  331. * Parse a comma separated list of function arguments. Each argument
  332. * may itself be an expression. This method consumes the closing right
  333. * parenthesis of the argument list.
  334. * @param nbArgs The number of arguments expected by the function.
  335. * @return An array of Property objects representing the arguments
  336. * found.
  337. * @throws PropertyException If the number of arguments found isn't equal
  338. * to the number expected.
  339. */
  340. Property[] parseArgs(int nbArgs) throws PropertyException {
  341. Property[] args = new Property[nbArgs];
  342. Property prop;
  343. int i = 0;
  344. if (currentToken == TOK_RPAR) {
  345. // No args: func()
  346. next();
  347. } else {
  348. while (true) {
  349. prop = parseAdditiveExpr();
  350. if (i < nbArgs) {
  351. args[i++] = prop;
  352. }
  353. // ignore extra args
  354. if (currentToken != TOK_COMMA)
  355. break;
  356. next();
  357. }
  358. expectRpar();
  359. }
  360. if (nbArgs != i) {
  361. throw new PropertyException("Wrong number of args for function");
  362. }
  363. return args;
  364. }
  365. /**
  366. * Evaluate an addition operation. If either of the arguments is null,
  367. * this means that it wasn't convertible to a Numeric value.
  368. * @param op1 A Numeric object (Number or Length-type object)
  369. * @param op2 A Numeric object (Number or Length-type object)
  370. * @return A new NumericProperty object holding an object which represents
  371. * the sum of the two operands.
  372. * @throws PropertyException If either operand is null.
  373. */
  374. private Property evalAddition(Numeric op1,
  375. Numeric op2) throws PropertyException {
  376. if (op1 == null || op2 == null)
  377. throw new PropertyException("Non numeric operand in addition");
  378. return new NumericProperty(op1.add(op2));
  379. }
  380. /**
  381. * Evaluate a subtraction operation. If either of the arguments is null,
  382. * this means that it wasn't convertible to a Numeric value.
  383. * @param op1 A Numeric object (Number or Length-type object)
  384. * @param op2 A Numeric object (Number or Length-type object)
  385. * @return A new NumericProperty object holding an object which represents
  386. * the difference of the two operands.
  387. * @throws PropertyException If either operand is null.
  388. */
  389. private Property evalSubtraction(Numeric op1,
  390. Numeric op2) throws PropertyException {
  391. if (op1 == null || op2 == null)
  392. throw new PropertyException("Non numeric operand in subtraction");
  393. return new NumericProperty(op1.subtract(op2));
  394. }
  395. /**
  396. * Evaluate a unary minus operation. If the argument is null,
  397. * this means that it wasn't convertible to a Numeric value.
  398. * @param op A Numeric object (Number or Length-type object)
  399. * @return A new NumericProperty object holding an object which represents
  400. * the negative of the operand (multiplication by *1).
  401. * @throws PropertyException If the operand is null.
  402. */
  403. private Property evalNegate(Numeric op) throws PropertyException {
  404. if (op == null)
  405. throw new PropertyException("Non numeric operand to unary minus");
  406. return new NumericProperty(op.multiply(negOne));
  407. }
  408. /**
  409. * Evaluate a multiplication operation. If either of the arguments is null,
  410. * this means that it wasn't convertible to a Numeric value.
  411. * @param op1 A Numeric object (Number or Length-type object)
  412. * @param op2 A Numeric object (Number or Length-type object)
  413. * @return A new NumericProperty object holding an object which represents
  414. * the product of the two operands.
  415. * @throws PropertyException If either operand is null.
  416. */
  417. private Property evalMultiply(Numeric op1,
  418. Numeric op2) throws PropertyException {
  419. if (op1 == null || op2 == null)
  420. throw new PropertyException("Non numeric operand in multiplication");
  421. return new NumericProperty(op1.multiply(op2));
  422. }
  423. /**
  424. * Evaluate a division operation. If either of the arguments is null,
  425. * this means that it wasn't convertible to a Numeric value.
  426. * @param op1 A Numeric object (Number or Length-type object)
  427. * @param op2 A Numeric object (Number or Length-type object)
  428. * @return A new NumericProperty object holding an object which represents
  429. * op1 divided by op2.
  430. * @throws PropertyException If either operand is null.
  431. */
  432. private Property evalDivide(Numeric op1,
  433. Numeric op2) throws PropertyException {
  434. if (op1 == null || op2 == null)
  435. throw new PropertyException("Non numeric operand in division");
  436. return new NumericProperty(op1.divide(op2));
  437. }
  438. /**
  439. * Evaluate a modulo operation. If either of the arguments is null,
  440. * this means that it wasn't convertible to a Number value.
  441. * @param op1 A Number object
  442. * @param op2 A Number object
  443. * @return A new NumberProperty object holding an object which represents
  444. * op1 mod op2.
  445. * @throws PropertyException If either operand is null.
  446. */
  447. private Property evalModulo(Number op1,
  448. Number op2) throws PropertyException {
  449. if (op1 == null || op2 == null)
  450. throw new PropertyException("Non number operand to modulo");
  451. return new NumberProperty(op1.doubleValue() % op2.doubleValue());
  452. }
  453. }