/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: PropertyList.java 1805173 2017-08-16 10:50:04Z ssteiner $ */

package org.apache.fop.fo;

// Java
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.xml.sax.Attributes;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.util.QName;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fo.expr.PropertyException;
import org.apache.fop.fo.properties.CommonAbsolutePosition;
import org.apache.fop.fo.properties.CommonAural;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.CommonFont;
import org.apache.fop.fo.properties.CommonHyphenation;
import org.apache.fop.fo.properties.CommonMarginBlock;
import org.apache.fop.fo.properties.CommonMarginInline;
import org.apache.fop.fo.properties.CommonRelativePosition;
import org.apache.fop.fo.properties.CommonTextDecoration;
import org.apache.fop.fo.properties.Property;
import org.apache.fop.fo.properties.PropertyMaker;

Class containing the collection of properties for a given FObj.
/** * Class containing the collection of properties for a given FObj. */
public abstract class PropertyList { private static boolean[] inheritableProperty; static { inheritableProperty = new boolean[Constants.PROPERTY_COUNT + 1]; PropertyMaker maker = null; for (int prop = 1; prop <= Constants.PROPERTY_COUNT; prop++) { maker = findMaker(prop); inheritableProperty[prop] = (maker != null && maker.isInherited()); } }
reference to the parent FO's propertyList
/** reference to the parent FO's propertyList **/
protected PropertyList parentPropertyList; private FObj fobj; private static Log log = LogFactory.getLog(PropertyList.class); private final UnknownPropertyHandler unknownPropertyHandler = new UnknownPropertyHandler();
Basic constructor.
Params:
  • fObjToAttach – the FO this PropertyList should be attached to
  • parentPropertyList – the PropertyList belonging to the new objects parent
/** * Basic constructor. * @param fObjToAttach the FO this PropertyList should be attached to * @param parentPropertyList the PropertyList belonging to the new objects * parent */
public PropertyList(FObj fObjToAttach, PropertyList parentPropertyList) { this.fobj = fObjToAttach; this.parentPropertyList = parentPropertyList; }
Returns:the FObj object to which this propertyList is attached
/** * @return the FObj object to which this propertyList is attached */
public FObj getFObj() { return this.fobj; }
Returns:the FObj object attached to the parentPropertyList
/** * @return the FObj object attached to the parentPropertyList */
public FObj getParentFObj() { if (parentPropertyList != null) { return parentPropertyList.getFObj(); } else { return null; } }
Adds an unknown property value to the property list so that if necessary, a warning can be displayed.
Params:
  • propertyValue – The unknown property value
  • output – The output of the property to validate
  • property – The original property containing the full value
/** * Adds an unknown property value to the property list so that if * necessary, a warning can be displayed. * @param propertyValue The unknown property value * @param output The output of the property to validate * @param property The original property containing the full value */
public void validatePropertyValue(String propertyValue, Property output, Property property) { unknownPropertyHandler.validatePropertyValue(propertyValue, output, property); }
Gets the current list of unknown property values
Returns:The set containing the list of unknown property values
/** * Gets the current list of unknown property values * @return The set containing the list of unknown property values */
public Map<String, Property> getUnknownPropertyValues() { return unknownPropertyHandler.getUnknownPropertyValues(); }
Returns:the FObj object attached to the parentPropetyList
/** * @return the FObj object attached to the parentPropetyList */
public PropertyList getParentPropertyList() { return parentPropertyList; }
Return the value explicitly specified on this FO.
Params:
  • propId – The id of the property whose value is desired.
Throws:
Returns:The value if the property is explicitly set or set by a shorthand property, otherwise null.
/** * Return the value explicitly specified on this FO. * @param propId The id of the property whose value is desired. * @return The value if the property is explicitly set or set by * a shorthand property, otherwise null. * @throws PropertyException ... */
public Property getExplicitOrShorthand(int propId) throws PropertyException { /* Handle request for one part of a compound property */ Property p = getExplicit(propId); if (p == null) { p = getShorthand(propId); } return p; }
A class to handle unknown shorthand property values e.g. border="solit 1pt"
/** * A class to handle unknown shorthand property values e.g. border="solit 1pt" */
private static class UnknownPropertyHandler {
A list of unknown properties identified by the value and property in which its featured
/** * A list of unknown properties identified by the value and property in which its featured */
private Map<String, Property> unknownPropertyValues = new HashMap<String, Property>();
A list of known properties which have already been processed
/** * A list of known properties which have already been processed */
private Set<Property> knownProperties = new HashSet<Property>(); void validatePropertyValue(String propertyValue, Property output, Property property) { if (!knownProperties.contains(property) && output == null) { if (propertyValue != null) { unknownPropertyValues.put(propertyValue, property); } } else { knownProperties.add(property); } } Map<String, Property> getUnknownPropertyValues() { return unknownPropertyValues; } }
Return the value explicitly specified on this FO.
Params:
  • propId – The ID of the property whose value is desired.
Returns:The value if the property is explicitly set, otherwise null.
/** * Return the value explicitly specified on this FO. * @param propId The ID of the property whose value is desired. * @return The value if the property is explicitly set, otherwise null. */
public abstract Property getExplicit(int propId);
Set an value defined explicitly on this FO.
Params:
  • propId – The ID of the property to set.
  • value – The value of the property.
/** * Set an value defined explicitly on this FO. * @param propId The ID of the property to set. * @param value The value of the property. */
public abstract void putExplicit(int propId, Property value);
Return the value of this property inherited by this FO. Implements the inherited-property-value function. The property must be inheritable!
Params:
  • propId – The ID of the property whose value is desired.
Throws:
Returns:The inherited value, otherwise null.
/** * Return the value of this property inherited by this FO. * Implements the inherited-property-value function. * The property must be inheritable! * @param propId The ID of the property whose value is desired. * @return The inherited value, otherwise null. * @throws PropertyException ... */
public Property getInherited(int propId) throws PropertyException { if (isInherited(propId)) { return getFromParent(propId); } else { // return the "initial" value return makeProperty(propId); } }
Return the property on the current FlowObject. If it isn't set explicitly, this will try to compute it based on other properties, or if it is inheritable, to return the inherited value. If all else fails, it returns the default value.
Params:
  • propId – The Constants ID of the property whose value is desired.
Throws:
Returns:the Property corresponding to that name
/** * Return the property on the current FlowObject. If it isn't set explicitly, * this will try to compute it based on other properties, or if it is * inheritable, to return the inherited value. If all else fails, it returns * the default value. * @param propId The Constants ID of the property whose value is desired. * @return the Property corresponding to that name * @throws PropertyException if there is a problem evaluating the property */
public Property get(int propId) throws PropertyException { return get(propId, true, true); }
Return the property on the current FlowObject. Depending on the passed flags, this will try to compute it based on other properties, or if it is inheritable, to return the inherited value. If all else fails, it returns the default value.
Params:
  • propId – the property's id
  • bTryInherit – true for inherited properties, or when the inherited value is needed
  • bTryDefault – true when the default value may be used as a last resort
Throws:
Returns:the property
/** * Return the property on the current FlowObject. Depending on the passed flags, * this will try to compute it based on other properties, or if it is * inheritable, to return the inherited value. If all else fails, it returns * the default value. * @param propId the property's id * @param bTryInherit true for inherited properties, or when the inherited * value is needed * @param bTryDefault true when the default value may be used as a last resort * @return the property * @throws PropertyException if there is a problem evaluating the property */
public Property get(int propId, boolean bTryInherit, boolean bTryDefault) throws PropertyException { PropertyMaker propertyMaker = findMaker(propId & Constants.PROPERTY_MASK); if (propertyMaker != null) { return propertyMaker.get(propId & Constants.COMPOUND_MASK, this, bTryInherit, bTryDefault); } return null; }
Return the "nearest" specified value for the given property. Implements the from-nearest-specified-value function.
Params:
  • propId – The ID of the property whose value is desired.
Throws:
Returns:The computed value if the property is explicitly set on some ancestor of the current FO, else the initial value.
/** * Return the "nearest" specified value for the given property. * Implements the from-nearest-specified-value function. * @param propId The ID of the property whose value is desired. * @return The computed value if the property is explicitly set on some * ancestor of the current FO, else the initial value. * @throws PropertyException if there an error occurred when getting the property */
public Property getNearestSpecified(int propId) throws PropertyException { Property p = null; PropertyList pList = parentPropertyList; while (pList != null) { p = pList.getExplicit(propId); if (p != null) { return p; } else { pList = pList.parentPropertyList; } } // If no explicit value found on any of the ancestor-nodes, // return initial (default) value. return makeProperty(propId); }
Return the value of this property on the parent of this FO. Implements the from-parent function.
Params:
  • propId – The Constants ID of the property whose value is desired.
Throws:
Returns:The computed value on the parent or the initial value if this FO is the root or is in a different namespace from its parent.
/** * Return the value of this property on the parent of this FO. * Implements the from-parent function. * @param propId The Constants ID of the property whose value is desired. * @return The computed value on the parent or the initial value if this * FO is the root or is in a different namespace from its parent. * @throws PropertyException ... */
public Property getFromParent(int propId) throws PropertyException { if (parentPropertyList != null) { return parentPropertyList.get(propId); } else { return makeProperty(propId); } }
Select a writing mode dependent property ID based on value of writing mode property.
Params:
  • lrtb – the property ID to return under lrtb writingmode.
  • rltb – the property ID to return under rltb writingmode.
  • tbrl – the property ID to return under tbrl writingmode.
  • tblr – the property ID to return under tblr writingmode.
Returns:one of the property IDs, depending on the writing mode.
/** * Select a writing mode dependent property ID based on value of writing mode property. * @param lrtb the property ID to return under lrtb writingmode. * @param rltb the property ID to return under rltb writingmode. * @param tbrl the property ID to return under tbrl writingmode. * @param tblr the property ID to return under tblr writingmode. * @return one of the property IDs, depending on the writing mode. */
public int selectFromWritingMode(int lrtb, int rltb, int tbrl, int tblr) { int propID; try { switch (get(Constants.PR_WRITING_MODE).getEnum()) { case Constants.EN_LR_TB: propID = lrtb; break; case Constants.EN_RL_TB: propID = rltb; break; case Constants.EN_TB_RL: propID = tbrl; break; case Constants.EN_TB_LR: propID = tblr; break; default: propID = -1; break; } } catch (PropertyException e) { propID = -1; } return propID; } private String addAttributeToList(Attributes attributes, String attributeName) throws ValidationException { String attributeValue = attributes.getValue(attributeName); if (attributeValue != null) { convertAttributeToProperty(attributes, attributeName, attributeValue); } return attributeValue; }

Adds the attributes, passed in by the parser to the PropertyList.

Note that certain attributes are given priority in terms of order of processing due to conversion dependencies, where the order is as follows:

  1. writing-mode
  2. column-number
  3. number-columns-spanned
  4. font
  5. font-size
  6. all others in order of appearance
Params:
  • attributes – Collection of attributes passed to us from the parser.
Throws:
  • ValidationException – if there is an attribute that does not map to a property id (strict validation only)
/** * <p>Adds the attributes, passed in by the parser to the PropertyList.</p> * <p>Note that certain attributes are given priority in terms of order of * processing due to conversion dependencies, where the order is as follows:</p> * <ol> * <li>writing-mode</li> * <li>column-number</li> * <li>number-columns-spanned</li> * <li>font</li> * <li>font-size</li> * <li>all others in order of appearance</li> * </ol> * * @param attributes Collection of attributes passed to us from the parser. * @throws ValidationException if there is an attribute that does not * map to a property id (strict validation only) */
public void addAttributesToList(Attributes attributes) throws ValidationException { /* * Give writing-mode highest conversion priority. */ addAttributeToList(attributes, "writing-mode"); /* * If column-number/number-columns-spanned are specified, then we * need them before all others (possible from-table-column() on any * other property further in the list... */ addAttributeToList(attributes, "column-number"); addAttributeToList(attributes, "number-columns-spanned"); /* * If font-size is set on this FO, must set it first, since * other attributes specified in terms of "ems" depend on it. */ String checkValue = addAttributeToList(attributes, "font"); if (checkValue == null || "".equals(checkValue)) { /* * font shorthand wasn't specified, so still need to process * explicit font-size */ addAttributeToList(attributes, "font-size"); } String attributeNS; String attributeName; String attributeValue; FOUserAgent userAgent = getFObj().getUserAgent(); for (int i = 0; i < attributes.getLength(); i++) { /* convert all attributes with the same namespace as the fo element * the "xml:lang" and "xml:base" properties are special cases */ attributeNS = attributes.getURI(i); attributeName = attributes.getQName(i); attributeValue = attributes.getValue(i); if (attributeNS == null || attributeNS.length() == 0 || "xml:lang".equals(attributeName) || "xml:base".equals(attributeName)) { convertAttributeToProperty(attributes, attributeName, attributeValue); } else if (!userAgent.isNamespaceIgnored(attributeNS)) { ElementMapping mapping = userAgent.getElementMappingRegistry().getElementMapping( attributeNS); QName attr = new QName(attributeNS, attributeName); if (mapping != null) { if (mapping.isAttributeProperty(attr) && mapping.getStandardPrefix() != null) { convertAttributeToProperty(attributes, mapping.getStandardPrefix() + ":" + attr.getLocalName(), attributeValue); } else { getFObj().addForeignAttribute(attr, attributeValue); } } else { handleInvalidProperty(attr); } } } }
Validates a property name.
Params:
  • propertyName – the property name to check
Returns:true if the base property name and the subproperty name (if any) can be correctly mapped to an id
/** * Validates a property name. * @param propertyName the property name to check * @return true if the base property name and the subproperty name (if any) * can be correctly mapped to an id */
protected boolean isValidPropertyName(String propertyName) { int propId = FOPropertyMapping.getPropertyId( findBasePropertyName(propertyName)); int subpropId = FOPropertyMapping.getSubPropertyId( findSubPropertyName(propertyName)); return !(propId == -1 || (subpropId == -1 && findSubPropertyName(propertyName) != null)); } public Property getPropertyForAttribute(Attributes attributes, String attributeName, String attributeValue) throws FOPException { if (attributeValue != null) { if (attributeName.startsWith("xmlns:") || "xmlns".equals(attributeName)) { return null; } String basePropertyName = findBasePropertyName(attributeName); String subPropertyName = findSubPropertyName(attributeName); int propId = FOPropertyMapping.getPropertyId(basePropertyName); int subpropId = FOPropertyMapping.getSubPropertyId(subPropertyName); if (propId == -1 || (subpropId == -1 && subPropertyName != null)) { return null; } return getExplicit(propId); } return null; }
Params:
  • attributes – Collection of attributes
  • attributeName – Attribute name to convert
  • attributeValue – Attribute value to assign to property
Throws:
/** * * @param attributes Collection of attributes * @param attributeName Attribute name to convert * @param attributeValue Attribute value to assign to property * @throws ValidationException in case the property name is invalid * for the FO namespace */
private void convertAttributeToProperty(Attributes attributes, String attributeName, String attributeValue) throws ValidationException { if (attributeName.startsWith("xmlns:") || "xmlns".equals(attributeName)) { /* Ignore namespace declarations if the XML parser/XSLT processor * reports them as 'regular' attributes */ return; } if (attributeValue != null) { /* Handle "compound" properties, ex. space-before.minimum */ String basePropertyName = findBasePropertyName(attributeName); String subPropertyName = findSubPropertyName(attributeName); int propId = FOPropertyMapping.getPropertyId(basePropertyName); int subpropId = FOPropertyMapping.getSubPropertyId(subPropertyName); if (propId == -1 || (subpropId == -1 && subPropertyName != null)) { handleInvalidProperty(new QName(null, attributeName)); } FObj parentFO = fobj.findNearestAncestorFObj(); PropertyMaker propertyMaker = findMaker(propId); if (propertyMaker == null) { log.warn("No PropertyMaker registered for " + attributeName + ". Ignoring property."); return; } try { Property prop = null; if (subPropertyName == null) { // base attribute only found /* Do nothing if the base property has already been created. * This is e.g. the case when a compound attribute was * specified before the base attribute; in these cases * the base attribute was already created in * findBaseProperty() */ if (getExplicit(propId) != null) { return; } prop = propertyMaker.make(this, attributeValue, parentFO); } else { // e.g. "leader-length.maximum" Property baseProperty = findBaseProperty(attributes, parentFO, propId, basePropertyName, propertyMaker); prop = propertyMaker.make(baseProperty, subpropId, this, attributeValue, parentFO); } if (prop != null) { putExplicit(propId, prop); } } catch (PropertyException e) { fobj.getFOValidationEventProducer().invalidPropertyValue(this, fobj.getName(), attributeName, attributeValue, e, fobj.locator); } } } private Property findBaseProperty(Attributes attributes, FObj parentFO, int propId, String basePropertyName, PropertyMaker propertyMaker) throws PropertyException { /* If the baseProperty has already been created, return it * e.g. <fo:leader xxxx="120pt" xxxx.maximum="200pt"... /> */ Property baseProperty = getExplicit(propId); if (baseProperty != null) { return baseProperty; } /* Otherwise If it is specified later in this list of Attributes, create it now * e.g. <fo:leader xxxx.maximum="200pt" xxxx="200pt"... /> */ String basePropertyValue = attributes.getValue(basePropertyName); if (basePropertyValue != null && propertyMaker != null) { baseProperty = propertyMaker.make(this, basePropertyValue, parentFO); return baseProperty; } return null; // could not find base property }
Handles an invalid property.
Params:
  • attr – the invalid attribute
Throws:
  • ValidationException – if an exception needs to be thrown depending on the validation settings
/** * Handles an invalid property. * @param attr the invalid attribute * @throws ValidationException if an exception needs to be thrown depending on the * validation settings */
protected void handleInvalidProperty(QName attr) throws ValidationException { if (!attr.getQName().startsWith("xmlns")) { fobj.getFOValidationEventProducer().invalidProperty(this, fobj.getName(), attr, true, fobj.locator); } }
Finds the first or base part (up to any period) of an attribute name. For example, if input is "space-before.minimum", should return "space-before".
Params:
  • attributeName – String to be atomized
Returns:the base portion of the attribute
/** * Finds the first or base part (up to any period) of an attribute name. * For example, if input is "space-before.minimum", should return * "space-before". * @param attributeName String to be atomized * @return the base portion of the attribute */
protected static String findBasePropertyName(String attributeName) { int separatorCharIndex = attributeName.indexOf('.'); String basePropertyName = attributeName; if (separatorCharIndex > -1) { basePropertyName = attributeName.substring(0, separatorCharIndex); } return basePropertyName; }
Finds the second or sub part (portion past any period) of an attribute name. For example, if input is "space-before.minimum", should return "minimum".
Params:
  • attributeName – String to be atomized
Returns:the sub portion of the attribute
/** * Finds the second or sub part (portion past any period) of an attribute * name. For example, if input is "space-before.minimum", should return * "minimum". * @param attributeName String to be atomized * @return the sub portion of the attribute */
protected static String findSubPropertyName(String attributeName) { int separatorCharIndex = attributeName.indexOf('.'); String subpropertyName = null; if (separatorCharIndex > -1) { subpropertyName = attributeName.substring(separatorCharIndex + 1); } return subpropertyName; }
Params:
  • propId – ID of property
Throws:
Returns:new Property object
/** * @param propId ID of property * @return new Property object * @throws PropertyException if there's a problem while processing the property */
private Property getShorthand(int propId) throws PropertyException { PropertyMaker propertyMaker = findMaker(propId); if (propertyMaker != null) { return propertyMaker.getShorthand(this); } else { //log.error("no Maker for " + propertyName); return null; } }
Params:
  • propId – ID of property
Throws:
Returns:new Property object
/** * @param propId ID of property * @return new Property object * @throws PropertyException if there's a problem while processing the property */
private Property makeProperty(int propId) throws PropertyException { PropertyMaker propertyMaker = findMaker(propId); if (propertyMaker != null) { return propertyMaker.make(this); } else { //log.error("property " + propertyName // + " ignored"); } return null; }
Params:
  • propId – ID of property
Returns:isInherited value from the requested Property.Maker
/** * @param propId ID of property * @return isInherited value from the requested Property.Maker */
private boolean isInherited(int propId) { return inheritableProperty[propId]; }
Params:
  • propId – Id of property
Returns:the Property.Maker for this property
/** * @param propId Id of property * @return the Property.Maker for this property */
private static PropertyMaker findMaker(int propId) { if (propId < 1 || propId > Constants.PROPERTY_COUNT) { return null; } else { return FObj.getPropertyMakerFor(propId); } }
Constructs a BorderAndPadding object.
Throws:
Returns:a BorderAndPadding object
/** * Constructs a BorderAndPadding object. * @return a BorderAndPadding object * @throws PropertyException if there's a problem while processing the properties */
public CommonBorderPaddingBackground getBorderPaddingBackgroundProps() throws PropertyException { return CommonBorderPaddingBackground.getInstance(this); }
Constructs a CommonHyphenation object.
Throws:
Returns:the CommonHyphenation object
/** * Constructs a CommonHyphenation object. * @return the CommonHyphenation object * @throws PropertyException if there's a problem while processing the properties */
public CommonHyphenation getHyphenationProps() throws PropertyException { return CommonHyphenation.getInstance(this); }
Constructs a CommonMarginBlock object.
Throws:
Returns:the CommonMarginBlock object
/** * Constructs a CommonMarginBlock object. * @return the CommonMarginBlock object * @throws PropertyException if there's a problem while processing the properties */
public CommonMarginBlock getMarginBlockProps() throws PropertyException { return new CommonMarginBlock(this); }
Constructs a CommonMarginInline object.
Throws:
Returns:the CommonMarginInline object
/** * Constructs a CommonMarginInline object. * @return the CommonMarginInline object * @throws PropertyException if there's a problem while processing the properties */
public CommonMarginInline getMarginInlineProps() throws PropertyException { return new CommonMarginInline(this); }
Constructs a CommonAural object.
Throws:
Returns:the CommonAural object
/** * Constructs a CommonAural object. * @return the CommonAural object * @throws PropertyException if there's a problem while processing the properties */
public CommonAural getAuralProps() throws PropertyException { CommonAural props = new CommonAural(this); return props; }
Constructs a RelativePositionProps objects.
Throws:
Returns:a RelativePositionProps object
/** * Constructs a RelativePositionProps objects. * @return a RelativePositionProps object * @throws PropertyException if there's a problem while processing the properties */
public CommonRelativePosition getRelativePositionProps() throws PropertyException { return new CommonRelativePosition(this); }
Constructs a CommonAbsolutePosition object.
Throws:
Returns:the CommonAbsolutePosition object
/** * Constructs a CommonAbsolutePosition object. * @return the CommonAbsolutePosition object * @throws PropertyException if there's a problem while processing the properties */
public CommonAbsolutePosition getAbsolutePositionProps() throws PropertyException { return new CommonAbsolutePosition(this); }
Constructs a CommonFont object.
Throws:
Returns:A CommonFont object
/** * Constructs a CommonFont object. * * @return A CommonFont object * @throws PropertyException if there's a problem while processing the properties */
public CommonFont getFontProps() throws PropertyException { return CommonFont.getInstance(this); }
Constructs a CommonTextDecoration object.
Throws:
Returns:a CommonTextDecoration object
/** * Constructs a CommonTextDecoration object. * @return a CommonTextDecoration object * @throws PropertyException if there's a problem while processing the properties */
public CommonTextDecoration getTextDecorationProps() throws PropertyException { return CommonTextDecoration.createFromPropertyList(this); } }