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

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