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 17KB

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