blob: b2b79eceb26d9b972debde553ca2bdbf12fc2e53 [file] [log] [blame]
/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* Licensed 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;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.apache.fop.apps.FOPException;
import org.apache.fop.datatypes.PercentBase;
import org.apache.fop.fo.flow.Marker;
import org.apache.fop.fo.properties.PropertyMaker;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
/**
* Base class for representation of formatting objects and their processing.
*/
public abstract class FObj extends FONode implements Constants {
public static PropertyMaker[] propertyListTable = null;
/** The immediate child nodes of this node. */
public List childNodes = null;
/** Used to indicate if this FO is either an Out Of Line FO (see rec)
or a descendant of one. Used during validateChildNode() FO
validation.
*/
private boolean isOutOfLineFODescendant = false;
/** Markers added to this element. */
protected Map markers = null;
/** Dynamic layout dimension. Used to resolve relative lengths. */
protected Map layoutDimension = null;
/**
* Create a new formatting object.
* All formatting object classes extend this class.
*
* @param parent the parent node
* @todo move propertyListTable initialization someplace else?
*/
public FObj(FONode parent) {
super(parent);
// determine if isOutOfLineFODescendant should be set
if (parent != null && parent instanceof FObj) {
if (((FObj)parent).getIsOutOfLineFODescendant() == true) {
isOutOfLineFODescendant = true;
} else {
int foID = getNameId();
if (foID == FO_FLOAT || foID == FO_FOOTNOTE
|| foID == FO_FOOTNOTE_BODY) {
isOutOfLineFODescendant = true;
}
}
}
if (propertyListTable == null) {
propertyListTable = new PropertyMaker[Constants.PROPERTY_COUNT + 1];
PropertyMaker[] list = FOPropertyMapping.getGenericMappings();
for (int i = 1; i < list.length; i++) {
if (list[i] != null) {
propertyListTable[i] = list[i];
}
}
}
}
/**
* @see org.apache.fop.fo.FONode#clone(FONode, boolean)
*/
public FONode clone(FONode parent, boolean removeChildren)
throws FOPException {
FObj fobj = (FObj) super.clone(parent, removeChildren);
if (removeChildren) {
fobj.childNodes = null;
}
return fobj;
}
/**
* @see org.apache.fop.fo.FONode#processNode
*/
public void processNode(String elementName, Locator locator,
Attributes attlist, PropertyList pList) throws FOPException {
setLocator(locator);
pList.addAttributesToList(attlist);
pList.setWritingMode();
bind(pList);
}
/**
* Create a default property list for this element.
*/
protected PropertyList createPropertyList(PropertyList parent,
FOEventHandler foEventHandler) throws FOPException {
return foEventHandler.getPropertyListMaker().make(this, parent);
}
/**
* Bind property values from the property list to the FO node.
* Must be overridden in all FObj subclasses that have properties
* applying to it.
* @param pList the PropertyList where the properties can be found.
* @throws FOPException
*/
public void bind(PropertyList pList) throws FOPException {
}
/**
* Setup the id for this formatting object.
* Most formatting objects can have an id that can be referenced.
* This methods checks that the id isn't already used by another
* fo and sets the id attribute of this object.
*/
protected void checkId(String id) throws ValidationException {
if (!id.equals("")) {
Set idrefs = getFOEventHandler().getIDReferences();
if (!idrefs.contains(id)) {
idrefs.add(id);
} else {
throw new ValidationException("Property id \"" + id +
"\" previously used; id values must be unique" +
" in document.", locator);
}
}
}
/**
* Returns Out Of Line FO Descendant indicator.
* @return true if Out of Line FO or Out Of Line descendant, false otherwise
*/
public boolean getIsOutOfLineFODescendant() {
return isOutOfLineFODescendant;
}
/**
* @see org.apache.fop.fo.FONode#addChildNode(FONode)
*/
protected void addChildNode(FONode child) throws FOPException {
if (PropertySets.canHaveMarkers(getNameId()) &&
child.getNameId() == FO_MARKER) {
addMarker((Marker) child);
} else {
if (childNodes == null) {
childNodes = new java.util.ArrayList();
}
childNodes.add(child);
}
}
/** @see org.apache.fop.fo.FONode#removeChild(org.apache.fop.fo.FONode) */
public void removeChild(FONode child) {
if (childNodes != null) {
childNodes.remove(child);
}
}
/**
* Find the nearest parent, grandparent, etc. FONode that is also an FObj
* @return FObj the nearest ancestor FONode that is an FObj
*/
public FObj findNearestAncestorFObj() {
FONode par = parent;
while (par != null && !(par instanceof FObj)) {
par = par.parent;
}
return (FObj) par;
}
/* This section is the implemenation of the property context. */
/**
* Assign the size of a layout dimension to the key.
* @param key the Layout dimension, from PercentBase.
* @param dimension The layout length.
*/
public void setLayoutDimension(PercentBase.LayoutDimension key, int dimension) {
if (layoutDimension == null) {
layoutDimension = new java.util.HashMap();
}
layoutDimension.put(key, new Integer(dimension));
}
/**
* Assign the size of a layout dimension to the key.
* @param key the Layout dimension, from PercentBase.
* @param dimension The layout length.
*/
public void setLayoutDimension(PercentBase.LayoutDimension key, float dimension) {
if (layoutDimension == null) {
layoutDimension = new java.util.HashMap();
}
layoutDimension.put(key, new Float(dimension));
}
/**
* Return the size associated with the key.
* @param key The layout dimension key.
* @return the length.
*/
public Number getLayoutDimension(PercentBase.LayoutDimension key) {
if (layoutDimension != null) {
Number result = (Number) layoutDimension.get(key);
if (result != null) {
return result;
}
}
if (parent != null) {
return ((FObj) parent).getLayoutDimension(key);
}
return new Integer(0);
}
/**
* Check if this formatting object generates reference areas.
* @return true if generates reference areas
* @todo see if needed
*/
public boolean generatesReferenceAreas() {
return false;
}
/**
* @see org.apache.fop.fo.FONode#getChildNodes()
*/
public ListIterator getChildNodes() {
if (childNodes != null) {
return childNodes.listIterator();
}
return null;
}
/**
* Return an iterator over the object's childNodes starting
* at the passed-in node.
* @param childNode First node in the iterator
* @return A ListIterator or null if childNode isn't a child of
* this FObj.
*/
public ListIterator getChildNodes(FONode childNode) {
if (childNodes != null) {
int i = childNodes.indexOf(childNode);
if (i >= 0) {
return childNodes.listIterator(i);
}
}
return null;
}
/**
* Notifies a FObj that one of it's children is removed.
* This method is subclassed by Block to clear the firstInlineChild variable.
* @param node the node that was removed
*/
protected void notifyChildRemoval(FONode node) {
//nop
}
/**
* Add the marker to this formatting object.
* If this object can contain markers it checks that the marker
* has a unique class-name for this object and that it is
* the first child.
* @param marker Marker to add.
*/
protected void addMarker(Marker marker) {
String mcname = marker.getMarkerClassName();
if (childNodes != null) {
// check for empty childNodes
for (Iterator iter = childNodes.iterator(); iter.hasNext();) {
FONode node = (FONode)iter.next();
if (node instanceof FOText) {
FOText text = (FOText)node;
if (text.willCreateArea()) {
getLogger().error("fo:marker must be an initial child: " + mcname);
return;
} else {
iter.remove();
notifyChildRemoval(node);
}
} else {
getLogger().error("fo:marker must be an initial child: " + mcname);
return;
}
}
}
if (markers == null) {
markers = new java.util.HashMap();
}
if (!markers.containsKey(mcname)) {
markers.put(mcname, marker);
} else {
getLogger().error("fo:marker 'marker-class-name' "
+ "must be unique for same parent: " + mcname);
}
}
/**
* @return true if there are any Markers attached to this object
*/
public boolean hasMarkers() {
return markers != null && !markers.isEmpty();
}
/**
* @return th collection of Markers attached to this object
*/
public Map getMarkers() {
return markers;
}
/*
* Return a string representation of the fo element.
* Deactivated in order to see precise ID of each fo element created
* (helpful for debugging)
*/
/* public String toString() {
return getName() + " at line " + line + ":" + column;
}
*/
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the "%block;" parameter entity
* as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isBlockItem(String nsURI, String lName) {
return (nsURI == FO_URI &&
(lName.equals("block")
|| lName.equals("table")
|| lName.equals("table-and-caption")
|| lName.equals("block-container")
|| lName.equals("list-block")
|| lName.equals("float")
|| isNeutralItem(nsURI, lName)));
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the "%inline;" parameter entity
* as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isInlineItem(String nsURI, String lName) {
return (nsURI == FO_URI &&
(lName.equals("bidi-override")
|| lName.equals("character")
|| lName.equals("external-graphic")
|| lName.equals("instream-foreign-object")
|| lName.equals("inline")
|| lName.equals("inline-container")
|| lName.equals("leader")
|| lName.equals("page-number")
|| lName.equals("page-number-citation")
|| lName.equals("basic-link")
|| (lName.equals("multi-toggle")
&& (getNameId() == FO_MULTI_CASE || findAncestor(FO_MULTI_CASE) > 0))
|| (lName.equals("footnote") && !isOutOfLineFODescendant)
|| isNeutralItem(nsURI, lName)));
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the "%block;" parameter entity
* or "%inline;" parameter entity
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isBlockOrInlineItem(String nsURI, String lName) {
return (isBlockItem(nsURI, lName) || isInlineItem(nsURI, lName));
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the neutral item list
* as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isNeutralItem(String nsURI, String lName) {
return (nsURI == FO_URI &&
(lName.equals("multi-switch")
|| lName.equals("multi-properties")
|| lName.equals("wrapper")
|| (!isOutOfLineFODescendant && lName.equals("float"))
|| lName.equals("retrieve-marker")));
}
/**
* Convenience method for validity checking. Checks if the
* current node has an ancestor of a given name.
* @param ancestorID -- Constants ID of node name to check for (e.g., FO_ROOT)
* @return number of levels above FO where ancestor exists,
* -1 if not found
*/
protected int findAncestor(int ancestorID) {
int found = 1;
FONode temp = getParent();
while (temp != null) {
if (temp.getNameId() == ancestorID) {
return found;
}
found += 1;
temp = temp.getParent();
}
return -1;
}
}