All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.Node Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Adobe Systems Incorporated and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Adobe Systems
 * Incorporated and its suppliers and may be covered by U.S. and Foreign
 * Patents, patents in process, and are protected by trade secret or copyright
 * law. Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained from
 * Adobe Systems Incorporated.
 */

/*
 * Note that this is not a direct
 * equivalent of the XFANode class from C++. Much of the C++ XFANode
 * functionality has moved to the Element class
 * (which doesn't exist in the C++ implementation).
 */

package com.adobe.xfa;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import com.adobe.xfa.Element.DualDomNode;
import com.adobe.xfa.Model.DualDomModel;
import com.adobe.xfa.ut.BooleanHolder;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.MsgFormat;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.IntegerHolder;
import com.adobe.xfa.ut.StringUtils;


/**
 * A base class to represent all the types of nodes in a DOM.
 *

* The class provides methods to traverse XML DOMs and XFA DOMs. * XFA DOMs differ from XML DOMs in that they are * composed exclusively of nodes that belong to an XFA schema. */ public abstract class Node extends Obj { // Helper for getUniqueSOMName private static final class SOMNameFilter extends NodeListFilter { public SOMNameFilter(String sSearchName) { super(); msSearchName = sSearchName; } public boolean accept(Node oNode) { String sSOM = oNode.getSomName(); return msSearchName.equals(sSOM); } private final String msSearchName; } /* * An enumeration representing the allowable eMode values in calls to * Node::assignNode(). * Note! The numbers assigned to these can't be changed! The * scripting interface uses the numerical value to specify formats. */ /** * An allowable enumeration eMode value to the * {@link #assignNode(String, String, int) assignNode()} method. */ public static final int CREATE_REPLACE = 0; /** * An allowable enumeration eMode value to the * {@link #assignNode(String, String, int) assignNode()} method. */ public static final int CREATE_MUST_NOT_EXIST = 1; /** * An allowable enumeration eMode value to the * {@link #assignNode(String, String, int) assignNode()} method. */ public static final int CREATE_IF_NOT_EXIST = 2; /** * An allowable enumeration eMode value to the * {@link #assignNode(String, String, int) assignNode()} method. */ public static final int CREATE_ALWAYS_NEW = 3; /** * @exclude from published api. */ public static final String gsXFANamespacePrefix = "http://www.xfa.org/schema/"; private boolean mbDefault; // is this a default property? private boolean mbLocked; private boolean mbIsDirty; private boolean mbIsMapped; private boolean mbPermsLock; private boolean mbIsTransient; // default to false; synonym for /** * @exclude from published api. */ protected Node mNextXMLSibling; private Element mXMLParent; private boolean mbChildListModified = true; // set when adding/deleting/moving children private Document mDocument; /** * The XFA DOM node that is the peer to this XML DOM node. *

* This corresponds to the jfNodeImpl::mpoUserObject that is * accessed using jfNodeImpl::getUserObject() and set via * jfNodeImpl::setUserObject(jfObjImpl). */ private Element mXfaPeer; /** * Instantiates a node. * @param parent the node's parent, if any. * @param prevSibling the node's previous sibling, if any. */ Node(Element parent, Node prevSibling) { if (parent == null) return; mXMLParent = parent; parent.setChildListModified(true); setDocument(parent.getOwnerDocument()); if (parent.getFirstXMLChild() == null) { assert prevSibling == null; parent.setFirstChild(this); } else { // If the prevSibling was not supplied, append to end of the parent's child list if (prevSibling == null) { prevSibling = parent.getLastXMLChild(); } else { assert(prevSibling.getXMLParent() == parent); } if (prevSibling != null) { mNextXMLSibling = prevSibling.mNextXMLSibling; prevSibling.mNextXMLSibling = this; } } // The parent should be dirtied as though Element.appendChild was called. parent.setDirty(); } /** * Assigns the value given to the node located by the given * SOM (Scripting Object Model) expression and interpreted relative * to this node's context. *

* If the node doesn't exist, * it can be created depending of the value of the given eMode. * The eMode values are: *

*
CREATE_REPLACE *
If the node exists, the value * is updated, if the node doesn't * exist, it will be created. *
CREATE_MUST_NOT_EXIST *
If the node exists, an error * will be thrown, if the node * doesn't exist, it will be created. *
CREATE_IF_NOT_EXIST *
If the node exists, no action is * taken, if the node doesn't exist, * it will be created. *
CREATE_ALWAYS_NEW *
A new node is always created. *
* * @param sSOMExpression * a SOM expression evaluating to a node. * @param sValue * the value to be assigned. * @param eMode * specifies whether the node should be created or not. * @return * null. */ public Node assignNode(String sSOMExpression, String sValue, int eMode) { return null; } /** * Creates a child of the current node. This is used by assignNode() * to create ancestor nodes extracted from a SOM expression. * Derived classes need to implement this method in a schema-specific * manner. * * @param bIsLeaf true if the child to be created is a leaf child. * @param aName an interned string representing the name * of the child to be created. * @return the node created. * * @exclude from published api. */ protected Node createChild(boolean bIsLeaf, String aName) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Node#createChild"); } /** * Determines if it is legal to create a given child child under this node * @param bIsLeaf true if the child is a leaf node. * @param aName The name of the child to be created. This String must be interned. * @return true if the create operation is allowed by the schema. * * @exclude from published api. */ protected boolean canCreateChild(boolean bIsLeaf, String aName) { return false; } /** * Determines whether this node is unlocked for scripting execution. * @return true if this node is unlocked for scripting execution. * @see #setPermsLock(boolean) * @see #checkAncestorPerms() * @see #checkDescendentPerms() */ public boolean checkPerms() { return !isPermsLockSet(); } /** * Determines whether this node and all of its ancestors are unlocked for scripting execution. * @return true if this node and all of its ancestors are unlocked for scripting execution. * @see #setPermsLock(boolean) * @see #checkPerms() * @see #checkDescendentPerms() */ public boolean checkAncestorPerms() { Node node = this; while (node != null) { if (!node.checkPerms()) return false; node = node.getXFAParent(); } return true; } /** * Checks that this node and all of its descendents are unlocked for scripting execution. * @return true if this node and all of its descendents are unlocked for scripting execution. * @see #setPermsLock(boolean) * @see #checkPerms() * @see #checkAncestorPerms() */ public boolean checkDescendentPerms() { if (!checkPerms()) return false; for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (!child.checkDescendentPerms()) return false; } return true; } static void checkValidNameSpace(Object oNode, String sName) { // // make sure that any elements or attributes which have prefixes // also have a namespace URI // if (oNode instanceof Element || oNode instanceof Attribute) { String sNameSpaceUri = null; if (oNode instanceof Element) sNameSpaceUri = ((Element) oNode).getNS(); else /* if (oNode instanceof Attribute) */ sNameSpaceUri = ((Attribute) oNode).getNS(); if (sNameSpaceUri == "") { // If it's empty if (sName.indexOf (':') >= 0 ) { // // if "xmlns:" at beginning of attribute name // without a URI, this is okay // if (! (oNode instanceof Attribute) || sName.startsWith(STRS.XMLPREFIX)) { throw new ExFull(ResId.NAMESPACE_PREFIX_ERR); } } } } } /** * @exclude from published api. */ abstract public Node clone(Element parent); static boolean doQualifyNodeName(String sNodeName, boolean bSuppressThrow /* = false */) { if (StringUtils.isEmpty(sNodeName)) throw new ExFull(ResId.DOM_NODE_NAME_ERR); boolean bValid = true; // // Check the first character // int i = 0; char cChar = sNodeName.charAt(i); if (cChar != '_' && cChar != ':' && ! Character.isLetter(cChar)) { bValid = false; } else { // // Check the rest of the name // i++; while (i < sNodeName.length()) { cChar = sNodeName.charAt(i); if (cChar != '.' && cChar != '-' && cChar != '_' && cChar != ':' && ! Character.isLetterOrDigit(cChar)) { bValid = false; break; } i++; } } if (! bSuppressThrow && ! bValid) { MsgFormatPos oMsg = new MsgFormatPos(ResId.DOM_NODE_NAME_ERR); oMsg.format(sNodeName); throw new ExFull(oMsg); } return bValid; } /** * Return the collection of like-named, in-scope, nodes. * @return the collection. * * @exclude from published api. */ public NodeList getAll(boolean bByName) { if (bByName && getName() == "") { MsgFormatPos oMessage = new MsgFormatPos(ResId.NoNameException); oMessage.format("index").format("classIndex"); throw new ExFull(oMessage); } ArrayNodeList all = new ArrayNodeList(); Element poParent = getXFAParent(); if (poParent == null) { all.append(this); // this is the root node -- and there's only one // root node } else { // If our parent is transparent, we need to go up the hierarchy // before beginning our search for like-named nodes. while (poParent.isTransparent()) { Element pNewParent = poParent.getXFAParent(); if (pNewParent == null) break; poParent = pNewParent; } getAll(poParent, bByName, all); } return all; } /** * private, recursive version of getAll */ final private void getAll(Element parent, boolean bByName, ArrayNodeList all) { if (parent == null) return; Node child = parent.getFirstXMLChild(); while (child != null) { if (isLikeNode(child, bByName)) all.append(child); if (child instanceof Element && child.isTransparent()) { getAll((Element) child, bByName, all); } child = child.getNextXMLSibling(); } } /** * @exclude from published api. */ protected final boolean isChildListModified() { return mbChildListModified; } /** * Gets this node's XML child count. * @return the number of XML child nodes. */ final public int getXMLChildCount() { Node child = getFirstXMLChild(); int count = 0; while (child != null) { count++; child = child.getNextXMLSibling(); } return count; } /** * Gets this node's data. * @return the data appropriate for the various node types. */ public String getData() { return ""; // Default implementation that Element and Document will use } /** * @exclude from published api. */ protected ScriptDynamicPropObj getDynamicScriptProp(String sPropertyName, boolean bPropertyOverride, boolean bPeek) { // bPropertyOverride(#sPropertyName) means don't hunt for child nodes by name // only get xfaproperty or child element(based on classname) or script property Node child = NodeScript.getScriptChild(this, sPropertyName, !bPropertyOverride); if (child != null) return bPropertyOverride ? locateChildByClassPropObj : locateChildByNamePropObj; return null; } protected ScriptDynamicPropObj getDynamicScriptProp( String sPropertyName, boolean bPropertyOverride, boolean bPeek, int nXFAVersion, int nAvailability) { if (NodeScript.getScriptChild(this, sPropertyName, !bPropertyOverride) != null) { ScriptDynamicPropObj propObj = bPropertyOverride ? locateChildByClassPropObj : locateChildByNamePropObj; if (propObj.getXFAVersion() == nXFAVersion && propObj.getAvailability() == nAvailability) return propObj; return new NodeScriptDynamicPropObj(bPropertyOverride, nXFAVersion, nAvailability); } return null; } private static class NodeScriptDynamicPropObj extends ScriptDynamicPropObj { private final boolean mbPropertyOverride; NodeScriptDynamicPropObj(boolean bPropertyOverride, int nXFAVersion, int nAvailability) { super(nXFAVersion, nAvailability); mbPropertyOverride = bPropertyOverride; } public boolean invokeGetProp(Obj scriptThis, Arg retValue, String sPropertyName) { if (mbPropertyOverride) return NodeScript.scriptPropLocateChildByClass(scriptThis, retValue, sPropertyName); else return NodeScript.scriptPropLocateChildByName(scriptThis, retValue, sPropertyName); } } private final static ScriptDynamicPropObj locateChildByClassPropObj = new NodeScriptDynamicPropObj(true, Schema.XFAVERSION_10, Schema.XFAAVAILABILITY_ALL); private final static ScriptDynamicPropObj locateChildByNamePropObj = new NodeScriptDynamicPropObj(false, Schema.XFAVERSION_10, Schema.XFAAVAILABILITY_ALL); // JavaPort: This is incompletely implemented in XFA4J, and is only used by Designer. // void getDynamicScriptProps(List oPropNames, boolean bUseDynamic) { // // if (!bUseDynamic) // return; // // Node child = getFirstXFAChild(); // while (child != null) { // String sName = child.getName(); // // if (StringUtils.isEmpty(sName)) // oPropNames.add(sName); // // if (child instanceof Element && child.isTransparent()) { // // if the child is transparent, treat its children as if they // // were at the same level as the child. // ((Element)child).getDynamicScriptProps(oPropNames, bUseDynamic); // } // child = child.getNextXFASibling(); // } // } /** * Gets this node's first XML child. * @return null - nodes do not have children. */ public Node getFirstXMLChild() { return null; } /** * Return the first child that is an element. * Scans through children sequentially for the first one that is an * element. This is useful when processing non-XFA content. * @return First element child of the node; null if the node has no * element children or the derived class is incapable of having * children. * @exclude from published api. */ public final Element getFirstXMLChildElement() { return getNextXMLElement (getFirstXMLChild()); } /** * Gets this node's first XFA child. * @return null - nodes do not have children. */ public Node getFirstXFAChild() { return null; } /** * @exclude from published api. */ public int getIndex(boolean bByName) { // This part corresponds to the old XFANodeImpl::getIndex() if (bByName && getName() == "") { MsgFormatPos oMessage = new MsgFormatPos(ResId.NoNameException); oMessage.format("all"); oMessage.format("getIndex"); throw new ExFull(oMessage); } Element parent = getXFAParent(); if (parent == null) return 0; // this is the root node -- and there's only one root // node // This part corresponds to the old XFATreeImpl::getIndex() // If parent is transparent, replace it with its first non-transparent // parent. while (parent.isTransparent()) { Element nextParent = parent.getXFAParent(); if (nextParent == null) break; parent = nextParent; } IntegerHolder nMatchCount = new IntegerHolder(); if (getIndex(parent, bByName, nMatchCount)) return nMatchCount.value; // if has parent and not in the child list, this means that the node // can look out but is hidden from other nodes return 0; } /** * Recursive version of getIndex which deals with transparent nodes. Returns * an Integer (in matchCount) if this is found under parent (with * nMatchCount updated). null otherwise. */ final boolean getIndex(Node parent, boolean bByName, IntegerHolder nMatchCount) { if (parent == null) return false; for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (this == child) return true; // found self if (isLikeNode(child, bByName)) nMatchCount.value++; if (child.isTransparent()) { if (getIndex(child, bByName, nMatchCount)) return true; } } return false; } /** * Gets this node's last XML child. * Note that this is a fairly expensive operation -- involves iterating to * find the last child. * @return the last child, or null if none. */ public Node getLastXMLChild() { Element parent = getXMLParent(); if (parent == null) return null; Node child = parent.getFirstXMLChild(); Node last = null; while (child != null) { last = child; child = child.getNextXMLSibling(); } return last; } /** * Gets the locked state of this node. * @return true if the node is locked and false otherwise. * * @exclude from published api. */ final public boolean getLocked() { return mbLocked; } /** * Gets this node's model. * @return the model. */ public Model getModel() { // JavaPort: This was ported to XFA4J from XFANodeImpl, but it should have // only been ported to Element. Since we're here, do something reasonable // and get the Model from our parent. if (getXMLParent() != null) return getXMLParent().getModel(); return null; } /** * Gets this node's name. * * The name of a node is the value of its name attribute, or the element * tagname if there is no name attribute. * @return the name of the node. */ abstract public String getName(); /** * Gets this node's next XML sibling. * @return the next XML sibling, or null if none. */ public final Node getNextXMLSibling() { return mNextXMLSibling; } /** * Return the next sibling that is an element. * This method skips through intervening non-element nodes until it * finds one that is an element. * @return Next element subling; null if there is no such sibling. * @exclude from published api. */ public final Element getNextXMLSiblingElement() { return getNextXMLElement (mNextXMLSibling); } /** * Gets this node's next XFA sibling. * @return the next XFA sibling, or null if none. */ public Node getNextXFASibling() { Node sibling = mNextXMLSibling; while (sibling != null && sibling.getClassTag() == XFA.INVALID_ELEMENT) { sibling = sibling.getNextXMLSibling(); } return sibling; } /** * Gets this node's list of all child nodes. * @return the list of child nodes. * * @exclude from published api. */ public NodeList getNodes() { return new ArrayNodeList(); } /** * Gets this node's owner document. * * @return the owner document, or null if * this node is a document or does not have a parent. */ public final Document getOwnerDocument() { return mDocument; } /** * Gets this node's XML parent. * @return the XML parent. * @see #getXFAParent() */ public Element getXMLParent() { return mXMLParent; } /** * Gets this node's XFA parent. * @return the XFA parent. * @see #getXMLParent() */ public Element getXFAParent() { return getXMLParent(); } /** * Gets this node's previous XML sibling. * Note that this may be a fairly expensive operation, as it involves * iterating through the parent's children looking for this node. * @return the previous sibling, or null, if none. */ public Node getPreviousXMLSibling() { Node node = this; if (node instanceof DualDomNode) node = ((DualDomNode)node).getXmlPeer(); Element parent = node.getXMLParent(); if (parent == null) return null; Node child = parent.getFirstXMLChild(); Node prev = null; while (child != null) { if (child == node) return prev; prev = child; child = child.getNextXMLSibling(); } return null; } /** * used for determining uniqueness when resolving protos * @exclude from published api. */ public String getPrivateName() { return getName(); } /** * Get a property for this node. * @param ePropertyTag * The XFA tag (name) of the property to check for. * @param nOccurrence * if this property can occur a fix number of times (usually * [0..n]), then specify which occurrence to get. Defaults to 0. * @return The requested property. If the property has not been specified, * this will contain a default value. * * There is a special case for the handling of pcdata. Technically, pcdata * is a child node relationship, but it is retrieved via an attribute - * XFAString. The property name in this case is * XFA::textNodeTag(). * * The return value will never be a null object. The XFAProperty will refer * to either an Node or an Attribute. * * @exclude from published api. */ public Object getProperty(int ePropertyTag, int nOccurrence /* = 0 */) { return null; } /** * string version of getProperty. ***Less efficient than the int version * The parameter propertyName should correspond to the name of * either a child element that occurs 0..1 times OR is the name of * an attribute. The parameter propertyName must be a valid * property for this particular class. * * @exclude from published api. */ public Object getProperty(String propertyName, int nOccurrence /* = 0 */) { return null; }// Search for a named child, handling transparent nodes. /** * @exclude from published api. */ public ScriptTable getScriptTable() { return NodeScript.getScriptTable(); } /** * private, recursive version of getSibling */ final Node getSibling(Node parent, int searchIndex, boolean bByName, IntegerHolder matchCount) { if (parent == null || parent.getFirstXFAChild() == null) return null; for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (isLikeNode(child, bByName)) { if (matchCount.value == searchIndex) return child; matchCount.value++; } if (child.isTransparent()) { // recursively look through transparent node Node node = getSibling(child, searchIndex, bByName, matchCount); if (node != null) return node; // found it. } } return null; } /** * @exclude from published api. */ public Node getSibling(int index, boolean bByName, boolean bExceptionIfNotFound) { if (bByName) assert (getName() != ""); Element parent = getXFAParent(); if (parent == null) { if (index == 0) // only one root node; 0 is only valid index return this; if (!bExceptionIfNotFound) return null; throw new ExFull(ResId.IndexOutOfBoundsException); } // If parent is transparent, replace it with its first non-transparent // parent. while (parent.isTransparent()) { Element nextParent = parent.getXFAParent(); if (nextParent == null) break; parent = nextParent; } Node sibling = getSibling(parent, index, bByName, new IntegerHolder()); if (sibling != null) return sibling; // requested index does not exist if (bExceptionIfNotFound) throw new ExFull(ResId.IndexOutOfBoundsException); return null; } /** * Gets this element's absolute SOM expression. * @return the SOM expression reflecting this element's absolute * location within the document hierarchy. */ final public String getSOMExpression() { return getSOMExpression(null, false); } /** * Gets this element's relative SOM expression. * * @param oRelativeTo if non-null, the SOM expression will be * relative to this node. In order for this to be useful, the * given node must be in the parent hierarchy of this element. * @return the SOM expression reflecting this element's relative * location within the document hierarchy. * * @exclude from published api. */ final public String getSOMExpression(Node oRelativeTo, boolean bSkipZeroIndex /* = false */) { if (this == oRelativeTo) return "$"; // don't check isTransparent, give explicit som expressions. // Get this node's name. StringBuilder sNodeName = new StringBuilder(getSomName()); // getSOMExpression is not defined if the node doesn't have a name. if (StringUtils.isEmpty(sNodeName)) { throw new ExFull(new MsgFormat(ResId.NamelessNodeInGetSOMException, getClassAtom())); } // Escape all dot characters if they appear in the name. SOMParser.escapeSomName(sNodeName); boolean bUseName = sNodeName.charAt(0) != '#'; // Get this element's index. int nIndex = getIndex(bUseName); if (!bSkipZeroIndex || nIndex != 0) { sNodeName.append('['); sNodeName.append(Integer.toString(nIndex)); sNodeName.append(']'); } // Get this element's parent. Element oParent = getXFAParent(); if (oParent != null && oParent != oRelativeTo) { // // Recurse up the parent hierarchy. // String sParent = oParent.getSOMExpression(oRelativeTo, bSkipZeroIndex); if (sParent.length() > 0) { sNodeName.insert(0, '.'); sNodeName.insert(0, sParent); } } return sNodeName.toString(); } /** * @exclude from published api. */ public String getSomName() { if (useNameInSOM()) return getName(); return "#" + getClassAtom(); } /** * Get a minimal SOM name/identifier for this node which is unique within * the given context. * @param oContextNode * the SOM expression generated will be a minimal SOM expression * capable of uniquely identifying the node within the context of * this node. This node must be an ancestor. * @return - minimal SOM expression * * @exclude from published api. */ /** * @exclude from published api. */ final public String getUniqueSOMName(Element contextNode) { // check that contextNode is an ancestor Element ancestor = getXMLParent(); while (true) { if (ancestor == null) return ""; // didn't find context node if (ancestor == contextNode) break; ancestor = ancestor.getXMLParent(); } // // Get NodeName name // String sNodeName = getSomName(); if (StringUtils.isEmpty(sNodeName)) return ""; SOMNameFilter filter = new SOMNameFilter(sNodeName); // using unescaped name List nodesInContext = filter.filterNodes(contextNode, 0); // Now escape any dot characters if they appear in the name. sNodeName = SOMParser.escapeSomName(sNodeName); Node parent = getXMLParent(); String sSiblingsSOM = sNodeName + "[*]"; NodeList scopeMatches = parent.resolveNodes(sSiblingsSOM, false, false, false); if (nodesInContext.size() == 1 && scopeMatches.length() == 1) { return sNodeName; } if (scopeMatches.length() > 1) { boolean bUseName = sNodeName.charAt(0) != '#'; int nIndex = getIndex(bUseName); sNodeName = sNodeName + '[' + Integer.toString(nIndex) + ']'; } if (scopeMatches.length() == nodesInContext.size()) { return sNodeName; } if (parent == contextNode) { return sNodeName; } String sParent = parent.getUniqueSOMName(contextNode); return sParent + '.' + sNodeName; } /** @exclude from published api */ public boolean getWillDirty() { Document document = getOwnerDocument(); // If this node is not attached to a document yet, then assume that // we are currently loading, so it won't dirty the doc. Actually appending // the node to the document should cause dirtying if necessary. if (document == null) return false; return document.getWillDirty(); } /** * Gets this node's number of XFA children. * @return the number of XFA child nodes. */ final public int getXFAChildCount() { Node child = getFirstXFAChild(); int count = 0; while (child != null) { count++; child = child.getNextXFASibling(); } return count; } /** * Return a list of all child nodes for this node. * @return the list of child nodes. */ NodeList getXFANodes() { return null; } /** * Sets this changed state of this node and its descendants to the given state. * * @param bIsDirty the dirty state. */ public final void hasChanged(boolean bIsDirty) { if (!getWillDirty()) return; Node node = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; node.mbIsDirty = bIsDirty; // When traversing down the tree, if we come to a ModelPeer that is not dual-DOM, // then we need to jump over to the XFA DOM side before traversing the children. if (node instanceof ModelPeer && !(node instanceof DualDomModel)) node = ((ModelPeer)node).getXfaPeer(); for (Node child = node.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { child.hasChanged(bIsDirty); } } public final void cleanDirtyFlags() { Node node = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; node.mbIsDirty = false; // When traversing down the tree, if we come to a ModelPeer that is not dual-DOM, // then we need to jump over to the XFA DOM side before traversing the children. if (node instanceof ModelPeer && !(node instanceof DualDomModel)) node = ((ModelPeer)node).getXfaPeer(); for (Node child = node.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { child.cleanDirtyFlags(); } } /** * Check to see if this is a container object. A container is defined as * something that is not a leaf node not properties ( [0..1] occurrences ). * It does NOT indicate whether this node derives from XFAContainer * @return true if this node is a container, false otherwise * * @exclude from published api. */ public boolean isContainer() { return false; } /** * @exclude from published api. */ public boolean isDefault(boolean bCheckProto/*true*/) { Node peer = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; if (peer != null) return peer.mbDefault; return false; } /** * Determine if this node is dirty. * @return the dirty state. * * @exclude from published api. */ public final boolean isDirty() { Node node = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; return node.mbIsDirty; } /** * Set the dirty state of this node. * @exclude from published api. */ public final void setDirty() { if (!getWillDirty()) return; Node node = this; while (node != null) { // Dirtiness is an XML Document thing, so maintain it on the XML DOM side. node = node instanceof DualDomNode ? ((DualDomNode)node).getXmlPeer() : node; if (node.mbIsDirty) return; node.mbIsDirty = true; // Continue propagating the dirtiness upward to the containing Document node = node.getXMLParent(); } } /** @exclude */ public final void setDocument(Document document) { mDocument = document; } /** * Is this node is a leaf. * @return true if this node is a leaf and false otherwise. * * @exclude from published api. */ abstract public boolean isLeaf(); @FindBugsSuppress(code="ES") boolean isLikeNode(Node pNode, boolean bByName) { if (this == pNode) // is it me? return true; if (bByName) return getName() == pNode.getName(); else return isSameClass(pNode); } /** * Get the mapped state for the current node. * * @exclude from public api. */ public boolean isMapped() { return mbIsMapped; } /** * Gets the permissions state of this node. * @return true if the node is locked. * * @exclude from public api. */ public boolean isPermsLockSet() { return mbPermsLock; } /** * Check if a specific property has been defined for this node. * @param ePropertyTag * The XFA tag (name) of the property to check for. * @param bCheckProtos * if true, check if this property is specified via prototype * inheritance. Defaults to true. * @param nOccurrence * if this property can occur a fix number of times (usually * [0..n]), then specify which occurrence to check for. Defaults * to 0. * @return True if the property has been specified. *

The property name should correspond to the name of either a * child element that occurs 0..1 times OR is the name of an * attribute. The parameter propertyName must be a valid property * for this particular class. * * @exclude from public api. */ public boolean isPropertySpecified(int ePropertyTag, boolean bCheckProtos/* = true */, int nOccurrence/* = 0 */) { return false; } // string version of isPropertySpecified. ***Less efficient than the int // version ***. boolean isPropertySpecified(String propertyName, boolean bCheckProtos/* = true */, int nOccurrence/* = 0 */) { return false; } /** * Check if a property, child, or oneOfChild has been defined for this node * @param ePropertyTag * The XFA tag (name) of the property/child/oneOfChild to check * for. * @param bCheckProtos * if true, check if this property is specified via prototype * inheritance. Defaults to true. * @param nOccurrence * if this property can occur a fix number of times (usually * [0..n]), then specify which occurrence to check for. Defaults * to 0. * @return True if the property has been specified. * * @exclude from public api. */ public boolean isSpecified(int ePropertyTag, boolean bCheckProtos/* =true */, int nOccurrence/* =0 */) { return false; } // string version of isSpecified. ***Less efficient than the int version // ***. boolean isSpecified(String sPropertyName, boolean bCheckProtos/* =true */, int nOccurrence/* =0 */) { return false; } /** * Determine if this node is transient or not. Transient nodes are not saved * when the DOM is written out. * @return boolean transient status. * * @exclude from published api. */ public boolean isTransient() { return mbIsTransient; } /** * Set the transient state of this node. Transient nodes are not saved when * the DOM is written out. * @param bTransient * the new transient state. * * @exclude from published api. */ public void isTransient(boolean bTransient, boolean bSetChildren /* = false */) { mbIsTransient = bTransient; // if setting to false. set parent node to false if (!bTransient) { Node parent = getXMLParent(); if ((parent != null) && (parent.isTransient() == true)) parent.isTransient(false, false); } if (!bSetChildren) return; Node child = getFirstXMLChild(); while (child != null) { child.isTransient(bTransient, true); child = child.getNextXMLSibling(); } } /** * @exclude from published api. */ public boolean isTransparent() { return false; } /** * @exclude from published api. */ public Node locateChildByClass(int eChildTag, int nIndex) { // first check children of this node for the named node int nFound = 0; Node child = getFirstXFAChild(); while (child != null) { if (child.getClassTag() == eChildTag) { if (nFound == nIndex) return child; nFound++; } child = child.getNextXFASibling(); } return null; } /** * Note. This method needs to use the "XML" API calls * as it is invoked by HrefStore.filterPackets() to read * config values. * @exclude from published api. */ @FindBugsSuppress(code="ES") public final Node locateChildByName(String aChildName, int nIndex) { if (aChildName == null) return null; // first check children of this node for the named node int nFound = 0; for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (aChildName == child.getName()) { if (nFound == nIndex) return child; nFound++; } } return null; } /** * Mark this element as a default property * * @exclude from published api. */ public void makeDefault() { Node peer = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; peer.mbDefault = true; Node child = peer.getFirstXFAChild(); while (child != null) { child.makeDefault(); child = child.getNextXFASibling(); } } /** * Mark this element to indicate it is not a default property * * @exclude from published api. */ public void makeNonDefault(boolean bRecursive /* = false */) { Node peer = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; // If we are currently a default property, changing us in any // aspect makes us a non-default (non-transient) property. if (peer.isDefault(false)) { // Version checking was deferred on default elements. (See XFANodeImpl::defaultElementImpl()). So do it now. if (getModel() != null) { Schema.checkVersion(getClassTag(), getModel(), getXFAParent()); } peer.setDefaultFlag(false, bRecursive); } // Watson 1616092. Must dirty the dom node even if oPeer.getDefaultFlag() // is off. Some elements on the form model are created with the default flag off, // but we have to catch the case where attributes on those nodes are modified. if (getModel() != null && !getModel().isLoading()) setDirty(); } /** * @exclude from published api. */ protected void setDefaultFlag(boolean bDefaultNode, boolean bSetChildren) { Node peer = this instanceof DualDomNode ? ((DualDomNode)this).getXmlPeer() : this; if (peer.isDefault(false) != bDefaultNode || bSetChildren) { peer.mbDefault = bDefaultNode; // Make the children non-default if (bDefaultNode || bSetChildren) { for (Node child = peer.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { child.setDefaultFlag(bDefaultNode, bSetChildren); } } // Make the parent non-default if (!bDefaultNode) { Element parent = peer.getXMLParent(); if (parent != null) { // JavaPort: Don't turn the AppModel non-transient... // The AppModel only becomes non-transient when it's loaded from XML or // created as a scratch document. if (!(parent instanceof AppModel)) parent.setDefaultFlag(false, false); } } } } /** * @exclude from published api. */ @FindBugsSuppress(code="SF") public void notifyPeers(int eventType, String arg1, Object arg2) { // XFA doesn't notify of changes during loads, there is no need and it improves load performance if (getModel() != null && getModel().isLoading()) return; super.notifyPeers(eventType, arg1, arg2); sendParentUpdate(eventType, arg1, arg2); } /** * Helper function for notify peers. * @exclude from published api. */ @FindBugsSuppress(code="SF") protected void sendParentUpdate(int eventType, String arg1, Object arg2) { // notify up to a container. if (notifyParent()) { // Node will notify upward until a updateFromPeer call returns FALSE; Node parent = getXFAParent(); if (parent != null && !isMute()) { // Change the type for the parent. int eNewType; switch(eventType) { case Peer.CHILD_ADDED: // fall through case Peer.DESCENDENT_ADDED: eNewType = Peer.DESCENDENT_ADDED; break; case Peer.CHILD_REMOVED: // fall through case Peer.DESCENDENT_REMOVED: eNewType = Peer.DESCENDENT_REMOVED; break; case Peer.ATTR_CHANGED: arg2 = this; // fall through case Peer.DESCENDENT_ATTR_CHANGED: eNewType = Peer.DESCENDENT_ATTR_CHANGED; break; case Peer.VALUE_CHANGED: arg2 = this; // fall through case Peer.DESCENDENT_VALUE_CHANGED: eNewType = Peer.DESCENDENT_VALUE_CHANGED; break; // handle new proto messages case Peer.PROTO_ATTR_CHANGED: case Peer.PROTO_DESCENDENT_ATTR_CHANGED: eNewType = Peer.PROTO_DESCENDENT_ATTR_CHANGED; break; case Peer.PROTO_VALUE_CHANGED: case Peer.PROTO_DESCENDENT_VALUE_CHANGED: eNewType = Peer.PROTO_DESCENDENT_VALUE_CHANGED; break; case Peer.PROTO_CHILD_ADDED: case Peer.PROTO_DESCENDENT_ADDED: eNewType = Peer.PROTO_DESCENDENT_ADDED; break; case Peer.PROTO_CHILD_REMOVED: case Peer.PROTO_DESCENDENT_REMOVED: eNewType = Peer.PROTO_DESCENDENT_REMOVED; break; default: return; // don't notify parent } // notify parent parent.notifyPeers(eNewType, arg1, arg2); } } } /** * @exclude from published api. */ protected boolean notifyParent() { return !isContainer(); } /** * @exclude from published api. */ public boolean performSOMAssignment(String sLHS, String sRHS, Obj[] oObjectParameters) { Model model = getModel(); if (model == null) return false; AppModel appModel = model.getAppModel(); if (appModel == null) return false; DependencyTracker oDependencyTracker = appModel.dependencyTracker(); SOMParser oParser = new SOMParser(oDependencyTracker); List oLHSResult = new ArrayList(); oParser.resolve(this, sLHS, oObjectParameters, oLHSResult, null); if (oLHSResult.size() == 0) return false; Arg arg = new Arg(); if (sRHS == null) arg.setNull(); else arg.setString(sRHS); for (int i = 0; i < oLHSResult.size(); i++) { SOMParser.SomResultInfo oResultInfo = ((SOMParser.SomResultInfo) oLHSResult.get(i)); String sProperty = null; if (oResultInfo.propertyName == null) { // // just a regular node, not a property name // this might cause an exception if the object // doesn't have a default property // sProperty = ""; } else { // // property value assignment // note this doesn't deal with occurrence numbers // of properties! should work OK // except when property is not an Node (Nodes are // handled in the above if clause) // sProperty = oResultInfo.propertyName; } oResultInfo.object.setScriptProperty(sProperty, arg, false); } return true; } /** * READ ONLY VERSION of getOneOfChild In the case where an element may * contain a "OneOf" child, this method will retrieve the child element that * has the XFA::oneOfChild relationship to its parent. *

* When one only one child node out of a set of nodes can exist for * a particular node, then that set of nodes is called a oneOf * children. *

* Note! Proto references are not expanded, SHOULD NOT modify the * resulting node. * * @param bReturnDefault * true if you want the defualt node to be returned if the * "OneOf" child doesn't exist, if false null will be returned * @return the "OneOf" child for this node. If this child has not been * specified, this method will return null. * @exclude from public api. */ public Node peekOneOfChild(boolean bReturnDefault /* = false */) { return null; } /** * Get a property for this node. *

* Note! Default properties are not created and proto references are not expanded, * SHOULD NOT modify the resulting node. * * @param ePropertyTag * The XFA tag (name) of the property to check for. * @param nOccurrence * if this property can occur a fix number of times (usually * [0..n]), then specify which occurrence to get. Defaults to 0. * @return The requested property. If the property has not been specified, * null is returned. */ Object peekProperty(int ePropTag, int nOccurrence/* =0 */) { return null; } /** * string version of peekProperty. ***Less efficient than the int version *

* The parameter propertyName should correspond to the name of * either a child element that occurs 0..1 times OR is the name of * an attribute. The parameter propertyName must be a valid * property for this particular class. */ Object peekProperty(String propertyName, int nOccurrence/* =0 */) { return null; } /** * @exclude from published api. */ abstract public void postSave(); /** * @exclude from published api. */ abstract public void preSave(boolean bSaveXMLScript /* = false */); /** * Removes this node from its parent child list. */ public void remove() { Element parent = getXFAParent(); // Do not dirty the document if we are removing a default node. The object must be in the same scope // as the code that removes the node Node willDirtyNode = null; boolean previousWillDirty = false; if (isDefault(true)) { willDirtyNode = parent; previousWillDirty = willDirtyNode.getWillDirty(); willDirtyNode.setWillDirty(false); } try { if (parent != null) { parent.removeChild(this); } } finally { if (willDirtyNode != null) willDirtyNode.setWillDirty(previousWillDirty); } } /** * Evaluates the Scripting Object Model expression, using this node as * the current context. *

* For example, resolveNode("data.name[1]") * returns the requested node if it exists; otherwise it returns null. *

* The method call resolveNode(somExpr) is equivalent * to the call: *

* resolveNode(somExpr, false, false, false) *
* @param somExpr a SOM expression. * @return the node corresponding to the SOM expression if it exists, * and null otherwise. * @exception ExFull of type SOMTypeException, if more than one node * was found. * @see #resolveNode(String, boolean, boolean, boolean) */ final public Node resolveNode(String somExpr) { return resolveNode(somExpr, false, false, false, null, null); } /** * Evaluates the Scripting Object Model expression, using this node as * the current context. *

* To peek at the node, set the peek argument to true. * If the node is present, it is returned; otherwise null * is returned. * When set to true, default properties aren't created, and proto * references are not expanded. * @param somExpr a SOM expression. * @param bPeek whether to beek at the node, or not. * @param bLastOccurence whether to get the last occurence * of the node whenever [*] is used in the somExpr argument, or not. * @param bNoProperties whether to return no properties * in the result, or not. * @return the node corresponding to the SOM expression if it exists, * and null otherwise. * @exception ExFull of type SOMTypeException, if more than one node * was found. */ final public Node resolveNode(String somExpr, boolean bPeek /* = false */, boolean bLastOccurence /* = false */, boolean bNoProperties /* = false */) { return resolveNode(somExpr, bPeek, bLastOccurence, bNoProperties, null, null); } /** * Evaluates the Scripting Object Model expression, using this node as * the current context. * * @exclude from published api. */ final public Node resolveNode(String somExpr, boolean bPeek /* = false */, boolean bLastOccurence /* = false */, boolean bNoProperties /* = false */, DependencyTracker dependencyTracker /* = null */, BooleanHolder isAssociation /* = null */) { NodeList oResult = resolveNodes(somExpr, bPeek, bLastOccurence, bNoProperties, dependencyTracker, isAssociation ); if (oResult.length() == 0) return null; if (oResult.length() != 1) throw new ExFull(ResId.SOMTypeException); return (Node) oResult.item(0); } /** * Evaluates the Scripting Object Model expression, using this node as * the current context. *

* For example, resolveNodes("data.name[*]") * returns a node list corresponding to the SOM expression, which may be * an empty. * @param somExpr a SOM expression. * @param bPeek if true, don't create default properties in the result. * @param bLastOccurence if true, only get the last occurence whenever [*] * is used in the SOM expression. * @param bNoProperties if true, don't return properties in the result. * @return a node list corresponding to the SOM expression if nodes are * found, an empty NodeList (not null) otherwise. */ public NodeList resolveNodes(String somExpr, boolean bPeek /* = false */, boolean bLastOccurence /* = false */, boolean bNoProperties /* = false */) { return resolveNodes(somExpr, bPeek, bLastOccurence, bNoProperties, null, null); } /** * Evaluates the Scripting Object Model expression, using this node as * the current context. * * @exclude from published api. */ public NodeList resolveNodes(String somExpr, boolean bPeek /* = false */, boolean bLastOccurence /* = false */, boolean bNoProperties /* = false */, DependencyTracker dependencyTracker /* = null */, BooleanHolder isAssociation /* = null */) { SOMParser oParser = new SOMParser(null); ArrayNodeList oResult = new ArrayNodeList(); oParser.setOptions(bPeek, bLastOccurence, bNoProperties); oParser.resolve(this, somExpr, oResult, isAssociation); return oResult; } /** * @see Obj#sendMessenge(ExFull, int) * @exclude from published api. */ public void sendMessenge(ExFull error, int eSeverity /* = LogMessage.MSG_WARNING */) { Model pModel = getModel(); if (pModel != null) pModel.addErrorList(error, eSeverity, null); } /** * The helper function used by saveXML() * @param outFile * Streamfile to write to * @param options * save options * @param level * the indent level * @param prevSibling * our previous sibling -- needed for some markup options. * @throws IOException * * @exclude from published api. */ abstract public void serialize(OutputStream outFile, DOMSaveOptions options, int level, Node prevSibling) throws IOException; /** @exclude from published api. */ protected final void setChildListModified(boolean bIsChildListModified) { mbChildListModified = bIsChildListModified; } /** * Set the locked state of this node to be locked * * @exclude from published api. */ final public void setLocked(boolean bLockState) { mbLocked = bLockState; } /** * Set the mapped state for the current node. * @param bIsMapped * @exclude from public api. */ public void setMapped(boolean bIsMapped) { mbIsMapped = bIsMapped; } /** * @exclude from published api. */ public void setName(String aName) { // Makes sense only in derived classes. } /** * Sets this node's next XML sibling. * @param node the sibling. */ protected final void setNextXMLSibling(Node node) { mNextXMLSibling = node; //setDirty(); // caller will dirty } /** * Sets this node's XML parent. * @param parent the parent. */ protected final void setXMLParent(Element parent) { mXMLParent = parent; //setDirty(); // caller will dirty } /** * Sets the permissions state of this node. Locking a node will * cause some attempts to invoke methods or set properties to throw * an exception. * * @param bPermsLock * the permissions state to set this node to: true * will lock the node; false will unlock the node. */ public void setPermsLock(boolean bPermsLock) { mbPermsLock = bPermsLock; if (bPermsLock) notifyPeers(Peer.PERMS_LOCK_SET, "", null); else notifyPeers(Peer.PERMS_LOCK_CLEARED, "", null); } /** @exclude from published api. */ public void setWillDirty(boolean bWillDirty) { if (getOwnerDocument() != null) getOwnerDocument().setWillDirty(bWillDirty); } /** * used when resolving protos * @exclude from published api. */ public void setPrivateName(String aNewName) { setName(aNewName); } /** * @exclude from published api. */ final public void unLock() { mbLocked = false; } boolean useNameInSOM() { return !StringUtils.isEmpty(getName()); } /** * Validate this node against the schema. * * @param nTargetVersion the target XFA version * @param nTargetAvailability a collection of bits defining what subsets of the schema are supported * @param pValidationInfo list of invalid children, attributes and attribute values based on the target * version and availability. If this node is not valid child of its parent this node will be * the first entry of pInfo. * @return TRUE if this node, the child or all attributes are valid, otherwise FALSE * @exclude from published api. */ public boolean validateSchema(int nTargetVersion /* = XFAVERSION_HEAD */, int nTargetAvailability /* = XFAAVAILABILITY_ALL */, boolean bRecursive /* = false */, List pValidationInfo /* = null */) { boolean bRet = true; Element poParent = getXFAParent(); if (poParent != null) { NodeSchema oNodeSchema = poParent.getNodeSchema(); if (!Element.validateNodeSchema(this, oNodeSchema, nTargetVersion, nTargetAvailability, pValidationInfo)) { // Node is invalid bRet = false; if (pValidationInfo == null) return false; // break out early if we know this node is invalid } } // JavaPort: TextNode elements need to exit here so that we can assume // we're dealing with an Element from here on. if (!(this instanceof Element)) return true; Element e = (Element)this; // get schema info for this node NodeSchema oNodeSchema = e.getNodeSchema(); // validate the attributes int len = e.getNumAttrs(); for (int i = 0; i < len; i++) { Attribute oAttr = e.getAttr(i); String aAttrName = oAttr.getLocalName(); // look up the attribute name int eTag = XFA.getTag(aAttrName); if (eTag != -1) { AttributeInfo pInfo = oNodeSchema.getAttributeInfo(eTag); // only validate attributes we know about if (pInfo != null) { boolean bInvalid = false; int nVerIntro = pInfo.getVersionIntroduced(); int nAvail = pInfo.getAvailability(); // deprecation is not an error if (nTargetVersion < nVerIntro && !(pInfo.getDefault() instanceof EnumValue)) { bInvalid = true; } else if ((nTargetAvailability & nAvail) == 0) bInvalid = true; if (bInvalid) { bRet = false; if (pValidationInfo != null) { // add attr NodeValidationInfo oInfo = new NodeValidationInfo(eTag, EnumAttr.UNDEFINED, nVerIntro, nAvail, this); pValidationInfo.add(oInfo); } else return false; // stop if we don't have a list to populate } else if (pInfo.getDefault() instanceof EnumValue) { EnumValue oEnum = (EnumValue) e.getAttribute(eTag); int eValue = oEnum.getInt(); // JavaPort: This code is structure a little different from the C++ // in that we have all the info we need in our EnumAttr class // while in C++ we have to loop through arrays... EnumAttr eAttr = oEnum.getAttr(); nVerIntro = eAttr.getVersionIntro(); nAvail = eAttr.getAvailability(); bInvalid = false; if (nTargetVersion < nVerIntro) bInvalid = true; else if ((nTargetAvailability & nAvail) == 0) bInvalid = true; if (bInvalid) { bRet = false; if (pValidationInfo != null) { // add attr value NodeValidationInfo oInfo = new NodeValidationInfo(eTag, eValue, nVerIntro, nAvail, this); pValidationInfo.add(oInfo); } else return false; // stop if we don't have a list to populate } } } } } // validate the children if (bRecursive) { Node poChild = getFirstXFAChild(); while (poChild != null) { if (!poChild.validateSchema(nTargetVersion, nTargetAvailability, true, pValidationInfo)) { bRet = false; if (pValidationInfo == null) return false; // stop if we don't have a list to populate } poChild = poChild.getNextXFASibling(); } } return bRet; } /** * @see Obj#validateUsage(int, int, boolean) * @exclude from published api. */ public boolean validateUsage(int nXFAVersion, int nAvailability, boolean bUpdateVersion) { Model model = getModel(); if (model != null) return model.validateUsage(nXFAVersion, nAvailability, bUpdateVersion); return false; } /** * @see Obj#validateUsageFailedIsFatal(int, int) * @exclude from published api. */ public boolean validateUsageFailedIsFatal(int nXFAVersion, int nAvailability) { Model model = getModel(); if (model != null) return model.validateUsageFailedIsFatal(nXFAVersion, nAvailability); return false; } /** * This interface defines the logging operations available when differences (changes) are encountered * while comparing DOMs using {@link #compareVersions(Node, Node.ChangeLogger, Object)}. */ public interface ChangeLogger { /** * Logs an encountered property change. * @param oContainer the node a change was found on. * @param sPropName the changed property name. * @param sPropValue the new property value. * @param oUserData an optional application-supplied object managed by the ChangeLogger. * @exclude from published api. */ public void logPropChange(Node oContainer, String sPropName, String sPropValue, Object oUserData); /** * Logs an encountered value change. * @param oContainer the node a change was found on. * @param sPropValue the changed property value. * @param oUserData an optional application-supplied object managed by the ChangeLogger. * @exclude from published api. */ public void logValueChange(Node oContainer, String sPropValue, Object oUserData); /** * Logs an encountered child change. * @param oContainer the node a change was found on. * @param oChild the changed child node. * @param oUserData an optional application-supplied object managed by the ChangeLogger. * @exclude from published api. */ public void logChildChange(Node oContainer, Node oChild, Object oUserData); /** * Logs an encountered data change. * @param oCurrent the node a change was found on. * @param oRollback the corresponding rollback node. * @param bCurrentModelled whether the changed value was modelled. * @param bRollbackModelled whether the rollback value was modelled. * @param sPropValue the changed value. * @param oUserData an optional application-supplied object managed by the ChangeLogger. * @exclude from published api. */ public void logDataChange(Node oCurrent, Node oRollback, boolean bCurrentModelled, boolean bRollbackModelled, String sPropValue, Object oUserData); } /** * Determines if this node (and all it's descendants) differs from the given roll-back node. * Callers wishing to know what differs can supply a change logger which will get a notification * call for each change found: any out-of-order nodes will get reported as changed. * * @param oRollbackNode the roll-back node. * @param oChangeLogger an optional (may be null) instance of a change logger. * The change logger's methods will be called for each change found. * @param oUserData an optional (may be null) user-supplied object managed by the change logger. * * @return true if this node matches the roll-back node, and false otherwise. */ public boolean compareVersions(Node oRollbackNode, Node.ChangeLogger oChangeLogger /* = null */, Object oUserData /* = null */) { if (compareVersionsBasic(oRollbackNode, this, oChangeLogger, oUserData) == false) return false; return compareVersions(oRollbackNode, this, oChangeLogger, oUserData); } /** * Base class method, with one additional parameter to the public API. * This method is overridden by Element, TextNode, RichTextNode, XMLMultiSelectNode, * Packet, and DataNode. * * Parameters are the same as above, with the addition of oContainer, * the current container context (always in the source DOM, even when the * current element crosses from the Form DOM into the Template DOM). * * @exclude from published api. */ protected boolean compareVersions(Node oRollbackNode, Node oContainer, Node.ChangeLogger oChangeLogger /* null */, Object oUserData /* null */) { return false; } /** * Test if the roll-back node is null or of a different class. * Emulates XFANode::compareVersions(), with the class test added * for TextNode. * * returns false if oRollbackNode is null or of a different class (handled) * true otherwise (proceed with further comparisons). * * This method is also called by TextNode. * * @exclude from published api. */ @FindBugsSuppress(code="ES") final boolean compareVersionsBasic(Node oRollbackNode, Node oContainer, Node.ChangeLogger oChangeLogger, Object oUserData) { // // TEST FOR NULL ROLLBACK // if (oRollbackNode == null) { if (oChangeLogger != null) oChangeLogger.logChildChange(getXFAParent(), this, oUserData); return false; } if ( this instanceof TextNode) { // // COMPARE ELEMENT TAG // if (getClassAtom() != oRollbackNode.getClassAtom()) { // oRollbackNode must be of the same class. // Non-matching children -- this either means the caller messed up (and started with non- // matching children), or a child has been added or deleted. if (oChangeLogger != null) { if (isContainer()) oChangeLogger.logChildChange(oContainer, this, oUserData); else oChangeLogger.logPropChange(oContainer, getPropName(getXFAParent(), XFA.INVALID_ELEMENT), getNodeAsXML(this), oUserData); } // XFA is pretty strongly-typed. There's certainly no sense in comparing attributes if // the elements don't match, and even comparing children is unlikely to provide useful // information. I suppose one could make the argument that knowing the content of a // vs. an might be useful, but for now we're going with the easy route. return false; } } else if (this instanceof Chars) { assert false; } // Odd edge-case du'jour: // // The modelling of some packets is controlled by UB rights, and there are some cases // where a packet may have been modelled in the current version but not in the rollback (and // presumably vice-versa). If the current is unmodelled it "just works" since XFAPacketImpl's // compareVersions() simply delegates to compareVersionsCanonically(). However, if current // is modelled but rollback is not, then we need to delegate here. // // See Watson 1918837. // if ( (this instanceof Packet) == false && (oRollbackNode instanceof Packet) == true) return ((Element) this).compareVersionsCanonically(oRollbackNode, this, oChangeLogger, oUserData); return true; } /** * Helper routine for compareVersions() * @exclude from published api. */ public static String getPropName(Element oNode, int eTag) { StringBuilder sPropName = new StringBuilder(); if (oNode != null && eTag != XFA.INVALID_ELEMENT) sPropName.append(oNode.getModel().getSchema().getAtom(eTag)); StringBuilder sNodeName = new StringBuilder(); while (oNode != null && oNode.isContainer() == false) { sNodeName.append(oNode.getClassAtom()); Element oParent = oNode.getXFAParent(); if (oParent != null && oParent.getChildReln(oNode.getClassTag()).getMax() > 1) { sNodeName.append('['); sNodeName.append(String.valueOf(oNode.getIndex(false))); sNodeName.append(']'); } if (StringUtils.isEmpty(sPropName)) { sPropName.append(sNodeName); } else { sPropName.insert(0, '.'); sPropName.insert(0, sNodeName); } // Clear the buffer. sNodeName.setLength(0); oNode = oNode.getXFAParent(); } return sPropName.toString(); } /** * Helper routine for compareVersions() * @exclude from published api. */ public String getPIName(Element oNode, String sPI) { String sPropName = getPropName(oNode, XFA.INVALID_ELEMENT); StringTokenizer st = new StringTokenizer(sPI); String sTarget = st.hasMoreTokens() == true ? st.nextToken() : ""; return sPropName + "."; } /** * Helper routine for compareVersions() * @exclude from published api. */ public static String getNodeAsXML(Node oNode) { DOMSaveOptions oOptions = new DOMSaveOptions(); // XFAPlugin Vantive bug#595482 Convert CR and extended characters to entity // references. Acrobat prefers these to raw utf8 byte data. oOptions.setDisplayFormat(DOMSaveOptions.RAW_OUTPUT); oOptions.setSaveTransient(true); oOptions.setEntityChars("\r"); oOptions.setRangeMin('\u007F'); oOptions.setRangeMax('\u00FF'); oOptions.setExcludePreamble(true); ByteArrayOutputStream oStream = new ByteArrayOutputStream(); if ( oNode instanceof Element ) { Element element = (Element)oNode; element.getModel().normalizeNameSpaces(element, ""); element.saveXML(oStream, oOptions); } else { oNode.getOwnerDocument().saveAs(oStream, oNode, oOptions); } return oStream.toString(); } /** * Helper routine for compareVersions() * @exclude from published api. */ public static String getPIAsXML(String sPI) { return ""; } /** * Helper routine for compareVersions() * @exclude from published api. */ public void logValueChangeHelper(Node oContainer, String sNewValue, Node.ChangeLogger oChangeLogger, Object oUserData) { boolean bFoundValue = false; boolean bFoundCaption = false; Node oNode = this; if (oNode.isSameClass(XFA.DATAGROUPTAG) || oNode.isSameClass(XFA.DATAVALUETAG)) bFoundValue = true; else { while (oNode != null && !oNode.isContainer()) { if (oNode.isSameClass(XFA.VALUETAG)) bFoundValue = true; else if (oNode.isSameClass(XFA.CAPTIONTAG)) bFoundCaption = true; oNode = oNode.getXFAParent(); } } if (bFoundValue && !bFoundCaption) { oChangeLogger.logValueChange(oContainer, sNewValue, oUserData); } else { // We found no node (or perhaps one under a caption), so we must have changed // the element content of a property (such as a