/* * 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.flow; import java.util.Map; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOTreeBuilderContext; import org.apache.fop.fo.FObj; import org.apache.fop.fo.FObjMixed; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.PropertyListMaker; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.PropertyCache; /** * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_marker"> * <code>fo:marker<code></a> object. */ public class Marker extends FObjMixed { // The value of properties relevant for fo:marker. private String markerClassName; // End of property values private PropertyListMaker savePropertyListMaker; private Map descendantPropertyLists = new java.util.HashMap(); /** * Create a marker fo. * * @param parent the parent {@link FONode} */ public Marker(FONode parent) { super(parent); } /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { if (findAncestor(FO_FLOW) < 0) { invalidChildError(locator, getParent().getName(), FO_URI, getLocalName(), "rule.markerDescendantOfFlow"); } markerClassName = pList.get(PR_MARKER_CLASS_NAME).getString(); if (markerClassName == null || markerClassName.equals("")) { missingPropertyError("marker-class-name"); } } /** * Retrieve the property list of the given {@link FONode} * descendant * * @param foNode the {@link FONode} whose property list is requested * @return the {@link MarkerPropertyList} for the given node */ protected MarkerPropertyList getPropertyListFor(FONode foNode) { return (MarkerPropertyList) descendantPropertyLists.get(foNode); } /** {@inheritDoc} */ public void startOfNode() { FOTreeBuilderContext builderContext = getBuilderContext(); // Push a new property list maker which will make MarkerPropertyLists. savePropertyListMaker = builderContext.getPropertyListMaker(); builderContext.setPropertyListMaker(new PropertyListMaker() { public PropertyList make(FObj fobj, PropertyList parentPropertyList) { PropertyList pList = new MarkerPropertyList(fobj, parentPropertyList); descendantPropertyLists.put(fobj, pList); return pList; } }); } /** {@inheritDoc} */ public void endOfNode() throws FOPException { super.endOfNode(); // Pop the MarkerPropertyList maker. getBuilderContext().setPropertyListMaker(savePropertyListMaker); savePropertyListMaker = null; } /** * {@inheritDoc} * <br>XSL Content Model: (#PCDATA|%inline;|%block;)* * <br><i>Additionally: "An fo:marker may contain any formatting objects that * are permitted as a replacement of any fo:retrieve-marker that retrieves * the fo:marker's children."</i> * TODO implement "additional" constraint, possibly within fo:retrieve-marker */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { if (FO_URI.equals(nsURI)) { if (!isBlockOrInlineItem(nsURI, localName)) { invalidChildError(loc, nsURI, localName); } } } /** {@inheritDoc} */ protected boolean inMarker() { return true; } /** @return the "marker-class-name" property */ public String getMarkerClassName() { return markerClassName; } /** {@inheritDoc} */ public String getLocalName() { return "marker"; } /** * {@inheritDoc} * @return {@link org.apache.fop.fo.Constants#FO_MARKER} */ public int getNameId() { return FO_MARKER; } /** {@inheritDoc} */ public String toString() { StringBuffer sb = new StringBuffer(super.toString()); sb.append(" {").append(getMarkerClassName()).append("}"); return sb.toString(); } /** * An implementation of {@link PropertyList} which only stores the explicitly * specified properties/attributes as bundles of name-value-namespace * strings */ protected class MarkerPropertyList extends PropertyList implements Attributes { /** the array of attributes **/ private MarkerAttribute[] attribs; /** * Overriding default constructor * * @param fobj the {@link FObj} to attach * @param parentPropertyList ignored */ public MarkerPropertyList(FObj fobj, PropertyList parentPropertyList) { /* ignore parentPropertyList * won't be used because the attributes will be stored * without resolving */ super(fobj, null); } /** * Override that doesn't convert the attributes to {@link Property} * instances, but simply stores the attributes for later processing. * * {@inheritDoc} */ public void addAttributesToList(Attributes attributes) throws ValidationException { this.attribs = new MarkerAttribute[attributes.getLength()]; String name; String value; String namespace; String qname; for (int i = attributes.getLength(); --i >= 0;) { namespace = attributes.getURI(i); qname = attributes.getQName(i); name = attributes.getLocalName(i); value = attributes.getValue(i); this.attribs[i] = MarkerAttribute.getInstance(namespace, qname, name, value); } } /** * Null implementation; not used by this type of {@link PropertyList}. * @param propId the propert id * @param value the property value */ public void putExplicit(int propId, Property value) { //nop } /** * Null implementation; not used by this type of {@link PropertyList}. * @param propId the propert id * @return the property id */ public Property getExplicit(int propId) { return null; } /** {@inheritDoc} */ public int getLength() { if (attribs == null) { return 0; } else { return attribs.length; } } /** {@inheritDoc} */ public String getURI(int index) { if (attribs != null && index < attribs.length && index >= 0 && attribs[index] != null) { return attribs[index].namespace; } else { return null; } } /** {@inheritDoc} */ public String getLocalName(int index) { if (attribs != null && index < attribs.length && index >= 0 && attribs[index] != null) { return attribs[index].name; } else { return null; } } /** {@inheritDoc} */ public String getQName(int index) { if (attribs != null && index < attribs.length && index >= 0 && attribs[index] != null) { return attribs[index].qname; } else { return null; } } /** * Default implementation; not used. * @param index a type index * @return type string */ public String getType(int index) { return "CDATA"; } /** {@inheritDoc} */ public String getValue(int index) { if (attribs != null && index < attribs.length && index >= 0 && attribs[index] != null) { return attribs[index].value; } else { return null; } } /** {@inheritDoc} */ public int getIndex(String name, String namespace) { int index = -1; if (attribs != null && name != null && namespace != null) { for (int i = attribs.length; --i >= 0;) { if (attribs[i] != null && namespace.equals(attribs[i].namespace) && name.equals(attribs[i].name)) { break; } } } return index; } /** {@inheritDoc} */ public int getIndex(String qname) { int index = -1; if (attribs != null && qname != null) { for (int i = attribs.length; --i >= 0;) { if (attribs[i] != null && qname.equals(attribs[i].qname)) { break; } } } return index; } /** * Default implementation; not used * @param name a type name * @param namespace a type namespace * @return type string */ public String getType(String name, String namespace) { return "CDATA"; } /** * Default implementation; not used * @param qname a type name * @return type string */ public String getType(String qname) { return "CDATA"; } /** {@inheritDoc} */ public String getValue(String name, String namespace) { int index = getIndex(name, namespace); if (index > 0) { return getValue(index); } return null; } /** {@inheritDoc} */ public String getValue(String qname) { int index = getIndex(qname); if (index > 0) { return getValue(index); } return null; } } /** Convenience inner class */ public static final class MarkerAttribute { private static final PropertyCache<MarkerAttribute> CACHE = new PropertyCache<MarkerAttribute>(); /** namespace */ private String namespace; /** qualfied name */ private String qname; /** local name */ private String name; /** value */ private String value; /** * Main constructor * @param namespace the namespace URI * @param qname the qualified name * @param name the name * @param value the value */ private MarkerAttribute(String namespace, String qname, String name, String value) { this.namespace = namespace; this.qname = qname; this.name = (name == null ? qname : name); this.value = value; } /** * Convenience method, reduces the number * of distinct MarkerAttribute instances * * @param namespace the attribute namespace * @param qname the fully qualified name of the attribute * @param name the attribute name * @param value the attribute value * @return the single MarkerAttribute instance corresponding to * the name/value-pair */ private static MarkerAttribute getInstance( String namespace, String qname, String name, String value) { return CACHE.fetch( new MarkerAttribute(namespace, qname, name, value)); } /** {@inheritDoc} */ public int hashCode() { int hash = 17; hash = (37 * hash) + (this.namespace == null ? 0 : this.namespace.hashCode()); hash = (37 * hash) + (this.qname == null ? 0 : this.qname.hashCode()); hash = (37 * hash) + (this.name == null ? 0 : this.name.hashCode()); hash = (37 * hash) + (this.value == null ? 0 : this.value.hashCode()); return hash; } /** {@inheritDoc} */ public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof MarkerAttribute) { MarkerAttribute attr = (MarkerAttribute) o; return (attr.namespace != null && attr.namespace.equals(this.namespace)) && (attr.qname != null && attr.qname.equals(this.qname)) && (attr.name != null && attr.name.equals(this.name)) && (attr.value != null && attr.value.equals(this.value)); } else { return false; } } } }