/*
* 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$ */
package org.apache.fop.fo;
// Java
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.FopFactory;
import org.apache.fop.fo.expr.PropertyException;
import org.apache.fop.fo.properties.CommonAbsolutePosition;
import org.apache.fop.fo.properties.CommonAccessibility;
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.
*/
public abstract class PropertyList {
// writing-mode index
private int writingMode;
private static boolean[] inheritableProperty;
/** reference to the parent FO's propertyList **/
protected PropertyList parentPropertyList = null;
private FObj fobj = null;
private static Log log = LogFactory.getLog(PropertyList.class);
/**
* 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;
}
/**
* @return the FObj object to which this propertyList is attached
*/
public FObj getFObj() {
return this.fobj;
}
/**
* @return the FObj object attached to the parentPropertyList
*/
public FObj getParentFObj() {
if (parentPropertyList != null) {
return parentPropertyList.getFObj();
} else {
return null;
}
}
/**
* @return the FObj object attached to the parentPropetyList
*/
public PropertyList getParentPropertyList() {
return parentPropertyList;
}
/**
* 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;
}
/**
* 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.
* @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!
* @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.
* @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.
* @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.
* @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.
* @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);
}
}
/**
* Set writing mode for this FO.
* Use that from the nearest ancestor, including self, which generates
* reference areas, or from root FO if no ancestor found.
* @throws PropertyException ...
*/
public void setWritingMode() throws PropertyException {
FObj p = fobj.findNearestAncestorFObj();
// If this is a reference area or the root, use the property value.
if (fobj.generatesReferenceAreas() || p == null) {
writingMode = get(Constants.PR_WRITING_MODE).getEnum();
} else {
// Otherwise get the writing mode value from the parent.
writingMode = getParentPropertyList().getWritingMode();
}
}
/**
* Return the "writing-mode" property value.
* @return the "writing-mode" property value.
*/
public int getWritingMode() {
return writingMode;
}
/**
* Uses the stored writingMode.
* @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.
* @return one of the property IDs, depending on the writing mode.
*/
public int getWritingMode(int lrtb, int rltb, int tbrl) {
switch (writingMode) {
case Constants.EN_LR_TB: return lrtb;
case Constants.EN_RL_TB: return rltb;
case Constants.EN_TB_RL: return tbrl;
default:
//nop
}
return -1;
}
/**
* Adds the attributes, passed in by the parser to the PropertyList
*
* @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 {
/*
* 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...
*/
String attributeName = "column-number";
String attributeValue = attributes.getValue(attributeName);
convertAttributeToProperty(attributes, attributeName,
attributeValue);
attributeName = "number-columns-spanned";
attributeValue = attributes.getValue(attributeName);
convertAttributeToProperty(attributes, attributeName,
attributeValue);
/*
* If font-size is set on this FO, must set it first, since
* other attributes specified in terms of "ems" depend on it.
*/
attributeName = "font";
attributeValue = attributes.getValue(attributeName);
convertAttributeToProperty(attributes, attributeName,
attributeValue);
if (attributeValue == null) {
/*
* font shorthand wasn't specified, so still need to process
* explicit font-size
*/
attributeName = "font-size";
attributeValue = attributes.getValue(attributeName);
convertAttributeToProperty(attributes, attributeName,
attributeValue);
}
String attributeNS;
FopFactory factory = getFObj().getUserAgent().getFactory();
for (int i = 0; i < attributes.getLength(); i++) {
/* convert all attributes with the same namespace as the fo element
* the "xml:lang" property is a special case */
attributeNS = attributes.getURI(i);
attributeName = attributes.getQName(i);
attributeValue = attributes.getValue(i);
if (attributeNS == null || attributeNS.length() == 0
|| "xml:lang".equals(attributeName)) {
convertAttributeToProperty(attributes, attributeName, attributeValue);
} else if (!factory.isNamespaceIgnored(attributeNS)) {
ElementMapping mapping = factory.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.
* @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));
}
/**
*
* @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 (attributeValue != null) {
if (attributeName.startsWith("xmlns:")
|| "xmlns".equals(attributeName)) {
//Ignore namespace declarations if the XML parser/XSLT processor
//reports them as 'regular' attributes
return;
}
/* 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.
*/
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.
*/
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.
* @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".
* @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".
* @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;
}
/**
* @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;
}
}
/**
* @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;
}
/**
* @param propId ID of property
* @return isInherited value from the requested Property.Maker
*/
private boolean isInherited(int propId) {
if (inheritableProperty == null) {
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());
}
}
return inheritableProperty[propId];
}
/**
* @param propId Id of property
* @return the Property.Maker for this property
*/
private PropertyMaker findMaker(int propId) {
if (propId < 1 || propId > Constants.PROPERTY_COUNT) {
return null;
} else {
return FObj.getPropertyMakerFor(propId);
}
}
/**
* 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.
* @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.
* @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.
* @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 CommonAccessibility object.
* @return the CommonAccessibility object
* @throws PropertyException if there's a problem while processing the properties
*/
public CommonAccessibility getAccessibilityProps() throws PropertyException {
return new CommonAccessibility(this);
}
/**
* 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.
* @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.
* @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.
*
* @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.
* @return a CommonTextDecoration object
* @throws PropertyException if there's a problem while processing the properties
*/
public CommonTextDecoration getTextDecorationProps() throws PropertyException {
return CommonTextDecoration.createFromPropertyList(this);
}
}