//Copyright (c) 2006, Adobe Systems Incorporated //All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. All advertising materials mentioning features or use of this software // must display the following acknowledgement: // This product includes software developed by the Adobe Systems Incorporated. // 4. Neither the name of the Adobe Systems Incorporated nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY ADOBE SYSTEMS INCORPORATED ''AS IS'' AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL ADOBE SYSTEMS INCORPORATED BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // http://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html package com.itextpdf.xmp.impl; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; import com.itextpdf.xmp.XMPError; import com.itextpdf.xmp.XMPException; import com.itextpdf.xmp.XMPIterator; import com.itextpdf.xmp.XMPMetaFactory; import com.itextpdf.xmp.impl.xpath.XMPPath; import com.itextpdf.xmp.impl.xpath.XMPPathParser; import com.itextpdf.xmp.options.IteratorOptions; import com.itextpdf.xmp.options.PropertyOptions; import com.itextpdf.xmp.properties.XMPPropertyInfo; /** * The <code>XMPIterator</code> implementation. * Iterates the XMP Tree according to a set of options. * During the iteration the XMPMeta-object must not be changed. * Calls to <code>skipSubtree()</code> / <code>skipSiblings()</code> will affect the iteration. * * @since 29.06.2006 */ public class XMPIteratorImpl implements XMPIterator { /** stores the iterator options */ private IteratorOptions options; /** the base namespace of the property path, will be changed during the iteration */ private String baseNS = null; /** flag to indicate that skipSiblings() has been called. */ protected boolean skipSiblings = false; /** flag to indicate that skipSiblings() has been called. */ protected boolean skipSubtree = false; /** the node iterator doing the work */ private Iterator nodeIterator = null; /** * Constructor with optionsl initial values. If <code>propName</code> is provided, * <code>schemaNS</code> has also be provided. * @param xmp the iterated metadata object. * @param schemaNS the iteration is reduced to this schema (optional) * @param propPath the iteration is redurce to this property within the <code>schemaNS</code> * @param options advanced iteration options, see {@link IteratorOptions} * @throws XMPException If the node defined by the paramters is not existing. */ public XMPIteratorImpl(XMPMetaImpl xmp, String schemaNS, String propPath, IteratorOptions options) throws XMPException { // make sure that options is defined at least with defaults this.options = options != null ? options : new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XMPNode startNode = null; String initialPath = null; boolean baseSchema = schemaNS != null && schemaNS.length() > 0; boolean baseProperty = propPath != null && propPath.length() > 0; if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.getRoot(); } else if (baseSchema && baseProperty) { // Schema and property node provided XMPPath path = XMPPathParser.expandXPath(schemaNS, propPath); // base path is the prop path without the property leaf XMPPath basePath = new XMPPath(); for (int i = 0; i < path.size() - 1; i++) { basePath.add(path.getSegment(i)); } startNode = XMPNodeUtils.findNode(xmp.getRoot(), path, false, null); baseNS = schemaNS; initialPath = basePath.toString(); } else if (baseSchema && !baseProperty) { // Only Schema provided startNode = XMPNodeUtils.findSchemaNode(xmp.getRoot(), schemaNS, false); } else // !baseSchema && baseProperty { // No schema but property provided -> error throw new XMPException("Schema namespace URI is required", XMPError.BADSCHEMA); } // create iterator if (startNode != null) { if (!this.options.isJustChildren()) { nodeIterator = new NodeIterator(startNode, initialPath, 1); } else { nodeIterator = new NodeIteratorChildren(startNode, initialPath); } } else { // create null iterator nodeIterator = Collections.EMPTY_LIST.iterator(); } } /** * @see XMPIterator#skipSubtree() */ public void skipSubtree() { this.skipSubtree = true; } /** * @see XMPIterator#skipSiblings() */ public void skipSiblings() { skipSubtree(); this.skipSiblings = true; } /** * @see java.util.Iterator#hasNext() */ public boolean hasNext() { return nodeIterator.hasNext(); } /** * @see java.util.Iterator#next() */ public Object next() { return nodeIterator.next(); } /** * @see java.util.Iterator#remove() */ public void remove() { throw new UnsupportedOperationException("The XMPIterator does not support remove()."); } /** * @return Exposes the options for inner class. */ protected IteratorOptions getOptions() { return options; } /** * @return Exposes the options for inner class. */ protected String getBaseNS() { return baseNS; } /** * @param baseNS sets the baseNS from the inner class. */ protected void setBaseNS(String baseNS) { this.baseNS = baseNS; } /** * The <code>XMPIterator</code> implementation. * It first returns the node itself, then recursivly the children and qualifier of the node. * * @since 29.06.2006 */ private class NodeIterator implements Iterator { /** iteration state */ protected static final int ITERATE_NODE = 0; /** iteration state */ protected static final int ITERATE_CHILDREN = 1; /** iteration state */ protected static final int ITERATE_QUALIFIER = 2; /** the state of the iteration */ private int state = ITERATE_NODE; /** the currently visited node */ private XMPNode visitedNode; /** the recursively accumulated path */ private String path; /** the iterator that goes through the children and qualifier list */ private Iterator childrenIterator = null; /** index of node with parent, only interesting for arrays */ private int index = 0; /** the iterator for each child */ private Iterator subIterator = Collections.EMPTY_LIST.iterator(); /** the cached <code>PropertyInfo</code> to return */ private XMPPropertyInfo returnProperty = null; /** * Default constructor */ public NodeIterator() { // EMPTY } /** * Constructor for the node iterator. * @param visitedNode the currently visited node * @param parentPath the accumulated path of the node * @param index the index within the parent node (only for arrays) */ public NodeIterator(XMPNode visitedNode, String parentPath, int index) { this.visitedNode = visitedNode; this.state = NodeIterator.ITERATE_NODE; if (visitedNode.getOptions().isSchemaNode()) { setBaseNS(visitedNode.getName()); } // for all but the root node and schema nodes path = accumulatePath(visitedNode, parentPath, index); } /** * Prepares the next node to return if not already done. * * @see Iterator#hasNext() */ public boolean hasNext() { if (returnProperty != null) { // hasNext has been called before return true; } // find next node if (state == ITERATE_NODE) { return reportNode(); } else if (state == ITERATE_CHILDREN) { if (childrenIterator == null) { childrenIterator = visitedNode.iterateChildren(); } boolean hasNext = iterateChildren(childrenIterator); if (!hasNext && visitedNode.hasQualifier() && !getOptions().isOmitQualifiers()) { state = ITERATE_QUALIFIER; childrenIterator = null; hasNext = hasNext(); } return hasNext; } else { if (childrenIterator == null) { childrenIterator = visitedNode.iterateQualifier(); } return iterateChildren(childrenIterator); } } /** * Sets the returnProperty as next item or recurses into <code>hasNext()</code>. * @return Returns if there is a next item to return. */ protected boolean reportNode() { state = ITERATE_CHILDREN; if (visitedNode.getParent() != null && (!getOptions().isJustLeafnodes() || !visitedNode.hasChildren())) { returnProperty = createPropertyInfo(visitedNode, getBaseNS(), path); return true; } else { return hasNext(); } } /** * Handles the iteration of the children or qualfier * @param iterator an iterator * @return Returns if there are more elements available. */ private boolean iterateChildren(Iterator iterator) { if (skipSiblings) { // setSkipSiblings(false); skipSiblings = false; subIterator = Collections.EMPTY_LIST.iterator(); } // create sub iterator for every child, // if its the first child visited or the former child is finished if ((!subIterator.hasNext()) && iterator.hasNext()) { XMPNode child = (XMPNode) iterator.next(); index++; subIterator = new NodeIterator(child, path, index); } if (subIterator.hasNext()) { returnProperty = (XMPPropertyInfo) subIterator.next(); return true; } else { return false; } } /** * Calls hasNext() and returnes the prepared node. Afterwards its set to null. * The existance of returnProperty indicates if there is a next node, otherwise * an exceptio is thrown. * * @see Iterator#next() */ public Object next() { if (hasNext()) { XMPPropertyInfo result = returnProperty; returnProperty = null; return result; } else { throw new NoSuchElementException("There are no more nodes to return"); } } /** * Not supported. * @see Iterator#remove() */ public void remove() { throw new UnsupportedOperationException(); } /** * @param currNode the node that will be added to the path. * @param parentPath the path up to this node. * @param currentIndex the current array index if an arrey is traversed * @return Returns the updated path. */ protected String accumulatePath(XMPNode currNode, String parentPath, int currentIndex) { String separator; String segmentName; if (currNode.getParent() == null || currNode.getOptions().isSchemaNode()) { return null; } else if (currNode.getParent().getOptions().isArray()) { separator = ""; segmentName = "[" + String.valueOf(currentIndex) + "]"; } else { separator = "/"; segmentName = currNode.getName(); } if (parentPath == null || parentPath.length() == 0) { return segmentName; } else if (getOptions().isJustLeafname()) { return !segmentName.startsWith("?") ? segmentName : segmentName.substring(1); // qualifier } else { return parentPath + separator + segmentName; } } /** * Creates a property info object from an <code>XMPNode</code>. * @param node an <code>XMPNode</code> * @param baseNS the base namespace to report * @param path the full property path * @return Returns a <code>XMPProperty</code>-object that serves representation of the node. */ protected XMPPropertyInfo createPropertyInfo(final XMPNode node, final String baseNS, final String path) { final String value = node.getOptions().isSchemaNode() ? null : node.getValue(); return new XMPPropertyInfo() { public String getNamespace() { if (!node.getOptions().isSchemaNode()) { // determine namespace of leaf node QName qname = new QName(node.getName()); return XMPMetaFactory.getSchemaRegistry().getNamespaceURI(qname.getPrefix()); } else { return baseNS; } } public String getPath() { return path; } public String getValue() { return value; } public PropertyOptions getOptions() { return node.getOptions(); } public String getLanguage() { // the language is not reported return null; } }; } /** * @return the childrenIterator */ protected Iterator getChildrenIterator() { return childrenIterator; } /** * @param childrenIterator the childrenIterator to set */ protected void setChildrenIterator(Iterator childrenIterator) { this.childrenIterator = childrenIterator; } /** * @return Returns the returnProperty. */ protected XMPPropertyInfo getReturnProperty() { return returnProperty; } /** * @param returnProperty the returnProperty to set */ protected void setReturnProperty(XMPPropertyInfo returnProperty) { this.returnProperty = returnProperty; } } /** * This iterator is derived from the default <code>NodeIterator</code>, * and is only used for the option {@link IteratorOptions#JUST_CHILDREN}. * * @since 02.10.2006 */ private class NodeIteratorChildren extends NodeIterator { /** */ private String parentPath; /** */ private Iterator childrenIterator; /** */ private int index = 0; /** * Constructor * @param parentNode the node which children shall be iterated. * @param parentPath the full path of the former node without the leaf node. */ public NodeIteratorChildren(XMPNode parentNode, String parentPath) { if (parentNode.getOptions().isSchemaNode()) { setBaseNS(parentNode.getName()); } this.parentPath = accumulatePath(parentNode, parentPath, 1); childrenIterator = parentNode.iterateChildren(); } /** * Prepares the next node to return if not already done. * * @see Iterator#hasNext() */ public boolean hasNext() { if (getReturnProperty() != null) { // hasNext has been called before return true; } else if (skipSiblings) { return false; } else if (childrenIterator.hasNext()) { XMPNode child = (XMPNode) childrenIterator.next(); index++; String path = null; if (child.getOptions().isSchemaNode()) { setBaseNS(child.getName()); } else if (child.getParent() != null) { // for all but the root node and schema nodes path = accumulatePath(child, parentPath, index); } // report next property, skip not-leaf nodes in case options is set if (!getOptions().isJustLeafnodes() || !child.hasChildren()) { setReturnProperty(createPropertyInfo(child, getBaseNS(), path)); return true; } else { return hasNext(); } } else { return false; } } } }