/*
* 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: FObj.java 442282 2006-09-11 18:24:35Z jeremias $ */
package org.apache.fop.fo;
import java.util.Collections;
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.fo.extensions.ExtensionAttachment;
import org.apache.fop.fo.flow.Marker;
import org.apache.fop.fo.properties.PropertyMaker;
import org.apache.fop.util.QName;
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 {
/** the list of property makers */
private static PropertyMaker[] propertyListTable
= FOPropertyMapping.getGenericMappings();
/** The immediate child nodes of this node. */
protected List childNodes = null;
/** The list of extension attachments, null if none */
private List extensionAttachments = null;
/** The map of foreign attributes, null if none */
private Map foreignAttributes = null;
/** Used to indicate if this FO is either an Out Of Line FO (see rec)
* or a descendant of one. Used during FO validation.
*/
private boolean isOutOfLineFODescendant = false;
/** Markers added to this element. */
private Map markers = null;
/**
* Create a new formatting object.
* All formatting object classes extend this class.
*
* @param parent the parent node
*/
public FObj(FONode parent) {
super(parent);
// determine if isOutOfLineFODescendant should be set
if (parent != null && parent instanceof FObj) {
if (((FObj) parent).getIsOutOfLineFODescendant()) {
isOutOfLineFODescendant = true;
} else {
int foID = getNameId();
if (foID == FO_FLOAT || foID == FO_FOOTNOTE
|| foID == FO_FOOTNOTE_BODY) {
isOutOfLineFODescendant = true;
}
}
}
}
/**
* @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;
}
/**
* Returns the PropertyMaker for a given property ID.
* @param propId the property ID
* @return the requested Property Maker
*/
public static PropertyMaker getPropertyMakerFor(int propId) {
return propertyListTable[propId];
}
/**
* @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);
if (!inMarker()
|| "marker".equals(elementName)) {
pList.setWritingMode();
bind(pList);
}
}
/**
* Create a default property list for this element.
* @see org.apache.fop.fo.FONode
*/
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 if there is a problem binding the values
*/
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
*
* @param id the id to check
* @throws ValidationException if the ID is already defined elsewhere
* (strict validation only)
*/
protected void checkId(String id) throws ValidationException {
if (!inMarker() && !id.equals("")) {
Set idrefs = getFOEventHandler().getIDReferences();
if (!idrefs.contains(id)) {
idrefs.add(id);
} else {
if (getUserAgent().validateStrictly()) {
throw new ValidationException("Property id \"" + id
+ "\" previously used; id values must be unique"
+ " in document.", locator);
} else {
if (log.isWarnEnabled()) {
StringBuffer msg = new StringBuffer();
msg.append("Found non-unique id on ").append(getName());
if (locator.getLineNumber() != -1) {
msg.append(" (at ").append(locator.getLineNumber())
.append("/").append(locator.getColumnNumber())
.append(")");
}
msg.append("\nAny reference to it will be considered "
+ "a reference to the first occurrence "
+ "in the document.");
log.warn(msg);
}
}
}
}
}
/**
* 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 (canHaveMarkers() && child.getNameId() == FO_MARKER) {
addMarker((Marker) child);
} else {
ExtensionAttachment attachment = child.getExtensionAttachment();
if (attachment != null) {
/* This removes the element from the normal children,
* so no layout manager is being created for them
* as they are only additional information.
*/
addExtensionAttachment(attachment);
} else {
if (childNodes == null) {
childNodes = new java.util.ArrayList();
}
childNodes.add(child);
}
}
}
/**
* Used by RetrieveMarker during Marker-subtree cloning
* @param child the (cloned) child node
* @param parent the (cloned) parent node
* @throws FOPException when the child could not be added to the parent
*/
protected static void addChildTo(FONode child, FObj parent)
throws FOPException {
parent.addChildNode(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;
}
/**
* 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;
}
/**
* Return a FONode based on the index in the list of childNodes.
* @param nodeIndex index of the node to return
* @return the node or null if the index is invalid
*/
public FONode getChildNodeAt(int nodeIndex) {
if (childNodes != null) {
if (nodeIndex >= 0 && nodeIndex < childNodes.size()) {
return (FONode) childNodes.get(nodeIndex);
}
}
return null;
}
/**
* Notifies a FObj that one of it's children is removed.
* This method is subclassed by Block to clear the
* firstInlineChild variable in case it doesn't generate
* any areas (see addMarker()).
* @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 FObj
|| (node instanceof FOText
&& ((FOText) node).willCreateArea())) {
getLogger().error(
"fo:marker must be an initial child: " + mcname);
return;
} else if (node instanceof FOText) {
iter.remove();
notifyChildRemoval(node);
}
}
}
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 the collection of Markers attached to this object
*/
public Map getMarkers() {
return markers;
}
/** @see org.apache.fop.fo.FONode#gatherContextInfo() */
protected String gatherContextInfo() {
if (getLocator() != null) {
return super.gatherContextInfo();
} else {
ListIterator iter = getChildNodes();
if (iter == null) {
return null;
}
StringBuffer sb = new StringBuffer();
while (iter.hasNext()) {
FONode node = (FONode) iter.next();
String s = node.gatherContextInfo();
if (s != null) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(s);
}
}
return (sb.length() > 0 ? sb.toString() : null);
}
}
/**
* 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 (FO_URI.equals(nsURI)
&& (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 (FO_URI.equals(nsURI)
&& (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("page-number-citation-last")
|| 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 (FO_URI.equals(nsURI)
&& (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 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;
}
/** @see org.apache.fop.fo.FONode#getNamespaceURI() */
public String getNamespaceURI() {
return FOElementMapping.URI;
}
/** @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */
public String getNormalNamespacePrefix() {
return "fo";
}
/**
* Add a new extension attachment to this FObj.
* (see org.apache.fop.fo.FONode for details)
*
* @param attachment the attachment to add.
*/
public void addExtensionAttachment(ExtensionAttachment attachment) {
if (attachment == null) {
throw new NullPointerException(
"Parameter attachment must not be null");
}
if (extensionAttachments == null) {
extensionAttachments = new java.util.ArrayList();
}
if (log.isDebugEnabled()) {
getLogger().debug("ExtensionAttachment of category "
+ attachment.getCategory() + " added to "
+ getName() + ": " + attachment);
}
extensionAttachments.add(attachment);
}
/** @return the extension attachments of this FObj. */
public List getExtensionAttachments() {
if (extensionAttachments == null) {
return Collections.EMPTY_LIST;
} else {
return extensionAttachments;
}
}
/**
* Adds a foreign attribute to this FObj.
* @param attributeName the attribute name as a QName instance
* @param value the attribute value
*/
public void addForeignAttribute(QName attributeName, String value) {
/* TODO: Handle this over FOP's property mechanism so we can use
* inheritance.
*/
if (attributeName == null) {
throw new NullPointerException("Parameter attributeName must not be null");
}
if (foreignAttributes == null) {
foreignAttributes = new java.util.HashMap();
}
foreignAttributes.put(attributeName, value);
}
/** @return the map of foreign attributes */
public Map getForeignAttributes() {
if (foreignAttributes == null) {
return Collections.EMPTY_MAP;
} else {
return foreignAttributes;
}
}
}