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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fo.expr;
  19. import java.util.HashMap;
  20. import java.util.LinkedList;
  21. import java.util.List;
  22. import org.apache.xmlgraphics.util.UnitConv;
  23. import org.apache.fop.datatypes.Length;
  24. import org.apache.fop.datatypes.LengthBase;
  25. import org.apache.fop.datatypes.Numeric;
  26. import org.apache.fop.datatypes.PercentBase;
  27. import org.apache.fop.fo.properties.ColorProperty;
  28. import org.apache.fop.fo.properties.FixedLength;
  29. import org.apache.fop.fo.properties.ListProperty;
  30. import org.apache.fop.fo.properties.NumberProperty;
  31. import org.apache.fop.fo.properties.PercentLength;
  32. import org.apache.fop.fo.properties.Property;
  33. import org.apache.fop.fo.properties.StringProperty;
  34. /**
  35. * Class to parse XSL-FO property expressions.
  36. * This class is heavily based on the epxression parser in James Clark's
  37. * XT, an XSLT processor.
  38. */
  39. public final class PropertyParser extends PropertyTokenizer {
  40. private PropertyInfo propInfo; // Maker and propertyList related info
  41. private static final String RELUNIT = "em";
  42. private static final HashMap FUNCTION_TABLE = new HashMap();
  43. static {
  44. // Initialize the HashMap of XSL-defined functions
  45. FUNCTION_TABLE.put("ceiling", new CeilingFunction());
  46. FUNCTION_TABLE.put("floor", new FloorFunction());
  47. FUNCTION_TABLE.put("round", new RoundFunction());
  48. FUNCTION_TABLE.put("min", new MinFunction());
  49. FUNCTION_TABLE.put("max", new MaxFunction());
  50. FUNCTION_TABLE.put("abs", new AbsFunction());
  51. FUNCTION_TABLE.put("rgb", new RGBColorFunction());
  52. FUNCTION_TABLE.put("system-color", new SystemColorFunction());
  53. FUNCTION_TABLE.put("from-table-column", new FromTableColumnFunction());
  54. FUNCTION_TABLE.put("inherited-property-value", new InheritedPropFunction());
  55. FUNCTION_TABLE.put("from-nearest-specified-value", new FromNearestSpecifiedValueFunction());
  56. FUNCTION_TABLE.put("from-parent", new FromParentFunction());
  57. FUNCTION_TABLE.put("proportional-column-width", new ProportionalColumnWidthFunction());
  58. FUNCTION_TABLE.put("label-end", new LabelEndFunction());
  59. FUNCTION_TABLE.put("body-start", new BodyStartFunction());
  60. FUNCTION_TABLE.put("rgb-icc", new RGBICCColorFunction());
  61. FUNCTION_TABLE.put("rgb-named-color", new RGBNamedColorFunction()); //since XSL-FO 2.0
  62. FUNCTION_TABLE.put("cie-lab-color", new CIELabColorFunction()); //since XSL-FO 2.0
  63. FUNCTION_TABLE.put("cmyk", new CMYKColorFunction()); //non-standard!!!
  64. /**
  65. * * NOT YET IMPLEMENTED!!!
  66. * FUNCTION_TABLE.put("system-font", new SystemFontFunction());
  67. * FUNCTION_TABLE.put("merge-property-values", new MergePropsFunction());
  68. */
  69. }
  70. /**
  71. * Public entrypoint to the Property expression parser.
  72. * @param expr The specified value (attribute on the xml element).
  73. * @param propInfo A PropertyInfo object representing the context in
  74. * which the property expression is to be evaluated.
  75. * @return A Property object holding the parsed result.
  76. * @throws PropertyException If the "expr" cannot be parsed as a Property.
  77. */
  78. public static Property parse(String expr, PropertyInfo propInfo)
  79. throws PropertyException {
  80. try {
  81. return new PropertyParser(expr, propInfo).parseProperty();
  82. } catch (PropertyException exc) {
  83. exc.setPropertyInfo(propInfo);
  84. throw exc;
  85. }
  86. }
  87. /**
  88. * Private constructor. Called by the static parse() method.
  89. * @param propExpr The specified value (attribute on the xml element).
  90. * @param pInfo A PropertyInfo object representing the context in
  91. * which the property expression is to be evaluated.
  92. */
  93. private PropertyParser(String propExpr, PropertyInfo pInfo) {
  94. super(propExpr);
  95. this.propInfo = pInfo;
  96. }
  97. /**
  98. * Parse the property expression described in the instance variables.
  99. * Note: If the property expression String is empty, a StringProperty
  100. * object holding an empty String is returned.
  101. * @return A Property object holding the parsed result.
  102. * @throws PropertyException If the "expr" cannot be parsed as a Property.
  103. */
  104. private Property parseProperty() throws PropertyException {
  105. next();
  106. if (currentToken == TOK_EOF) {
  107. // if prop value is empty string, force to StringProperty
  108. return StringProperty.getInstance("");
  109. }
  110. ListProperty propList = null;
  111. while (true) {
  112. Property prop = parseAdditiveExpr();
  113. if (currentToken == TOK_EOF) {
  114. if (propList != null) {
  115. propList.addProperty(prop);
  116. return propList;
  117. } else {
  118. return prop;
  119. }
  120. } else {
  121. if (propList == null) {
  122. propList = new ListProperty(prop);
  123. } else {
  124. propList.addProperty(prop);
  125. }
  126. }
  127. // throw new PropertyException("unexpected token");
  128. }
  129. // return prop;
  130. }
  131. /**
  132. * Try to parse an addition or subtraction expression and return the
  133. * resulting Property.
  134. */
  135. private Property parseAdditiveExpr() throws PropertyException {
  136. // Evaluate and put result on the operand stack
  137. Property prop = parseMultiplicativeExpr();
  138. loop:
  139. while (true) {
  140. switch (currentToken) {
  141. case TOK_PLUS:
  142. next();
  143. prop = evalAddition(prop.getNumeric(),
  144. parseMultiplicativeExpr().getNumeric());
  145. break;
  146. case TOK_MINUS:
  147. next();
  148. prop = evalSubtraction(prop.getNumeric(),
  149. parseMultiplicativeExpr().getNumeric());
  150. break;
  151. default:
  152. break loop;
  153. }
  154. }
  155. return prop;
  156. }
  157. /**
  158. * Try to parse a multiply, divide or modulo expression and return
  159. * the resulting Property.
  160. */
  161. private Property parseMultiplicativeExpr() throws PropertyException {
  162. Property prop = parseUnaryExpr();
  163. loop:
  164. while (true) {
  165. switch (currentToken) {
  166. case TOK_DIV:
  167. next();
  168. prop = evalDivide(prop.getNumeric(),
  169. parseUnaryExpr().getNumeric());
  170. break;
  171. case TOK_MOD:
  172. next();
  173. prop = evalModulo(prop.getNumber(),
  174. parseUnaryExpr().getNumber());
  175. break;
  176. case TOK_MULTIPLY:
  177. next();
  178. prop = evalMultiply(prop.getNumeric(),
  179. parseUnaryExpr().getNumeric());
  180. break;
  181. default:
  182. break loop;
  183. }
  184. }
  185. return prop;
  186. }
  187. /**
  188. * Try to parse a unary minus expression and return the
  189. * resulting Property.
  190. */
  191. private Property parseUnaryExpr() throws PropertyException {
  192. if (currentToken == TOK_MINUS) {
  193. next();
  194. return evalNegate(parseUnaryExpr().getNumeric());
  195. }
  196. return parsePrimaryExpr();
  197. }
  198. /**
  199. * Checks that the current token is a right parenthesis
  200. * and throws an exception if this isn't the case.
  201. */
  202. private void expectRpar() throws PropertyException {
  203. if (currentToken != TOK_RPAR) {
  204. throw new PropertyException("expected )");
  205. }
  206. next();
  207. }
  208. /**
  209. * Try to parse a primary expression and return the
  210. * resulting Property.
  211. * A primary expression is either a parenthesized expression or an
  212. * expression representing a primitive Property datatype, such as a
  213. * string literal, an NCname, a number or a unit expression, or a
  214. * function call expression.
  215. */
  216. private Property parsePrimaryExpr() throws PropertyException {
  217. Property prop;
  218. if (currentToken == TOK_COMMA) {
  219. //Simply skip commas, for example for font-family
  220. next();
  221. }
  222. switch (currentToken) {
  223. case TOK_LPAR:
  224. next();
  225. prop = parseAdditiveExpr();
  226. expectRpar();
  227. return prop;
  228. case TOK_LITERAL:
  229. prop = StringProperty.getInstance(currentTokenValue);
  230. break;
  231. case TOK_NCNAME:
  232. // Interpret this in context of the property or do it later?
  233. prop = new NCnameProperty(currentTokenValue);
  234. break;
  235. case TOK_FLOAT:
  236. prop = NumberProperty.getInstance(new Double(currentTokenValue));
  237. break;
  238. case TOK_INTEGER:
  239. prop = NumberProperty.getInstance(new Integer(currentTokenValue));
  240. break;
  241. case TOK_PERCENT:
  242. /*
  243. * Get the length base value object from the Maker. If null, then
  244. * this property can't have % values. Treat it as a real number.
  245. */
  246. double pcval = Double.parseDouble(
  247. currentTokenValue.substring(0, currentTokenValue.length() - 1)) / 100.0;
  248. PercentBase pcBase = this.propInfo.getPercentBase();
  249. if (pcBase != null) {
  250. if (pcBase.getDimension() == 0) {
  251. prop = NumberProperty.getInstance(pcval * pcBase.getBaseValue());
  252. } else if (pcBase.getDimension() == 1) {
  253. if (pcBase instanceof LengthBase) {
  254. if (pcval == 0.0) {
  255. prop = FixedLength.ZERO_FIXED_LENGTH;
  256. break;
  257. }
  258. //If the base of the percentage is known
  259. //and absolute, it can be resolved by the
  260. //parser
  261. Length base = ((LengthBase)pcBase).getBaseLength();
  262. if (base != null && base.isAbsolute()) {
  263. prop = FixedLength.getInstance(pcval * base.getValue());
  264. break;
  265. }
  266. }
  267. prop = new PercentLength(pcval, pcBase);
  268. } else {
  269. throw new PropertyException("Illegal percent dimension value");
  270. }
  271. } else {
  272. // WARNING? Interpret as a decimal fraction, eg. 50% = .5
  273. prop = NumberProperty.getInstance(pcval);
  274. }
  275. break;
  276. case TOK_NUMERIC:
  277. // A number plus a valid unit name.
  278. int numLen = currentTokenValue.length() - currentUnitLength;
  279. String unitPart = currentTokenValue.substring(numLen);
  280. double numPart = Double.parseDouble(currentTokenValue.substring(0, numLen));
  281. if (RELUNIT.equals(unitPart)) {
  282. prop = (Property) NumericOp.multiply(
  283. NumberProperty.getInstance(numPart),
  284. propInfo.currentFontSize());
  285. } else {
  286. if ("px".equals(unitPart)) {
  287. //pass the ratio between target-resolution and
  288. //the default resolution of 72dpi
  289. float resolution = propInfo.getPropertyList().getFObj()
  290. .getUserAgent().getSourceResolution();
  291. prop = FixedLength.getInstance(
  292. numPart, unitPart,
  293. UnitConv.IN2PT / resolution);
  294. } else {
  295. //use default resolution of 72dpi
  296. prop = FixedLength.getInstance(numPart, unitPart);
  297. }
  298. }
  299. break;
  300. case TOK_COLORSPEC:
  301. prop = ColorProperty.getInstance(propInfo.getUserAgent(), currentTokenValue);
  302. break;
  303. case TOK_FUNCTION_LPAR:
  304. Function function = (Function)FUNCTION_TABLE.get(currentTokenValue);
  305. if (function == null) {
  306. throw new PropertyException("no such function: "
  307. + currentTokenValue);
  308. }
  309. next();
  310. // Push new function (for function context: getPercentBase())
  311. propInfo.pushFunction(function);
  312. prop = function.eval(parseArgs(function), propInfo);
  313. propInfo.popFunction();
  314. return prop;
  315. default:
  316. // TODO: add the token or the expr to the error message.
  317. throw new PropertyException("syntax error");
  318. }
  319. next();
  320. return prop;
  321. }
  322. /**
  323. * Parse a comma separated list of function arguments. Each argument
  324. * may itself be an expression. This method consumes the closing right
  325. * parenthesis of the argument list.
  326. * @param function The function object for which the arguments are collected.
  327. * @return An array of Property objects representing the arguments found.
  328. * @throws PropertyException If the number of arguments found isn't equal
  329. * to the number expected or if another argument parsing error occurs.
  330. */
  331. Property[] parseArgs(Function function) throws PropertyException {
  332. int numReq = function.getRequiredArgsCount(); // # required args
  333. int numOpt = function.getOptionalArgsCount(); // # optional args
  334. boolean hasVar = function.hasVariableArgs(); // has variable args
  335. List<Property> args = new java.util.ArrayList<Property>(numReq + numOpt);
  336. if (currentToken == TOK_RPAR) {
  337. // No args: func()
  338. next();
  339. } else {
  340. while (true) {
  341. Property p = parseAdditiveExpr();
  342. int i = args.size();
  343. if ( ( i < numReq ) || ( ( i - numReq ) < numOpt ) || hasVar ) {
  344. args.add ( p );
  345. } else {
  346. throw new PropertyException ( "Unexpected function argument at index " + i );
  347. }
  348. // ignore extra args
  349. if (currentToken != TOK_COMMA) {
  350. break;
  351. }
  352. next();
  353. }
  354. expectRpar();
  355. }
  356. int numArgs = args.size();
  357. if ( numArgs < numReq ) {
  358. throw new PropertyException("Expected " + numReq + " required arguments, but only " + numArgs + " specified");
  359. } else {
  360. for ( int i = 0; i < numOpt; i++ ) {
  361. if ( args.size() < ( numReq + i + 1 ) ) {
  362. args.add ( function.getOptionalArgDefault ( i, propInfo ) );
  363. }
  364. }
  365. }
  366. return (Property[]) args.toArray ( new Property [ args.size() ] );
  367. }
  368. /**
  369. * Evaluate an addition 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 sum of the two operands.
  375. * @throws PropertyException If either operand is null.
  376. */
  377. private Property evalAddition(Numeric op1,
  378. Numeric op2) throws PropertyException {
  379. if (op1 == null || op2 == null) {
  380. throw new PropertyException("Non numeric operand in addition");
  381. }
  382. return (Property) NumericOp.addition(op1, op2);
  383. }
  384. /**
  385. * Evaluate a subtraction 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. * the difference of the two operands.
  391. * @throws PropertyException If either operand is null.
  392. */
  393. private Property evalSubtraction(Numeric op1,
  394. Numeric op2) throws PropertyException {
  395. if (op1 == null || op2 == null) {
  396. throw new PropertyException("Non numeric operand in subtraction");
  397. }
  398. return (Property) NumericOp.subtraction(op1, op2);
  399. }
  400. /**
  401. * Evaluate a unary minus operation. If the argument is null,
  402. * this means that it wasn't convertible to a Numeric value.
  403. * @param op A Numeric object (Number or Length-type object)
  404. * @return A new NumericProperty object holding an object which represents
  405. * the negative of the operand (multiplication by *1).
  406. * @throws PropertyException If the operand is null.
  407. */
  408. private Property evalNegate(Numeric op) throws PropertyException {
  409. if (op == null) {
  410. throw new PropertyException("Non numeric operand to unary minus");
  411. }
  412. return (Property) NumericOp.negate(op);
  413. }
  414. /**
  415. * Evaluate a multiplication operation. If either of the arguments is null,
  416. * this means that it wasn't convertible to a Numeric value.
  417. * @param op1 A Numeric object (Number or Length-type object)
  418. * @param op2 A Numeric object (Number or Length-type object)
  419. * @return A new NumericProperty object holding an object which represents
  420. * the product of the two operands.
  421. * @throws PropertyException If either operand is null.
  422. */
  423. private Property evalMultiply(Numeric op1,
  424. Numeric op2) throws PropertyException {
  425. if (op1 == null || op2 == null) {
  426. throw new PropertyException("Non numeric operand in multiplication");
  427. }
  428. return (Property) NumericOp.multiply(op1, op2);
  429. }
  430. /**
  431. * Evaluate a division operation. If either of the arguments is null,
  432. * this means that it wasn't convertible to a Numeric value.
  433. * @param op1 A Numeric object (Number or Length-type object)
  434. * @param op2 A Numeric object (Number or Length-type object)
  435. * @return A new NumericProperty object holding an object which represents
  436. * op1 divided by op2.
  437. * @throws PropertyException If either operand is null.
  438. */
  439. private Property evalDivide(Numeric op1,
  440. Numeric op2) throws PropertyException {
  441. if (op1 == null || op2 == null) {
  442. throw new PropertyException("Non numeric operand in division");
  443. }
  444. return (Property) NumericOp.divide(op1, op2);
  445. }
  446. /**
  447. * Evaluate a modulo operation. If either of the arguments is null,
  448. * this means that it wasn't convertible to a Number value.
  449. * @param op1 A Number object
  450. * @param op2 A Number object
  451. * @return A new NumberProperty object holding an object which represents
  452. * op1 mod op2.
  453. * @throws PropertyException If either operand is null.
  454. */
  455. private Property evalModulo(Number op1,
  456. Number op2) throws PropertyException {
  457. if (op1 == null || op2 == null) {
  458. throw new PropertyException("Non number operand to modulo");
  459. }
  460. return NumberProperty.getInstance(op1.doubleValue() % op2.doubleValue());
  461. }
  462. }