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.

PropertyList.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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;
  19. // Java
  20. import org.xml.sax.Attributes;
  21. import org.apache.commons.logging.Log;
  22. import org.apache.commons.logging.LogFactory;
  23. import org.apache.xmlgraphics.util.QName;
  24. import org.apache.fop.apps.FOUserAgent;
  25. import org.apache.fop.fo.expr.PropertyException;
  26. import org.apache.fop.fo.properties.CommonAbsolutePosition;
  27. import org.apache.fop.fo.properties.CommonAural;
  28. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  29. import org.apache.fop.fo.properties.CommonFont;
  30. import org.apache.fop.fo.properties.CommonHyphenation;
  31. import org.apache.fop.fo.properties.CommonMarginBlock;
  32. import org.apache.fop.fo.properties.CommonMarginInline;
  33. import org.apache.fop.fo.properties.CommonRelativePosition;
  34. import org.apache.fop.fo.properties.CommonTextDecoration;
  35. import org.apache.fop.fo.properties.Property;
  36. import org.apache.fop.fo.properties.PropertyMaker;
  37. /**
  38. * Class containing the collection of properties for a given FObj.
  39. */
  40. public abstract class PropertyList {
  41. private static boolean[] inheritableProperty;
  42. /** reference to the parent FO's propertyList **/
  43. protected PropertyList parentPropertyList = null;
  44. private FObj fobj = null;
  45. private static Log log = LogFactory.getLog(PropertyList.class);
  46. /**
  47. * Basic constructor.
  48. * @param fObjToAttach the FO this PropertyList should be attached to
  49. * @param parentPropertyList the PropertyList belonging to the new objects
  50. * parent
  51. */
  52. public PropertyList(FObj fObjToAttach, PropertyList parentPropertyList) {
  53. this.fobj = fObjToAttach;
  54. this.parentPropertyList = parentPropertyList;
  55. }
  56. /**
  57. * @return the FObj object to which this propertyList is attached
  58. */
  59. public FObj getFObj() {
  60. return this.fobj;
  61. }
  62. /**
  63. * @return the FObj object attached to the parentPropertyList
  64. */
  65. public FObj getParentFObj() {
  66. if (parentPropertyList != null) {
  67. return parentPropertyList.getFObj();
  68. } else {
  69. return null;
  70. }
  71. }
  72. /**
  73. * @return the FObj object attached to the parentPropetyList
  74. */
  75. public PropertyList getParentPropertyList() {
  76. return parentPropertyList;
  77. }
  78. /**
  79. * Return the value explicitly specified on this FO.
  80. * @param propId The id of the property whose value is desired.
  81. * @return The value if the property is explicitly set or set by
  82. * a shorthand property, otherwise null.
  83. * @throws PropertyException ...
  84. */
  85. public Property getExplicitOrShorthand(int propId) throws PropertyException {
  86. /* Handle request for one part of a compound property */
  87. Property p = getExplicit(propId);
  88. if (p == null) {
  89. p = getShorthand(propId);
  90. }
  91. return p;
  92. }
  93. /**
  94. * Return the value explicitly specified on this FO.
  95. * @param propId The ID of the property whose value is desired.
  96. * @return The value if the property is explicitly set, otherwise null.
  97. */
  98. public abstract Property getExplicit(int propId);
  99. /**
  100. * Set an value defined explicitly on this FO.
  101. * @param propId The ID of the property to set.
  102. * @param value The value of the property.
  103. */
  104. public abstract void putExplicit(int propId, Property value);
  105. /**
  106. * Return the value of this property inherited by this FO.
  107. * Implements the inherited-property-value function.
  108. * The property must be inheritable!
  109. * @param propId The ID of the property whose value is desired.
  110. * @return The inherited value, otherwise null.
  111. * @throws PropertyException ...
  112. */
  113. public Property getInherited(int propId) throws PropertyException {
  114. if (isInherited(propId)) {
  115. return getFromParent(propId);
  116. } else {
  117. // return the "initial" value
  118. return makeProperty(propId);
  119. }
  120. }
  121. /**
  122. * Return the property on the current FlowObject. If it isn't set explicitly,
  123. * this will try to compute it based on other properties, or if it is
  124. * inheritable, to return the inherited value. If all else fails, it returns
  125. * the default value.
  126. * @param propId The Constants ID of the property whose value is desired.
  127. * @return the Property corresponding to that name
  128. * @throws PropertyException if there is a problem evaluating the property
  129. */
  130. public Property get(int propId) throws PropertyException {
  131. return get(propId, true, true);
  132. }
  133. /**
  134. * Return the property on the current FlowObject. Depending on the passed flags,
  135. * this will try to compute it based on other properties, or if it is
  136. * inheritable, to return the inherited value. If all else fails, it returns
  137. * the default value.
  138. * @param propId the property's id
  139. * @param bTryInherit true for inherited properties, or when the inherited
  140. * value is needed
  141. * @param bTryDefault true when the default value may be used as a last resort
  142. * @return the property
  143. * @throws PropertyException if there is a problem evaluating the property
  144. */
  145. public Property get(int propId, boolean bTryInherit,
  146. boolean bTryDefault) throws PropertyException {
  147. PropertyMaker propertyMaker = findMaker(propId & Constants.PROPERTY_MASK);
  148. if (propertyMaker != null) {
  149. return propertyMaker.get(propId & Constants.COMPOUND_MASK, this,
  150. bTryInherit, bTryDefault);
  151. }
  152. return null;
  153. }
  154. /**
  155. * Return the "nearest" specified value for the given property.
  156. * Implements the from-nearest-specified-value function.
  157. * @param propId The ID of the property whose value is desired.
  158. * @return The computed value if the property is explicitly set on some
  159. * ancestor of the current FO, else the initial value.
  160. * @throws PropertyException if there an error occurred when getting the property
  161. */
  162. public Property getNearestSpecified(int propId) throws PropertyException {
  163. Property p = null;
  164. PropertyList pList = parentPropertyList;
  165. while (pList != null) {
  166. p = pList.getExplicit(propId);
  167. if (p != null) {
  168. return p;
  169. } else {
  170. pList = pList.parentPropertyList;
  171. }
  172. }
  173. // If no explicit value found on any of the ancestor-nodes,
  174. // return initial (default) value.
  175. return makeProperty(propId);
  176. }
  177. /**
  178. * Return the value of this property on the parent of this FO.
  179. * Implements the from-parent function.
  180. * @param propId The Constants ID of the property whose value is desired.
  181. * @return The computed value on the parent or the initial value if this
  182. * FO is the root or is in a different namespace from its parent.
  183. * @throws PropertyException ...
  184. */
  185. public Property getFromParent(int propId) throws PropertyException {
  186. if (parentPropertyList != null) {
  187. return parentPropertyList.get(propId);
  188. } else {
  189. return makeProperty(propId);
  190. }
  191. }
  192. /**
  193. * Select a writing mode dependent property ID based on value of writing mode property.
  194. * @param lrtb the property ID to return under lrtb writingmode.
  195. * @param rltb the property ID to return under rltb writingmode.
  196. * @param tbrl the property ID to return under tbrl writingmode.
  197. * @param tblr the property ID to return under tblr writingmode.
  198. * @return one of the property IDs, depending on the writing mode.
  199. */
  200. public int selectFromWritingMode(int lrtb, int rltb, int tbrl, int tblr) {
  201. int propID;
  202. try {
  203. switch (get(Constants.PR_WRITING_MODE).getEnum()) {
  204. case Constants.EN_LR_TB:
  205. propID = lrtb;
  206. break;
  207. case Constants.EN_RL_TB:
  208. propID = rltb;
  209. break;
  210. case Constants.EN_TB_RL:
  211. propID = tbrl;
  212. break;
  213. case Constants.EN_TB_LR:
  214. propID = tblr;
  215. break;
  216. default:
  217. propID = -1;
  218. break;
  219. }
  220. } catch ( PropertyException e ) {
  221. propID = -1;
  222. }
  223. return propID;
  224. }
  225. private String addAttributeToList(Attributes attributes,
  226. String attributeName) throws ValidationException {
  227. String attributeValue = attributes.getValue(attributeName);
  228. if ( attributeValue != null ) {
  229. convertAttributeToProperty(attributes, attributeName, attributeValue);
  230. }
  231. return attributeValue;
  232. }
  233. /**
  234. * <p>Adds the attributes, passed in by the parser to the PropertyList.</p>
  235. * <p>Note that certain attributes are given priority in terms of order of
  236. * processing due to conversion dependencies, where the order is as follows:</p>
  237. * <ol>
  238. * <li>writing-mode</li>
  239. * <li>column-number</li>
  240. * <li>number-columns-spanned</li>
  241. * <li>font</li>
  242. * <li>font-size</li>
  243. * <li><emph>all others in order of appearance</emph></li>
  244. * </ol>
  245. *
  246. * @param attributes Collection of attributes passed to us from the parser.
  247. * @throws ValidationException if there is an attribute that does not
  248. * map to a property id (strict validation only)
  249. */
  250. public void addAttributesToList(Attributes attributes)
  251. throws ValidationException {
  252. /*
  253. * Give writing-mode highest conversion priority.
  254. */
  255. addAttributeToList(attributes, "writing-mode");
  256. /*
  257. * If column-number/number-columns-spanned are specified, then we
  258. * need them before all others (possible from-table-column() on any
  259. * other property further in the list...
  260. */
  261. addAttributeToList(attributes, "column-number");
  262. addAttributeToList(attributes, "number-columns-spanned");
  263. /*
  264. * If font-size is set on this FO, must set it first, since
  265. * other attributes specified in terms of "ems" depend on it.
  266. */
  267. String checkValue = addAttributeToList(attributes, "font");
  268. if (checkValue == null || "".equals(checkValue)) {
  269. /*
  270. * font shorthand wasn't specified, so still need to process
  271. * explicit font-size
  272. */
  273. addAttributeToList(attributes, "font-size");
  274. }
  275. String attributeNS;
  276. String attributeName;
  277. String attributeValue;
  278. FOUserAgent userAgent = getFObj().getUserAgent();
  279. for (int i = 0; i < attributes.getLength(); i++) {
  280. /* convert all attributes with the same namespace as the fo element
  281. * the "xml:lang" and "xml:base" properties are special cases */
  282. attributeNS = attributes.getURI(i);
  283. attributeName = attributes.getQName(i);
  284. attributeValue = attributes.getValue(i);
  285. if (attributeNS == null || attributeNS.length() == 0
  286. || "xml:lang".equals(attributeName)
  287. || "xml:base".equals(attributeName)) {
  288. convertAttributeToProperty(attributes, attributeName, attributeValue);
  289. } else if (!userAgent.isNamespaceIgnored(attributeNS)) {
  290. ElementMapping mapping = userAgent.getElementMappingRegistry().getElementMapping(
  291. attributeNS);
  292. QName attr = new QName(attributeNS, attributeName);
  293. if (mapping != null) {
  294. if (mapping.isAttributeProperty(attr)
  295. && mapping.getStandardPrefix() != null) {
  296. convertAttributeToProperty(attributes,
  297. mapping.getStandardPrefix() + ":" + attr.getLocalName(),
  298. attributeValue);
  299. } else {
  300. getFObj().addForeignAttribute(attr, attributeValue);
  301. }
  302. } else {
  303. handleInvalidProperty(attr);
  304. }
  305. }
  306. }
  307. }
  308. /**
  309. * Validates a property name.
  310. * @param propertyName the property name to check
  311. * @return true if the base property name and the subproperty name (if any)
  312. * can be correctly mapped to an id
  313. */
  314. protected boolean isValidPropertyName(String propertyName) {
  315. int propId = FOPropertyMapping.getPropertyId(
  316. findBasePropertyName(propertyName));
  317. int subpropId = FOPropertyMapping.getSubPropertyId(
  318. findSubPropertyName(propertyName));
  319. return !(propId == -1
  320. || (subpropId == -1
  321. && findSubPropertyName(propertyName) != null));
  322. }
  323. /**
  324. *
  325. * @param attributes Collection of attributes
  326. * @param attributeName Attribute name to convert
  327. * @param attributeValue Attribute value to assign to property
  328. * @throws ValidationException in case the property name is invalid
  329. * for the FO namespace
  330. */
  331. private void convertAttributeToProperty(Attributes attributes,
  332. String attributeName,
  333. String attributeValue)
  334. throws ValidationException {
  335. if (attributeName.startsWith("xmlns:")
  336. || "xmlns".equals(attributeName)) {
  337. /* Ignore namespace declarations if the XML parser/XSLT processor
  338. * reports them as 'regular' attributes */
  339. return;
  340. }
  341. if (attributeValue != null) {
  342. /* Handle "compound" properties, ex. space-before.minimum */
  343. String basePropertyName = findBasePropertyName(attributeName);
  344. String subPropertyName = findSubPropertyName(attributeName);
  345. int propId = FOPropertyMapping.getPropertyId(basePropertyName);
  346. int subpropId = FOPropertyMapping.getSubPropertyId(subPropertyName);
  347. if (propId == -1
  348. || (subpropId == -1 && subPropertyName != null)) {
  349. handleInvalidProperty(new QName(null, attributeName));
  350. }
  351. FObj parentFO = fobj.findNearestAncestorFObj();
  352. PropertyMaker propertyMaker = findMaker(propId);
  353. if (propertyMaker == null) {
  354. log.warn("No PropertyMaker registered for " + attributeName
  355. + ". Ignoring property.");
  356. return;
  357. }
  358. try {
  359. Property prop = null;
  360. if (subPropertyName == null) { // base attribute only found
  361. /* Do nothing if the base property has already been created.
  362. * This is e.g. the case when a compound attribute was
  363. * specified before the base attribute; in these cases
  364. * the base attribute was already created in
  365. * findBaseProperty()
  366. */
  367. if (getExplicit(propId) != null) {
  368. return;
  369. }
  370. prop = propertyMaker.make(this, attributeValue, parentFO);
  371. } else { // e.g. "leader-length.maximum"
  372. Property baseProperty
  373. = findBaseProperty(attributes, parentFO, propId,
  374. basePropertyName, propertyMaker);
  375. prop = propertyMaker.make(baseProperty, subpropId,
  376. this, attributeValue, parentFO);
  377. }
  378. if (prop != null) {
  379. putExplicit(propId, prop);
  380. }
  381. } catch (PropertyException e) {
  382. fobj.getFOValidationEventProducer().invalidPropertyValue(this, fobj.getName(),
  383. attributeName, attributeValue, e, fobj.locator);
  384. }
  385. }
  386. }
  387. private Property findBaseProperty(Attributes attributes,
  388. FObj parentFO,
  389. int propId,
  390. String basePropertyName,
  391. PropertyMaker propertyMaker)
  392. throws PropertyException {
  393. /* If the baseProperty has already been created, return it
  394. * e.g. <fo:leader xxxx="120pt" xxxx.maximum="200pt"... />
  395. */
  396. Property baseProperty = getExplicit(propId);
  397. if (baseProperty != null) {
  398. return baseProperty;
  399. }
  400. /* Otherwise If it is specified later in this list of Attributes, create it now
  401. * e.g. <fo:leader xxxx.maximum="200pt" xxxx="200pt"... />
  402. */
  403. String basePropertyValue = attributes.getValue(basePropertyName);
  404. if (basePropertyValue != null && propertyMaker != null) {
  405. baseProperty = propertyMaker.make(this, basePropertyValue,
  406. parentFO);
  407. return baseProperty;
  408. }
  409. return null; // could not find base property
  410. }
  411. /**
  412. * Handles an invalid property.
  413. * @param attr the invalid attribute
  414. * @throws ValidationException if an exception needs to be thrown depending on the
  415. * validation settings
  416. */
  417. protected void handleInvalidProperty(QName attr)
  418. throws ValidationException {
  419. if (!attr.getQName().startsWith("xmlns")) {
  420. fobj.getFOValidationEventProducer().invalidProperty(this, fobj.getName(),
  421. attr, true, fobj.locator);
  422. }
  423. }
  424. /**
  425. * Finds the first or base part (up to any period) of an attribute name.
  426. * For example, if input is "space-before.minimum", should return
  427. * "space-before".
  428. * @param attributeName String to be atomized
  429. * @return the base portion of the attribute
  430. */
  431. protected static String findBasePropertyName(String attributeName) {
  432. int separatorCharIndex = attributeName.indexOf('.');
  433. String basePropertyName = attributeName;
  434. if (separatorCharIndex > -1) {
  435. basePropertyName = attributeName.substring(0, separatorCharIndex);
  436. }
  437. return basePropertyName;
  438. }
  439. /**
  440. * Finds the second or sub part (portion past any period) of an attribute
  441. * name. For example, if input is "space-before.minimum", should return
  442. * "minimum".
  443. * @param attributeName String to be atomized
  444. * @return the sub portion of the attribute
  445. */
  446. protected static String findSubPropertyName(String attributeName) {
  447. int separatorCharIndex = attributeName.indexOf('.');
  448. String subpropertyName = null;
  449. if (separatorCharIndex > -1) {
  450. subpropertyName = attributeName.substring(separatorCharIndex + 1);
  451. }
  452. return subpropertyName;
  453. }
  454. /**
  455. * @param propId ID of property
  456. * @return new Property object
  457. * @throws PropertyException if there's a problem while processing the property
  458. */
  459. private Property getShorthand(int propId) throws PropertyException {
  460. PropertyMaker propertyMaker = findMaker(propId);
  461. if (propertyMaker != null) {
  462. return propertyMaker.getShorthand(this);
  463. } else {
  464. //log.error("no Maker for " + propertyName);
  465. return null;
  466. }
  467. }
  468. /**
  469. * @param propId ID of property
  470. * @return new Property object
  471. * @throws PropertyException if there's a problem while processing the property
  472. */
  473. private Property makeProperty(int propId) throws PropertyException {
  474. PropertyMaker propertyMaker = findMaker(propId);
  475. if (propertyMaker != null) {
  476. return propertyMaker.make(this);
  477. } else {
  478. //log.error("property " + propertyName
  479. // + " ignored");
  480. }
  481. return null;
  482. }
  483. /**
  484. * @param propId ID of property
  485. * @return isInherited value from the requested Property.Maker
  486. */
  487. private boolean isInherited(int propId) {
  488. if (inheritableProperty == null) {
  489. inheritableProperty = new boolean[Constants.PROPERTY_COUNT + 1];
  490. PropertyMaker maker = null;
  491. for (int prop = 1; prop <= Constants.PROPERTY_COUNT; prop++) {
  492. maker = findMaker(prop);
  493. inheritableProperty[prop] = (maker != null && maker.isInherited());
  494. }
  495. }
  496. return inheritableProperty[propId];
  497. }
  498. /**
  499. * @param propId Id of property
  500. * @return the Property.Maker for this property
  501. */
  502. private PropertyMaker findMaker(int propId) {
  503. if (propId < 1 || propId > Constants.PROPERTY_COUNT) {
  504. return null;
  505. } else {
  506. return FObj.getPropertyMakerFor(propId);
  507. }
  508. }
  509. /**
  510. * Constructs a BorderAndPadding object.
  511. * @return a BorderAndPadding object
  512. * @throws PropertyException if there's a problem while processing the properties
  513. */
  514. public CommonBorderPaddingBackground getBorderPaddingBackgroundProps()
  515. throws PropertyException {
  516. return CommonBorderPaddingBackground.getInstance(this);
  517. }
  518. /**
  519. * Constructs a CommonHyphenation object.
  520. * @return the CommonHyphenation object
  521. * @throws PropertyException if there's a problem while processing the properties
  522. */
  523. public CommonHyphenation getHyphenationProps() throws PropertyException {
  524. return CommonHyphenation.getInstance(this);
  525. }
  526. /**
  527. * Constructs a CommonMarginBlock object.
  528. * @return the CommonMarginBlock object
  529. * @throws PropertyException if there's a problem while processing the properties
  530. */
  531. public CommonMarginBlock getMarginBlockProps() throws PropertyException {
  532. return new CommonMarginBlock(this);
  533. }
  534. /**
  535. * Constructs a CommonMarginInline object.
  536. * @return the CommonMarginInline object
  537. * @throws PropertyException if there's a problem while processing the properties
  538. */
  539. public CommonMarginInline getMarginInlineProps() throws PropertyException {
  540. return new CommonMarginInline(this);
  541. }
  542. /**
  543. * Constructs a CommonAural object.
  544. * @return the CommonAural object
  545. * @throws PropertyException if there's a problem while processing the properties
  546. */
  547. public CommonAural getAuralProps() throws PropertyException {
  548. CommonAural props = new CommonAural(this);
  549. return props;
  550. }
  551. /**
  552. * Constructs a RelativePositionProps objects.
  553. * @return a RelativePositionProps object
  554. * @throws PropertyException if there's a problem while processing the properties
  555. */
  556. public CommonRelativePosition getRelativePositionProps() throws PropertyException {
  557. return new CommonRelativePosition(this);
  558. }
  559. /**
  560. * Constructs a CommonAbsolutePosition object.
  561. * @return the CommonAbsolutePosition object
  562. * @throws PropertyException if there's a problem while processing the properties
  563. */
  564. public CommonAbsolutePosition getAbsolutePositionProps() throws PropertyException {
  565. return new CommonAbsolutePosition(this);
  566. }
  567. /**
  568. * Constructs a CommonFont object.
  569. *
  570. * @return A CommonFont object
  571. * @throws PropertyException if there's a problem while processing the properties
  572. */
  573. public CommonFont getFontProps() throws PropertyException {
  574. return CommonFont.getInstance(this);
  575. }
  576. /**
  577. * Constructs a CommonTextDecoration object.
  578. * @return a CommonTextDecoration object
  579. * @throws PropertyException if there's a problem while processing the properties
  580. */
  581. public CommonTextDecoration getTextDecorationProps() throws PropertyException {
  582. return CommonTextDecoration.createFromPropertyList(this);
  583. }
  584. }