com.adobe.xfa.Node Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* 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 + "." + sTarget + " ... ?>";
}
/**
* 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 "" + sPI + "?>";
}
/**
* 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