com.adobe.xfa.Element 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.
*/
package com.adobe.xfa;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.Attributes;
import com.adobe.xfa.data.DataModel.AttributeWrapper;
import com.adobe.xfa.dom.DOM;
import com.adobe.xfa.dom.NamespaceContextImpl;
import com.adobe.xfa.service.canonicalize.Canonicalize;
import com.adobe.xfa.ut.Assertions;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.Key;
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.StringHolder;
import com.adobe.xfa.ut.StringUtils;
import com.adobe.xfa.ut.UuidFactory;
import com.adobe.xfa.ut.trace.Trace;
import com.adobe.xfa.ut.trace.TraceHandler;
import com.adobe.xfa.ut.trace.TraceTimer;
import com.adobe.xfa.ut.trace.TraceHandler.TimingType;
/**
* A class to represent the XFA elements in a DOM.
* Elements differ from nodes in that they all
* conform to an XFA schema. All the XML
* comments {@link Comment} and processing instructions
* {@link ProcessingInstruction} of a DOM,
* lie outside the XFA schemas.
*/
public class Element extends Node {
/**
* Interface that is implemented by classes that use a dual XFA/XML DOM.
* @exclude from published api.
*/
public interface DualDomNode {
Node getXmlPeer();
void setXmlPeer(Node peer);
}
/*
* This class did not exist in the XFA C++ code base. It's needed in
* the Java implementation since we need a class that can represent
* elements that don't have a corresponding XFA schema.
*/
/**
* @exclude from published api.
*/
protected static final int TEXT = 0x00;
/**
* @exclude from published api.
*/
protected static final int ATTRIBUTE = 0x01;
/**
* @exclude from published api.
*/
protected static final int ELEMENT = 0x02;
/**
* @exclude from published api.
*/
protected static final int ONEOF = 0x03;
/**
* @exclude from published api.
*/
protected static final int CHILD = 0x04;
/**
* @exclude from published api.
*/
protected static final int INVALID = 0x05;
// Attribute properties are bit flags
/**
* @exclude from published api.
*/
public static final int AttrIsDefault = 1;
/**
* @exclude from published api.
*/
public static final int AttrIsFragment = 2;
/**
* @exclude from published api.
*/
public static final int AttrIsTransient = 4;
private static final GenericAttribute gsEmptyStringAttr = new GenericAttribute("","");
/*
* Resolve actions
*/
//private static final int NOACTION = 0;
/**
* @exclude from public api.
*/
public static final int CREATEACTION = 1;
private static final int APPENDACTION = 2;
/**
* This instance is also used by EventManager.
* @exclude from published api.
*/
static final Trace oScriptTrace = new Trace("script", ResId.ScriptTraceHelp);
/**
* Specifies handling of existing node content when XML content is loaded.
* @see Element#loadXML(InputStream, boolean, ReplaceContent)
* @exclude from published api.
*/
public enum ReplaceContent {
/**
* Leave existing content untouched and append newly loaded result.
*/
None,
/**
* Remove any XFA node children and then add newly loaded result.
*/
XFAContent,
/**
* Remove all XFA and DOM children (e.g. PI children are DOM only) and
* then add newly loaded result.
*/
AllContent
}
static private void removeNamespaceDef(Element element, String aPrefix) {
//
// When debugging serialization, while using detail formatters that
// do serialization, it's possible to get a null SaveNameSpaceChecker,
// so guard against that.
//
SaveNameSpaceChecker checker = element.getOwnerDocument().getSaveChecker();
if (checker != null )
checker.removePrefix(element, aPrefix);
}
/*
* Adds extra namespace attribute to output if needed. This method differs
* from C++. In jfDom we had a base class jfNodeImpl that was common to both
* element and attribute. In Java these classes don't have a common
* ancestor. So I've made this method static and passed in extra parameters
* so that it can be used for both elements and attributes.
*/
@FindBugsSuppress(code="ES")
static private void addNamespaceDef(OutputStream outStream,
DOMSaveOptions options,
Element element,
String aPrefix,
String aNamespaceURI) {
aNamespaceURI = aNamespaceURI == null ? "" : aNamespaceURI;
// Note: before adding an attribute, make sure there isn't already
// an existing attribute declaring the ns prefix with a different
// value. Adding a second attr with same name will generate
// invalid XML. ref: Watson 1239029 and friends.
if (!findNamespace(element, aPrefix, aNamespaceURI, options, true) &&
!elementHasNamespacePrefixDeclared(element, aPrefix)) {
element.getOwnerDocument().getSaveChecker().addPrefix(element, aPrefix, aNamespaceURI);
try {
outStream.write(Document.MarkupSpace);
outStream.write(Document.MarkupXMLns);
if (aPrefix != "") {
outStream.write(Document.MarkupColon);
outStream.write(aPrefix.getBytes(Document.Encoding));
}
outStream.write(Document.MarkupAttrMiddle);
outStream.write(aNamespaceURI.getBytes(Document.Encoding));
outStream.write(Document.MarkupDQuoteString);
}
catch (IOException e) {
throw new ExFull(e);
}
}
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
private static boolean elementHasNamespacePrefixDeclared(Element element, String aPrefix) {
if (element == null)
return false;
// when the prefix is blank, the name to match will be
// the string "xmlns", as we will be looking at attribute nodes
String aFixedPrefix = aPrefix;
if (aFixedPrefix == "")
aFixedPrefix = STRS.XMLNS;
// look for prefix in current element's attributes
int nSize = element.getNumAttrs();
for (int nIndex = 0; nIndex < nSize; nIndex++) {
Attribute oAttr = element.getAttr(nIndex);
if (oAttr.isNameSpaceAttr() && oAttr.getLocalName() == aFixedPrefix) {
// this node already has a ns declaration for this prefix
//
// see note below re: Watson 1239029.
// If we get here it is probably an error condition for the
// document - however it's not very feasible to attempt
// correcting the error while generating markup. So we just
// avoid creating invalid XML (since this has very bad results,
// e.g., failure to save any data in Acrobat) by not writing
// a second attribute.
// Ideally it would be nice to report this state for dev
// purposes to allow attempts to avoid the condition.
return true;
}
}
return false;
}
/**
* Search up the hierarchy looking for a declaration of this namespace -- either as a namespace prefix
* or as a default namespace
* @param element The node that needs to have its namespace declared
* @param aPrefix The namespace prefix used. Empty string if looking for a default namespace
* @param aNamespaceURI The namespace we're looking for
* @return true if the declaration was found. false if not.
*/
@FindBugsSuppress(code="ES")
private static boolean findNamespace(Element element,
String aPrefix,
String aNamespaceURI,
DOMSaveOptions options,
boolean bSuppressEmptyNSCheck) {
// The xml prefix is always defined
if (aPrefix == STRS.LOWERXMLSTR)
return true;
if (aNamespaceURI == null)
aNamespaceURI = "";
// look up from this node up to the document node
// run from (usually the owner document), trying to find the prefix
SaveNameSpaceChecker checker = element.getOwnerDocument().getSaveChecker();
//
// When debugging serialization, while using detail formatters that
// do serialization, it's possible to get a null SaveNameSpaceChecker,
// so guard against that, below.
//
// bSuppressEmptyNSCheck is only set to TRUE when checking for redundant namespaces
// that is, when this method is called from jfAttrImpl::displayAttr
if (checker != null && checker.missingPrefix(element, aPrefix, aNamespaceURI, bSuppressEmptyNSCheck))
return true;
// when the prefix is blank, the name to match will be
// the string "xmlns", as we will be looking at attribute nodes
String aFixedPrefix = (aPrefix == "") ? STRS.XMLNS : aPrefix;
// We are loose about recognizing the xsi namespace variants
// JavaPort: Why is this different from C++?
boolean bFindXsi = isXsiNamespace(aNamespaceURI);
Node stopNode = (checker != null) ? checker.stopNode() : null;
while (element != null && element != stopNode) {
if (element instanceof Document)
return false;
// look for prefix in current element's attributes.
int nSize = element.getNumAttrs();
for (int nIndex = 0; nIndex < nSize; nIndex++) {
Attribute attr = element.getAttr(nIndex);
if (attr.isNameSpaceAttr() && attr.getLocalName() == aFixedPrefix) {
return bFindXsi ? isXsiNamespace(attr.getAttrValue()) : aNamespaceURI == attr.getAttrValue();
}
}
element = getXMLParent(element);
}
return false;
}
@FindBugsSuppress(code="ES")
private static boolean isXsiNamespace(String aNamespaceURI) {
return aNamespaceURI == STRS.XSINS ||
(aNamespaceURI.startsWith(STRS.XSINSPREFIX) && aNamespaceURI.endsWith(STRS.XSINSSUFFIX));
}
/** @exclude from published api */
static Element getXMLParent(Node node) {
if (node instanceof DualDomNode)
node = ((DualDomNode)node).getXmlPeer();
if (node == null)
return null;
Element element = node.getXMLParent();
// Ensure that we are on the XML DOM side
if (element instanceof DualDomNode)
element = (Element)((DualDomNode)element).getXmlPeer();
return element;
}
/**
* Determines whether to suppress duplicate namespace attributes.
* @param a a namespace declaration attribute
* @return true
if the namespace declaration should be serialized
*/
@FindBugsSuppress(code="ES")
private static boolean displayNamespace(Attribute a, Element parent, DOMSaveOptions options, boolean bSuppressEmptyNSCheck) {
String aPrefix = a.getLocalName();
if (aPrefix == STRS.XMLNS)
aPrefix = "";
parent = getXMLParent(parent);
if (parent == null)
return true;
return !findNamespace(parent, aPrefix, a.getAttrValue(), options, bSuppressEmptyNSCheck);
}
/**
* validate a node schema against a specific target version
* @param node the node to validate
* @param nodeSchema the corresponding node schema
* @param nTargetVersion the target version
* @param nTargetAvailability the target availability
* @param validationInfos an array of validation failures. May be null
* @return boolean true if valid
* @exclude
*/
static boolean validateNodeSchema(Node node, NodeSchema nodeSchema,
int nTargetVersion, int nTargetAvailability,
List validationInfos /* = null */) {
int eTag = node.getClassTag();
ChildRelnInfo childRelnInfo = nodeSchema.getChildRelnInfo(eTag);
// only validate children we know about
if (childRelnInfo != null) {
boolean bInvalid = false;
int nVerIntro = childRelnInfo.getVersionIntroduced();
int nAvail = childRelnInfo.getAvailability();
Model model = node.getModel();
// JavaPort: We don't store the model for text nodes, to try again to
// get the model from our parent...
if (model == null)
model = node.getXFAParent().getModel();
assert (model != null);
// deprecation is not an error
if (!model.isVersionCompatible(nVerIntro, nTargetVersion))
bInvalid = true;
else if ((nTargetAvailability & nAvail) == 0)
bInvalid = true;
if (bInvalid) {
// add this
if (validationInfos != null) {
NodeValidationInfo nodeValidationInfo = new NodeValidationInfo(nVerIntro, nAvail, node);
validationInfos.add(nodeValidationInfo);
}
return false;
}
}
return true;
}
/**
* cached name -- normally represents the value of the name attribute in the
* XFA Template Schema.
*
* @exclude from published api.
*/
protected String maName; // interned
/**
* Since attributes are immutable, we need to store their volatile properties in their Element.
*/
private byte[] mAttrProperties;
private Attribute[] mAttrs;
private int nAttrs;
private boolean mbInhibitPrettyPrint;
private boolean mbIsFragment;
private boolean mbIsHidden; // default to false. Used in single dom models to hide things like config passwords.
private boolean mbIsIndexed; // default to false. Set to true by the mpMapImpl object when the node is indexed
private boolean mbSaveXMLSaveTransient;
private boolean mbTransparent; // default to false;
private EventManager.EventTable mEventTable;
/**
* @exclude from published api.
*/
protected Node mFirstXMLChild;
private String mLocalName; // interned
private Model mModel;
private int mnLineNumber;
private NodeSchema mNodeSchema;
private String mQName; // interned
/**
* Data window subtrees are not connected to their parent
* document's root element. This flag lets us know that
* they should still be considered part of the document.
*/
private boolean mbIsDataWindowRoot;
/**
* @exclude
*/
protected String mURI; // interned
private static XPathFactory mXPathFactory;
/**
* Instantiates an element node.
*/
protected Element() {
super(null, null);
}
/**
* Instantiates an element node.
* @param parent the element's parent, if any.
* @param prevSibling the element's previous sibling, if any.
*/
protected Element(Element parent, Node prevSibling) {
super(parent, prevSibling);
if (parent != null) {
setModel(parent.getModel());
setDocument(parent.getOwnerDocument());
}
}
/**
* Instantiates an element node with the given properties.
* @param parent the element's parent, if any.
* @param prevSibling the element's previous sibling, if any.
* @param uri the element's namespace URI.
* @param name the element's name.
*/
protected Element(Element parent, Node prevSibling,
String uri, String name) {
this(parent, prevSibling);
if (uri == null && parent != null)
uri = parent.mURI;
String localName = null;
if (name != null) {
name = name.intern();
int nsSplit = name.indexOf (':');
if (nsSplit < 0) {
localName = name;
} else {
localName = name.substring (nsSplit + 1);
localName = localName.intern();
}
}
uri = uri != null ? uri.intern() : null;
setClass( name, XFA.INVALID_ELEMENT );
mURI = uri;
mQName = name;
mLocalName = localName;
}
/**
* Instantiates an element node with the given properties.
* All name properties (including those in attributes) must be interned strings.
* @param parent the element's parent, if any.
* @param prevSibling the element's previous sibling, if any.
* @param uri the element's namespace URI.
* @param localName the element's local name.
* @param qName the element's qualified name.
* @param attributes the element's list of XML attributes.
* @param classTag the element's class tag.
* @param className the element's class name.
*
* @exclude from published api.
*/
public Element(Element parent, Node prevSibling,
String uri, String localName, String qName,
Attributes attributes,
int classTag, String className) {
this(parent, prevSibling);
if (uri == null && parent != null)
uri = parent.mURI;
if (uri != null) {
uri = uri.intern();
}
mURI = uri;
mLocalName = localName;
mQName = qName;
setClass( className, classTag );
if (attributes != null)
assignAttrs(attributes);
}
/**
* @exclude from published api.
*/
public void appendChild(Node child) {
if (child.getXMLParent() != null)
child.remove();
//
// Find the last child...
//
Node lastChild = getFirstXMLChild();
Node currentChild = lastChild;
while (currentChild != null) {
currentChild = currentChild.getNextXMLSibling();
if (currentChild != null)
lastChild = currentChild;
}
//
// Add either as the next sibling to our last child or
// as our first child.
//
if (lastChild == null) {
setFirstChild(child);
}
else {
lastChild.setNextXMLSibling(child);
}
child.setXMLParent(this);
setChildListModified(true);
if (child instanceof Element) {
((Element) child).setNS(getNS());
// Check to see if the new node is coming from a different model.
// If so, then we need to update its model reference, as well as
// all of its children nodes.
if (child.getModel() != getModel() ||
child.getOwnerDocument() != getOwnerDocument())
updateModelAndDocument(child);
}
if (this instanceof DualDomNode && child instanceof DualDomNode) {
Element dualDomParent = (Element)((DualDomNode)this).getXmlPeer();
Node domPeer = ((DualDomNode)child).getXmlPeer();
if (domPeer.getModel() != getModel() ||
domPeer.getOwnerDocument() != getOwnerDocument())
updateModelAndDocument(domPeer);
if (domPeer instanceof AttributeWrapper) {
AttributeWrapper wrapper = (AttributeWrapper)domPeer;
Attribute attr = dualDomParent.setAttribute(wrapper.getNS(), wrapper.getXMLName(), wrapper.getLocalName(), wrapper.getValue(), false);
AttributeWrapper xmlPeer = new AttributeWrapper(attr, dualDomParent);
xmlPeer.setXfaPeer((Element)child);
((DualDomNode)child).setXmlPeer(xmlPeer);
}
else {
dualDomParent.appendChild(domPeer);
}
}
if (child instanceof Element) {
if (getOwnerDocument() != null)
getOwnerDocument().indexSubtree((Element)child, false);
}
setDirty();
// if (bNotify) {
// The C++ code base adds a bNotify parameter to this method.
// It appears this is just so that we can avoid these notifications during
// the DOM construction. Since our code doesn't use this method during
// DOM construction, we'll try to avoid adding the parameter for now.
// We assume we always notify
// notify child's peers that the parent has changed
if (!child.isMute())
child.notifyPeers(Peer.PARENT_CHANGED, getClassAtom(), this);
// notify the peers that a child was added
if (!isMute())
notifyPeers(Peer.CHILD_ADDED, child.getClassAtom(), child);
// }
}
/**
* Appends the given child to this element.
* @param child the child node being appended.
* @param bValidate when true, ensures the given child is valid
* per the model's schema and throws an {@link ExFull} if not.
*/
public void appendChild(Node child, boolean bValidate /* = true */) {
if (bValidate) {
// just make sure that oNewChild is a valid child
isValidChild(child.getClassTag(), ResId.InvalidChildAppendException, true, false);
}
if (child == this)
throw new ExFull(new MsgFormat(ResId.HierarchyRequestException, child.getName()));
boolean bIsDefault = child.isDefault(false);
// don't notify of any changes getProperty makes, only do it when a default property is changed
// Mute also if dom peer is null - (watson 1824495 - crash due to notification with no dom peer)
if (bIsDefault)
mute();
try {
if (!child.isDefault(true))
makeNonDefault(false);
// remove the node from its parent first
if (child.getXMLParent() != null)
child.remove();
appendChild(child);
}
finally {
// restore peer updates
if (bIsDefault)
unMute();
}
// setDirty(); // appendChild(Node) will dirty
}
/**
* @exclude from published api.
*/
public final void appendPI(String aPiName, String sData) {
assert (aPiName != null);
new ProcessingInstruction(this, null, aPiName, sData);
setDirty();
}
/**
* @exclude from published api.
*/
public final void appendPI(String aPiName, String sPropName, String sData) {
assert (aPiName != null);
// Add propName to the data
String sTemp = sPropName + " " + sData;
new ProcessingInstruction(this, null, aPiName, sTemp);
// UNDO(XFAInsertDomNodeUndo, (this, oPI));
setDirty();
}
/**
* Apply an XSL tranformation to the XML representation of the current node.
* This is equivalent to calling saveXML()
and transforming
* the result with the specified XSL document.
* @param sXSL
* input XSL document
* @param sOutStream
* output stream
*
* @exclude from published api.
*/
final void applyXSL(InputStream xsl, OutputStream out) {
//
// First save this node to an XML document, contained in oMemStream.
//
ByteArrayOutputStream tempStream = new ByteArrayOutputStream();
saveXML(tempStream, null);
ByteArrayInputStream oMemStream = new ByteArrayInputStream(tempStream.toByteArray());
//
// Apply the xsl transformation.
//
XSLTranslator translator = new XSLTranslator(xsl);
translator.process(oMemStream, out);
}
/**
* Assign attributes to an XML DOM node.
* All name properties in attributes must be interned strings.
* This method will only be used to assign attributes during a parse, so it doesn't
* do ID index maintenance (which is done only after the document is parsed).
* @exclude from published api.
*/
private final void assignAttrs(Attributes attributes) {
final int newAttributeCount = attributes.getLength();
if (newAttributeCount == 0)
return;
Model m = getModel();
NodeSchema schema = getNodeSchema();
ensureAttributeCapacity(nAttrs + newAttributeCount);
int namespaceDeclarationCount = nAttrs;
for (int i = 0; i < newAttributeCount; i++) {
Attribute attribute = createAttribute(attributes.getLocalName(i), attributes.getURI(i), attributes.getQName(i), attributes.getValue(i), schema);
if (m != null)
m.saveProtoInformation(this, attribute.getLocalName(), i == 0);
int position = nAttrs;
// The C++ version that uses the expat parser treats xmlns
// declarations differently.
// The expat parser doesn't return them as attributes.
// Consequently the jfDom code inserts them -- and they always
// show up before normal attributes.
// So to be consistent, re-position the attributes.
if (attribute.isNameSpaceAttr()) {
if (i != 0) {
System.arraycopy(mAttrs, namespaceDeclarationCount, mAttrs, namespaceDeclarationCount + 1, nAttrs - namespaceDeclarationCount);
System.arraycopy(mAttrProperties, namespaceDeclarationCount, mAttrProperties, namespaceDeclarationCount + 1, nAttrs - namespaceDeclarationCount);
position = namespaceDeclarationCount;
}
namespaceDeclarationCount++;
}
mAttrs[position] = attribute;
mAttrProperties[position] = 0;
nAttrs++;
}
// setDirty(); Doesn't make sense during parse
}
/**
* Assigns the value given to the node located by the given
* SOM (Scripting Object Model) expression and interpreted relative
* to this element's context.
*
* If the node doesn't exist,
* it can be created depending of the value of the given eMode.
* The eMode values are those identified in
* {@link Node#assignNode(String, String, int) Node.assignNode()}.
*
* @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
* the node identified by the SOM expression.
*/
public final Node assignNode(String sSOMExpression,
String sValue, int eMode) {
Node node = null;
switch (eMode) {
case CREATE_REPLACE:
{
SOMParser.SomResultInfo result
= resolveNodeCreate(sSOMExpression, CREATEACTION,
true, false, false);
node = (Node) result.object;
if (node == null)
throw new ExFull(
new MsgFormat(ResId.HierarchyRequestException,
sSOMExpression));
//
// Assume it's a string property since we really have no idea.
//
Arg arg = new Arg();
arg.setString(sValue);
//
// If propertyName is not empty, then it's a reference
// to a property as opposed to a node.
//
if (!StringUtils.isEmpty(result.propertyName))
node.setScriptProperty(result.propertyName, arg, false);
else
node.setScriptProperty(XFA.VALUE, arg, false);
}
break;
case CREATE_ALWAYS_NEW: {
SOMParser.SomResultInfo result
= resolveNodeCreate(sSOMExpression, APPENDACTION,
true, false, false);
node = (Node) result.object;
if (node == null)
throw new ExFull(
new MsgFormat(ResId.HierarchyRequestException,
sSOMExpression));
//
// Assume it's a string property since we really have no idea.
//
Arg arg = new Arg();
arg.setString(sValue);
//
// If propertyName is not empty, then it's a reference
// to a property as opposed to a node.
//
if (!StringUtils.isEmpty(result.propertyName))
node.setScriptProperty(result.propertyName, arg, false);
else
node.setScriptProperty(XFA.VALUE, arg, false);
}
break;
case CREATE_MUST_NOT_EXIST:
case CREATE_IF_NOT_EXIST: {
List result = new ArrayList();
SOMParser oParser = new SOMParser(null);
oParser.setOptions(true, true, false);
oParser.resolve(this, sSOMExpression, result);
if (result.size() != 0) {
if (eMode == CREATE_MUST_NOT_EXIST)
throw new ExFull(
new MsgFormat(ResId.NodeAlreadyExistException,
sSOMExpression));
}
else {
node = assignNode(sSOMExpression, sValue, CREATE_REPLACE);
}
}
break;
}
setDirty();
return null;
}
/**
* @exclude from published api.
*/
protected void childRemoved(Node child) {
if (!child.isDefault(true))
makeNonDefault(false);
}
/**
* @exclude from published api.
*/
public Node clone(Element parent) {
return clone(parent, true);
}
/**
* @exclude from published api.
*/
public Element clone(Element parent, boolean deep) {
return cloneHelper(parent, deep, null, null);
}
private Element cloneHelper(Element parent, boolean deep, NodeList ancestors, NodeList leafs) {
// both must not be null
if (ancestors != null && leafs != null) {
boolean bContinue = false;
// if leaf node, clone the entire sub tree. todo this pass null pAncestors and pLeafs to the next
// call to clone
int nLeafs = leafs.length();
int i = 0;
for (i = 0; i < nLeafs; i++) {
Element leaf = (Element)leafs.item(i);
if (this == leaf) {
leafs.remove(this);
return clone(parent, true);
}
}
int nAncestors = ancestors.length();
for (i = 0; i < nAncestors; i++) {
Element poAncestors = (Element)ancestors.item(i);
if (this == poAncestors) {
ancestors.remove(this); // remove this from pAncestors
bContinue = true;
break;
}
}
// this node is not in either list so don't copy it
if (!bContinue)
return null;
}
Node srcPeer;
Node newPeer;
Element newNode;
if (getModel() == null || getClassTag() == XFA.INVALID_ELEMENT) {
assert !(this instanceof DualDomNode);
srcPeer = this;
// create a generic XFANode
newNode = new Element(parent, null, getNS(), getLocalName(), getXMLName(), null, getClassTag(), getClassName());
copyContent(newNode, false);
newPeer = newNode;
}
else {
Model model = parent != null ? parent.getModel() : getModel();
newNode = getModel().getSchema().getInstance(getClassTag(), model, parent, null, true);
// Watson 2412987. When created from the schema, models have a NULL getAppModelImpl value,
// while non-model nodes inherit from their model. To work around this, set it explicitly
// for models.
if (newNode instanceof Model) {
Model newModel = (Model) newNode;
if (newModel.getAppModel() == null)
newModel.setAppModel(model.getAppModel());
}
if (this instanceof DualDomNode) {
// If a peer was created implicitly, remove it
newPeer = ((DualDomNode)newNode).getXmlPeer();
if (newPeer != null)
newPeer.remove();
srcPeer = ((DualDomNode)this).getXmlPeer();
newPeer = importDomNode(parent, this, false);
newPeer.setXfaPeer(newNode);
((DualDomNode)newNode).setXmlPeer(newPeer);
}
else {
newNode.setDOMProperties(mURI, getLocalName(), getXMLName(), null);
srcPeer = this;
copyContent(newNode, false);
newPeer = newNode;
}
}
if (deep) {
// Copy all the PIs and comments.
// Note: someday this will get us into trouble, as we're effectively moving
// all the PIs and comments to the front...
for (Node srcDomChild = srcPeer.getFirstXMLChild(); srcDomChild != null; srcDomChild = srcDomChild.getNextXMLSibling()) {
if (srcDomChild instanceof ProcessingInstruction || srcDomChild instanceof Comment) {
Node newDomChild = newNode.getOwnerDocument().importNode(srcDomChild, true);
((Element)newPeer).appendChild(newDomChild);
}
}
// JavaPort: use XML APIs here (not XFA) since we need to handle cloning of
// children of #xHTML and #xml that are not XFA nodes, but live on the XFA side
// because the model is single-DOM.
for (Node srcChild = getFirstXMLChild(); srcChild != null; srcChild = srcChild.getNextXMLSibling()) {
if (srcChild instanceof ProcessingInstruction || srcChild instanceof Comment)
continue;
if (srcChild instanceof Element && ancestors != null && leafs != null)
((Element)srcChild).cloneHelper(newNode, true, ancestors, leafs);
else
srcChild.clone(newNode);
}
}
if (getLocked())
newNode.setLocked(true);
return newNode;
}
/**
* @exclude from published api.
*/
protected static Node importDomNode(Element parent, Node node, boolean bXMLDeep) {
Node retDomNode;
Node domPeer = ((DualDomNode)node).getXmlPeer();
if (parent != null) {
Element parentPeer = (Element)((DualDomNode)parent).getXmlPeer();
if (domPeer instanceof AttributeWrapper) {
AttributeWrapper wrapper = (AttributeWrapper)domPeer;
Attribute attr = parentPeer.setAttribute(wrapper.getNS(), wrapper.getLocalName(), wrapper.getXMLName(), wrapper.getValue(), false);
retDomNode = new AttributeWrapper(attr, parentPeer);
}
else {
retDomNode = parentPeer.getOwnerDocument().importNode(domPeer, bXMLDeep);
parentPeer.appendChild(retDomNode);
}
}
else {
if (domPeer instanceof AttributeWrapper) {
AttributeWrapper wrapper = (AttributeWrapper)domPeer;
Attribute attr = new StringAttr(wrapper.getNS(), wrapper.getLocalName(), wrapper.getXMLName(), wrapper.getValue(), false);
retDomNode = new AttributeWrapper(attr, null);
}
else {
retDomNode = domPeer.getOwnerDocument().importNode(domPeer, bXMLDeep);
}
}
return retDomNode;
}
/**
* @exclude from published api.
*/
public void copyContent(Element newNode, boolean deep) {
// Unlike some other flags, the fragment flag is cloned. Otherwise
// an external fragment gets expanded into a real (non-fragment)
// object when copied.
newNode.isFragment(isFragment(),false);
// Copy attributes - note that cloning has special dispensation to
// copy attributes without doing checks for duplicate IDs. Any duplicate
// IDs will be uniquified when the new node is indexed.
// The Attributes themselves don't need to be copied since they are immutable.
if (nAttrs != 0) {
newNode.nAttrs = nAttrs;
newNode.mAttrs = mAttrs.clone();
newNode.mAttrProperties = mAttrProperties.clone();
int nAttrs = getNumAttrs();
for (int i = 0; i < nAttrs; i++)
newNode.mAttrProperties[i] &= AttrIsFragment;
}
Node child = getFirstXMLChild();
if (deep && child != null) {
// Copy all the PIs and comments, which have no peers in the XFA DOM
// Note: someday this will get us into trouble, as we're effectively
// moving all the PIs and comments to the front...
while (child != null) {
if (child instanceof Element) {
((Element) child).clone(newNode, true);
}
else {
child.clone(newNode);
}
child = child.getNextXMLSibling();
}
}
newNode.maName = maName;
if (getLocked())
newNode.setLocked(true);
}
/**
* Create a new attribute -- try to make it fit with our schema.
* @param localName This String must be interned.
* @param NS This String must be interned.
* @param qName This String must be interned.
* @param value
* @param schema
* @return the new attribute
*/
@FindBugsSuppress(code="ES")
private final Attribute createAttribute(String localName, String NS,
String qName, String value,
NodeSchema schema) {
if (Assertions.isEnabled) assert localName == localName.intern();
if (Assertions.isEnabled) assert NS == null || NS == NS.intern();
if (Assertions.isEnabled) assert qName == qName.intern();
int eTag = XFA.INVALID_ELEMENT;
boolean bXFAAttr = localName == qName;
Model m = getModel();
if (!bXFAAttr) {
// When mQName and mName are not the same, that's an indication
// that the attribute may be living in a different namespace
// isCompatibleNS is relatively expensive, so we try to avoid
// calling it.
if (m != null && m.isCompatibleNS(NS))
bXFAAttr = true;
if (localName.equals(XFA.RID))
bXFAAttr = true;
}
Attribute defaultAttribute = null;
boolean bValidAttr = true;
if (bXFAAttr) {
eTag = XFA.getAttributeTag(localName);
if (eTag != XFA.INVALID_ELEMENT) {
AttributeInfo info = schema.getAttributeInfo(eTag);
if (info != null) {
defaultAttribute = info.getDefault();
bValidAttr = isValidAttr(eTag, true, null);
}
}
}
if (defaultAttribute == null)
defaultAttribute = gsEmptyStringAttr;
value = internAttributeValue(defaultAttribute, value);
Attribute a = null;
try {
a = defaultAttribute.newAttribute(NS, localName, qName, value, false);
} catch (ExFull ex) {
if (ex.hasResId(ResId.InvalidPropertyValueException)
|| ex.hasResId(ResId.InvalidEnumeratedValue)) {
m.addXMLLoadErrorContext(getLineNumber(),
getOwnerDocument().getParseFileName(), ex);
m.addErrorList(ex, LogMessage.MSG_WARNING, this);
a = defaultAttribute;
}
else {
a = gsEmptyStringAttr.newAttribute(NS, localName, qName, value, false);
}
}
if (bXFAAttr) {
if (a.getLocalName() == XFA.NAME) {
// We know that the name value has been interned
maName = a.getAttrValue();
}
}
if (eTag != XFA.INVALID_ELEMENT && bValidAttr)
return a;
// Stick around for error reporting...
if (eTag == XFA.INVALID_ELEMENT) {
// One more check before we really decide it's an error...
if (qName == STRS.XMLNS || qName.startsWith(STRS.XMLPREFIX))
return a;
else if (qName == STRS.XLIFFNS || qName.startsWith(STRS.XLIFFPREFIX))
return a;
}
if (bXFAAttr && getClassTag() != XFA.INVALID_ELEMENT && m != null
&& (! bValidAttr || ! isValidAttr(eTag, false, null))) {
String sLine = Integer.toString(getLineNumber());
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidAttributeLoadException);
message.format(localName);
message.format(getLocalName());
message.format(sLine);
m.addErrorList(new ExFull(message), LogMessage.MSG_WARNING, this);
}
return a;
}
/**
* Values are interned because it is required of certain attributes or
* attribute types, as well as an optimization that saves space (which in
* turn improves performance).
*
* When Attribute.newAttribute(..., false) is called, for the special cases
* of namespace attributes and EnumValue, the attribute implementation
* can assume that the value has been interned. This is a bit of a break
* in encapsulation, but the performance improvement by using the optimized
* implementation provided by Model.intern() at this level justifies this.
*
* When interning to save space, avoid interning long strings since
* they tend not to repeat so much, and they will just fill up the cache.
* As a special case, we always need to intern the values for Enum, and
* we prefer to do it here where we can intern more efficiently than
* in the EnumValue.newAttribute().
*
* Note that the Attribute constructor also ensures that the value of
* name and namespace attributes are interned. This is because we have to
* test for these attributes based on the localName/qName, which isn't
* reliable until the Attribute has been constructed.
*
* @param defaultAttribute the Attribute that will be used as a template to create
* the new Attribute.
* @param value the value of the new attribute.
* @return the (possibly) interned value.
*/
private String internAttributeValue(Attribute defaultAttribute, String value) {
final int maxLengthToIntern = 9;
if (value.length() <= maxLengthToIntern ||
defaultAttribute instanceof EnumValue) {
Model m = getModel();
value = m != null ? m.intern(value) : value.intern();
}
return value;
}
/**
* @exclude from published api.
*/
public Attribute defaultAttribute(int eTag) {
// Default nodes don't have a schema, so can't return a default
// attribute.
int thisTag = getElementClass();
if (thisTag == XFA.INVALID_ELEMENT || thisTag == XFA.DSIGDATATAG)
return gsEmptyStringAttr;
return getModel().getSchema().defaultAttribute(eTag, thisTag);
}
/**
* Returns the class tag of the default one-of child (if there is one)
* @return the integer tag for the default element.
*
* @exclude from published api.
*/
public int defaultElement() {
//
// if the name is empty, we'll assume the most common default which is
// an XFATextNode.
//
if (isValidElement(XFA.TEXTNODETAG, false))
return XFA.TEXTNODETAG;
return XFA.SCHEMA_DEFAULTTAG;
}
/**
*
* @param eTag The tag of the attribute we want
* @param nOccurrence The zero-based occurrence of the attribute (usually zero)
* @return the default value for this attribute in the context of this node.
* @exclude
*/
public Node defaultElement(int eTag, int nOccurrence) {
// Mark the dom document as loading to ensure that the nodes
// are not marked as dirty. If the transient flag is turned off
// mark the nodes as dirty then. See makeNonDefaultProperty
final boolean previousWillDirty = getWillDirty();
setWillDirty(false);
try {
// Original comment for Watson 1220444 (the fix for which caused Watson 1729702):
// Special handling for Border/Edge/Corner: when border/edge/corner node is created by getElement(), an undo object
// should be created. If not, undo operation will fail to remove border/edge/corner node which results in unexpected
// rendering result.
// Note: It doesn't seem to be ideal to put special handling code in a generic method. XFABorderImpl::defaultElementImpl()
// is only good for handling corner and edge. Is there any better place to put this code?
// Addendum for Watson 1729702. Change the above fix in two ways:
// 1) Use the new non-destructive undo, which means no action is taken if the stack is in a
// rolled-back state (i.e. can redo).
// 2) Use XFAXMLUndo rather than XFAInsertUndo since it stores the entire contents of this node,
// and hence behaves better in the absence of undo information caused by #1. XFAXMLUndo
// goes before the operation, to take a snapshot.
// if (eTag == XFA::BORDERTAG || eTag == XFA::CORNERTAG || eTag == XFA::EDGETAG)
// UNDO_ND(XFAXMLUndo, (this));
// get the default
Node ret = defaultElementImpl(eTag, nOccurrence, true);
// mark the node as transient
if (ret != null) {
if (isTransient())
ret.isTransient(true, true);
else if (ret.isTransient())
// Javaport TODO: Nodes created by defaultElementImpl() are
// set to transient (why?), but if the parent is non-transient
// the created node should also be non-transient.
// Probably would make more sense to change defaultElementImpl()
// but had issues getting that to work properly.
ret.isTransient(false, false);
if (isFragment()) {
if (ret instanceof Element)
((Element)ret).isFragment(true, true);
else if (ret instanceof TextNode)
((TextNode)ret).isFragment(true);
}
ret.makeDefault();
}
return ret;
}
finally {
setWillDirty(previousWillDirty);
}
}
/**
* creates an orphaned default element with the parent pointer set to this.
*/
Node createDefaultElement(int eTag, int nOccurrence) {
// don't set the parent because you will get an assert when the node is freed.
// if the call wants to walk up the tree they will have to temporary set the parent.
Node ret = defaultElementImpl(eTag, nOccurrence, false);
// Fix for watson bug 1678902, when returning a default node
// make sure it is marked so as to ensure property default values are returned
ret.makeDefault();
return ret;
}
/**
* Appends the element to the parent (The helper is virtual).
* @param bAppend if true add the default to this node as a child, otherwise it will be an orphaned node.
* @exclude from published api.
*/
protected Node defaultElementImpl(int eTag, int nOccurrence, boolean bAppend /* = true */) {
if (eTag == XFA.SCHEMA_DEFAULTTAG)
eTag = defaultElement();
if (eTag == XFA.SCHEMA_DEFAULTTAG)
return null;
Element parent = bAppend ? this : null;
// JavaPort: In C++, createElement() can create text nodes, but in Java,
// a TextNode is not an Element, so we have to special case it here.
Node node;
if (eTag == XFA.TEXTNODETAG)
node = getModel().createTextNode(parent, null, "");
else
//Creating a default, do not perform version checking. This will be postponed until (if) makeNonDefault() is called.
node = getModel().createNode(eTag, parent, "", mURI, false);
node.isTransient(true, false); // JavaPort: Why is this different from C++?
return node;
}
/**
* @exclude from published api.
*/
public final String establishID() {
String sID = getID();
if (!StringUtils.isEmpty(sID))
return sID; // There is already a nice one
// We need to create an ID; try using the name to be human-reader-friendly
sID = getName();
if (StringUtils.isEmpty(sID))
sID = getClassAtom();
sID += "_ID";
Document doc = getOwnerDocument();
// We're going to generate a template-namespace-specific ID, so it's
// tempting to only guarantee uniqueness within the template by using
// the namespace-aware version of getElementId(). However, we may someday
// want to validate an entire XDP, so we're better off making sure
// we're overly unique.
//
String aID = sID;
if (doc.idValueInUse(aID)) {
// Well, it was nice to try and generate a nice friendly one, but
// it's already in use. There's no sense messing about at this
// point -- instead we'll generate something we "know" to be unique.
sID = UuidFactory.getUuid();
}
setID(sID);
return sID;
}
/**
* Evaluates a fragment of script. This function is analogous to the
* built-in scripting capability wherein script fragments can be contained
* in "xfe:script" and "xfe:contentType" attributes in a file.
*
* @param sEvalText - the text to execute.
* @param sEvalTypeText - the language name. This can be the name of a script
* language for which the application has installed a handler, such as "formcalc"
* or "javascript", or an empty string. An empty string is equivalent to "formcalc".
* Note that the appropriate language handler must be installed by the application.
* @return The return value of the script.
* @exclude from public api.
*/
public final Arg evaluate(
String sEvalText,
String sEvalTypeText /* = "" */,
int executeReason /* = ScriptHandler.UNSPECIFIED */,
boolean bReportNonFatalErrors /* = true */ ) {
boolean bTrace = oScriptTrace.isEnabled(2);
Arg returnCode = new Arg();
if (StringUtils.isEmpty(sEvalTypeText))
sEvalTypeText = "formcalc"; // default to formcalc if not specified
else if (sEvalTypeText.startsWith("application/x-")) {
// Remove application/x-
sEvalTypeText = sEvalTypeText.substring("application/x-".length());
}
Model model = getModel();
ScriptHandler handler = model.getScriptHandler(sEvalTypeText);
if (handler == null) {
MsgFormatPos msg = new MsgFormatPos(ResId.UnknownScriptLanguageException);
msg.format(sEvalTypeText); // script type eg. formcalc
msg.format(getSOMExpression()); // context
ExFull err = new ExFull(msg);
getModel().addErrorList(err, LogMessage.MSG_WARNING, this);
}
else {
AppModel appModel = getAppModel();
// Retrieve the current SOM context node so that we can restore it.
Node prevContext = appModel.getContext();
try {
// Set the AppModel's current SOM context node to this node.
appModel.setContext(this);
// Adobe patent application tracking # B136, entitled "Applying locale behaviors to regions of a form", inventors: Gavin McKenzie, Mike Tardif, John Brinkman"
String sLocale = getInstalledLocale();
long before = 0;
if (bTrace || TraceHandler.scriptLoggingEnabled()) {
MsgFormatPos msg = new MsgFormatPos(ResId.ScriptTraceExecute);
msg.format(sEvalTypeText); // script type eg. formcalc
msg.format(sEvalText); // script contents
msg.format(getSOMExpression()); // context
msg.format(ScriptHandler.executeReasonToString(executeReason)); // reason
TraceHandler.reportScriptInfo(msg.toString());
if (bTrace)
oScriptTrace.trace(2, msg);
before = System.currentTimeMillis();
}
TraceTimer scriptOnlyTimer = new TraceTimer(TimingType.XFA_SCRIPTS_ONLY_TIMING);
try {
handler.execute(sEvalText, sLocale, returnCode, executeReason);
}
finally {
scriptOnlyTimer.stopTiming();
}
// Theoretically the script could increase the tracing level,
// so don't assume before has been set.
if ((bTrace || TraceHandler.scriptLoggingEnabled()) && before != 0) {
long after = System.currentTimeMillis();
MsgFormatPos msg1 = new MsgFormatPos(ResId.ScriptTraceReturnValue);
msg1.format(returnCode.getAsString(false)); // return value of script
//determine the execution time of the script in milliseconds
String sMSecs = Long.toString(after - before);
MsgFormatPos msg2 = new MsgFormatPos(ResId.ScriptTraceTime);
msg2.format(sMSecs);
TraceHandler.reportScriptInfo(msg1.toString());
TraceHandler.reportScriptInfo(msg2.toString());
TraceHandler.reportScriptInfo("\n");
if (bTrace) {
oScriptTrace.trace(2, msg1);
oScriptTrace.trace(2, msg2);
}
}
}
catch (ExFull ex) {
// Don't swallow internal errors!
if (ex.getResId(0) == ResId.SOFTWARE_FAILURE)
throw ex;
if (bReportNonFatalErrors || handler.wasFatalError()) {
MsgFormatPos msg = new MsgFormatPos(ResId.ScriptFailure);
msg.format(sEvalTypeText); // script type eg. formcalc
msg.format(getSOMExpression()); // content
msg.format(sEvalText);
ExFull err = new ExFull(msg);
err.insert(ex, true);
model.addErrorList(err, LogMessage.MSG_WARNING, this);
}
returnCode.setException(ex);
}
catch (java.lang.OutOfMemoryError ex) {
MsgFormatPos msg = new MsgFormatPos(ResId.ScriptFailure);
msg.format(sEvalTypeText); // script type eg. formcalc
msg.format(getSOMExpression()); // content
msg.format(sEvalText); // script contents
msg.format(ex.toString());
ExFull err = new ExFull(msg);
throw err;
}
finally {
// Restore the SOM context node to what it was before.
appModel.setContext(prevContext);
}
}
return returnCode;
}
/**
* Extend our array of attributes.
* @param newAttr the new attribute to add.
*/
private void extendAttributes(Attribute newAttr) {
ensureAttributeCapacity(nAttrs + 1);
mAttrs[nAttrs] = newAttr;
nAttrs++;
Element e = getXmlPeerElement();
if (getOwnerDocument() != null &&
getOwnerDocument().isId(e.getNSInternal(), e.getLocalName(), newAttr.getNS(), newAttr.getLocalName())) {
// verify that no other ID shares the same value
if (getOwnerDocument().idValueInUse(newAttr.getAttrValue()))
throw new ExFull(ResId.DOM_DUPLICATE_ID_ERR);
getOwnerDocument().indexNode(this, false);
}
}
private void ensureAttributeCapacity(int capacity) {
if (capacity == 0) return;
if (mAttrs != null) {
if (capacity <= mAttrs.length) return;
// Grow the existing capacity
capacity = (capacity * 3) / 2 + 1;
}
// else there are no attributes yet, so allocate the exact capacity specified.
Attribute[] replacementAttrs = new Attribute[capacity];
byte[] replacementProperties = new byte[capacity];
if (mAttrs != null && nAttrs > 0) {
System.arraycopy(mAttrs, 0, replacementAttrs, 0, nAttrs);
System.arraycopy(mAttrProperties, 0, replacementProperties, 0, nAttrs);
}
mAttrs = replacementAttrs;
mAttrProperties = replacementProperties;
}
/**
* Find an attribute value by the combination of URI and name.
* @param URI
* the namespace for this attribute. If null, don't worry about
* namespace matching. If non-null, this String must be interned.
* @param name
* the name of the attribute. This String must be interned.
* @return The position of this attribute, or -1 if not found.
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public final int findAttr(String URI, String name) {
if (Assertions.isEnabled) assert (name == name.intern());
if (Assertions.isEnabled) assert (URI == null || URI == URI.intern());
int nAttrs = getNumAttrs();
for (int i = 0; i < nAttrs; i++) {
Attribute attribute = getAttr(i);
boolean nsMatch;
if (URI == null) {
nsMatch = true;
}
else {
String attributeNS = attribute.getNS();
if (URI == "") {
nsMatch = StringUtils.isEmpty(attributeNS);
}
else {
nsMatch = attributeNS == URI;
}
}
if (!nsMatch) continue;
if (name == attribute.getName() || name == attribute.getQName()) {
return i;
}
}
return -1;
}
/**
* @exclude from published api.
*/
protected final ProtoableNode findExternalProto(int eClassTag,
int eAltClassTag, String urlRef, boolean bPeek/*false*/) {
assert this instanceof ProtoableNode;
if (StringUtils.isEmpty(urlRef))
return null;
AppModel appModel = getAppModel();
//
// Get handle to href service.
//
HrefHandler hrefHandler = appModel.getHrefHandler();
if (hrefHandler == null)
return null;
Node target = null;
try {
//
// Call into it to load the url into an XFA template model.
//
AppModel fragAppModel = hrefHandler.loadFragment((ProtoableNode)this);
//
// Extract any fragment ids.
//
String sFragId = null;
int nSharp = urlRef.indexOf('#');
if (nSharp >= 0)
sFragId = urlRef.substring(nSharp + 1);
else
sFragId = "som($template.#subform.#subform)";
//
// If fragment id is an XML id then ...
//
if (! sFragId.toLowerCase().startsWith("som")) {
//
// Search the corresponding DOM model for the given XML id.
//
Model doc = hrefHandler.getDocument(fragAppModel);
Element element = doc.getNode(sFragId);
if (element == null)
return null;
target = element;
}
//
// Else fragment id is a SOM id so ...
//
else {
//
// Extract SOM expression from its envelop.
//
int nParen = sFragId.indexOf('(');
if (nParen >= 0 )
sFragId = sFragId.substring(nParen + 1, sFragId.length() - 1);
if (!sFragId.startsWith("$"))
sFragId = "$template.#subform.." + sFragId;
//
// Search the XFA model for the given SOM expression.
//
NodeList list = fragAppModel.resolveNodes(sFragId,
bPeek, false, false);
if (list == null)
return null;
else if (list.length() != 1)
return null;
target = (Node)list.item(0);
}
// Copy errors logged in the fragment app model into the current model,
// so that the application can see them.
List errs = fragAppModel.getErrorList();
List contextErrs = fragAppModel.getErrorContextList();
if (errs.size() > 0) {
// Watson 1610012. Check for CircularProtoException and consider that
// a fatal error (bail out and stop resolving).
boolean bFatal = false;
for (int i = 0; i < errs.size(); i++) {
getModel().addErrorList(errs.get(i), 0, contextErrs.get(i));
if (errs.get(i).hasResId(ResId.CircularProtoException))
bFatal = true;
}
fragAppModel.clearErrorList();
if (bFatal) {
// Prevent memory leaks due to circular references by recursively
// nulling out references to external protos.
if (target instanceof ProtoableNode)
ProtoableNode.releaseExternalProtos((ProtoableNode)target);
return null;
}
}
} catch (ExFull ex) {
getModel().addErrorList(ex, 0, this);
}
//
// Ensure target node is protoable and same class as source.
//
if (target instanceof ProtoableNode) {
// If the target node is in the parent lineage, we have a circular reference.
Element parentCheck = getXFAParent();
while (parentCheck != null) {
if (parentCheck == target) {
MsgFormatPos message = new MsgFormatPos(ResId.CircularProtoException, getSOMExpression());
throw new ExFull(message);
}
parentCheck = parentCheck.getXFAParent();
}
ProtoableNode protoableNode = (ProtoableNode) target;
if (protoableNode.isSameClass(eClassTag)
|| protoableNode.isSameClass(eAltClassTag))
return protoableNode;
}
return null;
}
/**
* @exclude from published api.
*/
protected final ProtoableNode findInternalProto(int eClassTag,
int eAltClassTag, Node contextNode, String sReference,
boolean bPeek /* = false */) {
Object target = null;
if (StringUtils.isEmpty(sReference)) {
return null;
}
else if (sReference.startsWith("#")) { // Simple ID-reference lookup...
String protoID = sReference.substring(1); // strip '#' from ID
List protos = getModel().getProtoList();
for (int i = 0; protos != null && i < protos.size(); i++) {
ProtoableNode node = protos.get(i);
if (node.getID().equals(protoID)) {
target = node;
break;
}
}
if (target == null) {
// This code is here as a fall back just in case the user has
// been loading
// the XML file through APIs without saving the proto
// information.
Element e = getModel().getNode(protoID);
if (e != null)
target = e;
}
}
else { // Resolve SOM expression...
String sSOMExpression = sReference;
// Watson 1500551 -- trim som(...) if it's present.
if (sSOMExpression.startsWith("som(") && sSOMExpression.endsWith(")")) {
// Trim "som(" and trailing ")"
sSOMExpression = sSOMExpression.substring(4, sSOMExpression.length() - 1);
}
target = contextNode.resolveNode(sSOMExpression,
bPeek, false, false);
}
if (target instanceof ProtoableNode) {
// If the target node is in the parent lineage, we have a circular reference.
Element parentCheck = getXFAParent();
while (parentCheck != null) {
if (parentCheck == target) {
MsgFormatPos message = new MsgFormatPos(ResId.CircularProtoException, getSOMExpression());
throw new ExFull(message);
}
parentCheck = parentCheck.getXFAParent();
}
ProtoableNode protoableNode = (ProtoableNode) target;
if (protoableNode.isSameClass(eClassTag)
|| protoableNode.isSameClass(eAltClassTag))
return protoableNode;
}
return null;
}
/**
* Find an attribute value by name.
* The attribute must be in a compatible namespace with the model.
* @param name
* the name of the attribute. This String must be interned.
* @return The position of this attribute. -1 if not found.
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public final int findSchemaAttr(String name) {
int nAttrs = getNumAttrs();
for (int i = 0; i < nAttrs; i++) {
Attribute attribute = getAttr(i);
if (name == attribute.getName() && attribute.isSchemaAttr())
return i;
}
return -1;
}
/**
* Force an attribute ID to a new value regardless of immutability.
* Throws an exception if the ID is already in use.
* @param sID the new ID value to use.
*/
final void forceID(String sID) {
// Note: for Watson 1203998, avoid calling removeAttribute(aXFA_ID). See note there.
Attribute idAttr = getAttribute(XFA.IDTAG, true, false);
if (idAttr != null) {
removeAttr(null, XFA.ID);
}
if (sID.length() > 0)
setID(sID);
}
/**
* @exclude from published api.
*/
final public void foundBadAttribute(int eTag, String attrValue) {
// Bad value: '%1' of the '%2' attribute of '%3' element '%4'.
// Default will be used.
String attrName = getAtom(eTag);
MsgFormatPos warning
= new MsgFormatPos(ResId.FoundBadAttributeException);
warning.format(attrValue);
warning.format(attrName);
warning.format(getClassAtom());
warning.format(getName());
getModel().addErrorList(new ExFull(warning),
LogMessage.MSG_WARNING, this);
}
/**
* @exclude from published api.
*/
final public void foundBadAttribute(String attrName, String attrValue) {
// Bad value: '%1' of the '%2' attribute of '%3' element '%4'.
// Default will be used.
MsgFormatPos warning
= new MsgFormatPos(ResId.FoundBadAttributeException);
warning.format(attrValue);
warning.format(attrName);
warning.format(getClassAtom());
warning.format(getName());
getModel().addErrorList(new ExFull(warning),
LogMessage.MSG_WARNING, this);
}
/**
* Return the collection of like-named, in-scope, nodes.
* @return the collection.
*
* @exclude from published api.
*/
final public NodeList getAll(boolean bByName) {
if (bByName && getName() == "") {
MsgFormatPos message = new MsgFormatPos(ResId.NoNameException);
message.format("index").format("classIndex");
throw new ExFull(message);
}
Element parent = getXFAParent();
if (parent != null) {
// ALL Properties and oneof children will be referenced by their
// classname;
int eType = parent.getSchemaType(getClassTag());
if (eType == TEXT || eType == ELEMENT) {
ArrayNodeList retList = new ArrayNodeList();
for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
// JavaPort: must use class tags to compare for sameness rather than
// class names because ex-schema elements will have an invalid tag.
if (isSameClass(child.getClassTag())) {
retList.append(child);
}
}
return retList;
}
// can only be one one of child
else if (eType == ONEOF) {
ArrayNodeList retList = new ArrayNodeList();
retList.append(this);
return retList;
}
}
return super.getAll(bByName);
}
/**
* @exclude from published api.
*/
public AppModel getAppModel() {
Model model = getModel();
assert (model != null);
return model.getAppModel();
}
/**
* get an attribute value, will return the default attribute value if none
* exist NON validating
*
* @exclude from published api.
*/
public String getAtom(int eTag) {
// We really should be using our schema to look up atom names, instead
// of assuming that all schema elements live in XFANamespace.
// However, in the event that there's no Model/Schema, we have to resort
// to the most likely location - XFANamespace.
if (mModel == null)
return XFA.getAtom(eTag);
return mModel.getSchema().getAtom(eTag);
}
/**
* Gets this element's n'th attribute.
* @param n the zero-based index of the attribute.
* @return the n'th attribute.
*/
public final Attribute getAttr(int n) {
return getXmlPeerElement().mAttrs[n];
}
/**
* Gets this element's attribute with the given tag.
* It will return the default attribute value if none
* exist. Beware: it will NOT validate.
*
* @exclude from published api.
*/
public Attribute getAttribute(int eAttributeTag) {
return getAttribute(eAttributeTag, false, false);
}
/**
* Gets this element's attribute whose attribute tag is given.
*
* To peek at an attribute, set the peek argument to true.
* If the attribute is present, it is returned; otherwise null
* is returned.
*
* To create an attribute, set the peek argument to false.
* If the attribute is absent, a default attribute is created
* and returned; for attributes with no default, null is
* returned.
*
* If validation argument is true, and
* the validation fails, this method throws.
* @param eTag the tag of the attribute.
* @param bPeek whether to peek at the attribute or not.
* @param bValidate whether to validate the attribute or not.
* @return the attribute.
*/
public Attribute getAttribute(int eTag,
boolean bPeek /* = false */,
boolean bValidate /* = false */) {
// look into the dom for the attribute
String aPropertyName = getAtom(eTag);
int attr = findSchemaAttr(aPropertyName);
if (attr != -1) {
return getAttr(attr);
}
Attribute defaultAttribute = null;
boolean bNoSchema = false;
// regular nodes
if (getElementClass() == XFA.INVALID_ELEMENT || getElementClass() == XFA.DSIGDATATAG)
bNoSchema = true;
else {
AttributeInfo info = getNodeSchema().getAttributeInfo(eTag);
if (info != null)
defaultAttribute = info.getDefault();
}
// invalid
if (defaultAttribute == null && !bNoSchema) {
if (bValidate) {
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidGetPropertyException);
message.format(getClassAtom());
message.format(aPropertyName);
throw new ExFull(message);
}
// invalid attr so return null
else
return null;
}
if (bPeek)
return null;
if (bNoSchema)
return gsEmptyStringAttr;
// return default
return defaultAttribute;
}
/**
* get the named attribute.
*
* @param aAttrName - the attribute name.
* @param bSearchProto - whether to search protos.
* @return Attribute object, which may be null.
* @exclude
*/
public Attribute getAttributeByName(String aAttrName, boolean bSearchProto /* = false */ ) {
Attribute attr = null;
if ( aAttrName != null ) {
int n = getNumAttrs();
for (int i = 0; i < n; i++) {
if (getAttrName(i).equals(aAttrName) == true ) {
attr = getAttr(i);
break;
}
}
}
return attr;
}
/**
* get the index of this attribute.
* @param attr the attribute to find.
* @return the index position in the attribute array.
* @exclude
*/
public final int getAttrIndex(Attribute attr) {
int n = getNumAttrs();
for (int i = 0; i < n; i++) {
if (getAttr(i) == attr)
return i;
}
assert(false); // Can't think of any use-case yet where we wouldn't find the attribute.
return -1; // not found ! ?
}
/**
* @exclude from published api.
*/
public final String getAttrName(int index) {
Attribute attribute = getAttr(index);
String name = attribute.getName();
if (StringUtils.isEmpty(name))
name = attribute.getQName();
return name;
}
/**
* @param index the index of the attribute.
* @return the namespace of the attribute at the specified index. This String must be interned.
* @exclude from published api.
*/
public final String getAttrNS(int index) {
return getAttr(index).getNS();
}
/**
* Get one of the volatile attribute properties. Since attributes are immutable, we can't store these
* properties in the attributes themselves.
* @param attrIndex The offset into the attribute array.
* @param eProp The property to return. One of AttrIsDefault, AttrIsFragment, AttrIsTransient.
* @return the boolean value of the property
* @exclude
*/
public final boolean getAttrProp(int attrIndex, int eProp) {
return (getXmlPeerElement().mAttrProperties[attrIndex] & eProp) != 0;
}
/**
* @exclude from published api.
*/
public final String getAttrQName(int index) {
return getAttr(index).getQName();
}
/**
* @exclude from published api.
*/
public final String getAttrVal(int index) {
return getAttr(index).getAttrValue();
}
/**
* Gets this element's n'th XML child.
* @param n the zero-based index of the XML child.
* @return the n'th child.
*/
public final Node getXMLChild(int n) {
Node child = getFirstXMLChild();
for (int i = 0; i < n; i++) {
child = child.getNextXMLSibling();
}
return child;
}
/**
* Gets this element's n'th XFA child.
* @param n the zero-based index of the XFA child.
* @return the n'th child.
*/
public final Node getXFAChild(int n) {
Node child = getFirstXFAChild();
for (int i = 0; i < n; i++) {
child = child.getNextXFASibling();
}
return child;
}
/**
* @exclude from published api.
*/
public final ChildReln getChildReln(int eTag) {
ChildRelnInfo info = getNodeSchema().getChildRelnInfo(eTag);
if (info != null)
return info.getRelationship();
return null;
}
/**
* Return the collection of like-class, in-scope, nodes.
* @return the collection.
*
* @exclude from published api.
*/
final public NodeList getClassAll() {
// TODO Auto-generated method stub
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Element#getClassAll");
}
/**
* Return the position of this node in its collection of like-class,
* in-scope, like-child relationship, nodes.
* @return the 0-based position
*
* @exclude from published api.
*/
final public int getClassIndex() {
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Element#getClassIndex");
}
/**
* Gets this element's class name.
* @return the class name as an interned string.
*
* @exclude from published api.
*/
public final String getClassName() {
String name = super.getClassAtom();
if (name == null)
return mLocalName;
return name;
}
/**
* @exclude from published api.
*/
int getDefaultOneOfTag() {
int eOneOfChild = XFA.SCHEMA_DEFAULTTAG;
NodeSchema nodeSchema = getNodeSchema();
SchemaPairs children = nodeSchema.getValidChildren();
if (children != null) {
int eOneOfChildDefault = defaultElement();
boolean bFound = false;
for (int i = 0; i < children.size(); i++) {
ChildReln reln = (ChildReln)children.value(i);
int eTag = children.key(i);
if (reln.getOccurrence() == ChildReln.oneOfChild) {
if (eOneOfChildDefault == eTag) {
bFound = true;
eOneOfChild = eTag;
break;
}
else if (!bFound) {
// we found the first one but we still need to
// keep looking for the default oneOfChild
bFound = true;
eOneOfChild = eTag;
}
}
}
if (!bFound)
eOneOfChild = eOneOfChildDefault;
}
return eOneOfChild;
}
// Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
// Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
/**
* Gets a collection of deltas to restore.
* The locale attribute is always restored immediately, and is not returned in the
* list of deltas.
* @param delta an Element from the form packet.
* @param list the list of deltas to be populated.
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public void getDeltas(Element delta, XFAList list) {
if (delta != null &&
isSameClass(delta) &&
getName() == delta.getName()) {
SchemaPairs attrs = getNodeSchema().getValidAttributes();
if (attrs != null) {
Attribute localeDelta = null;
if (list != null) {
for (int i = 0; i < attrs.size(); i++) {
int eTag = attrs.key(i);
// do not restore name tags
if (eTag == XFA.NAMETAG)
continue;
// see if we have the attribute defined, if we do, copy it over.
Attribute deltaAttr = delta.getAttribute(eTag, true, false);
if (eTag == XFA.LOCALETAG) {
localeDelta = deltaAttr;
continue;
}
if (deltaAttr != null) {
Attribute attr = getAttribute(eTag, false, false);
// create a delta
Delta newDelta = new Delta(this, delta, attr, deltaAttr, XFA.getString(eTag));
list.append(newDelta);
}
}
}
else {
localeDelta = delta.getAttribute(XFA.LOCALETAG, true, false);
}
// need to always restore locale, if we don't we could show the data incorrectly
if (localeDelta != null) {
setAttribute(localeDelta, XFA.LOCALETAG);
}
}
ElementNodeList children = new ElementNodeList(this);
ElementNodeList deltaChildren = new ElementNodeList(delta);
for (Node deltaChild = delta.getFirstXFAChild(); deltaChild != null; deltaChild = deltaChild.getNextXFASibling()) {
Node targetChild = null;
boolean bIsContainerChild = deltaChild.isContainer();
int eClassTag = deltaChild.getClassTag();
ChildReln childReln = getChildReln(eClassTag);
if (childReln != null) {
if (childReln.getOccurrence() == ChildReln.oneOfChild) {
targetChild = getOneOfChild(false, false);
if (targetChild == null || !targetChild.isSameClass(eClassTag)) {
if (list != null) {
Delta newDelta = new Delta(this, delta, targetChild, deltaChild, "");
list.append(newDelta);
}
targetChild = null;
}
}
else if (childReln.getMax() != -1) {
if (eClassTag == XFA.TEXTNODETAG) {
targetChild = getText(false, false, false);
}
else {
Integer nOccurrence = deltaChildren.getOccurrence(deltaChild);
if (nOccurrence != null)
targetChild = getElement(eClassTag, false, nOccurrence.intValue(), false, false);
}
}
else {
Integer nOccurrence = deltaChildren.getOccurrence(deltaChild);
if (nOccurrence != null) {
targetChild = children.getNamedItem(deltaChild.getName(), deltaChild.getClassAtom(), nOccurrence.intValue());
}
if (list != null && targetChild == null && !bIsContainerChild) { // restore o..n children that are not containers.
targetChild = null;
Delta newDelta = new Delta(this, delta, targetChild, deltaChild, "");
list.append(newDelta);
}
}
}
if (targetChild != null) {
// JavaPort: Need to special case TextNode since it isn't derived from Element in XFA4J.
if (targetChild instanceof TextNode) {
((TextNode)targetChild).getDeltas((TextNode)deltaChild, list);
}
else if (targetChild instanceof Element && deltaChild instanceof Element) {
((Element)targetChild).getDeltas((Element)deltaChild, list);
}
}
}
}
}
/**
* Gets the Element that represents this XFA Element's peer in the XML DOM.
* @return if this Element is a DualDomNode, the corresponding XML peer; otherwise, this.
*/
private Element getXmlPeerElement() {
return this instanceof DualDomNode ? (Element)((DualDomNode)this).getXmlPeer() : this;
}
/**
* @exclude from published api.
*/
protected ScriptDynamicPropObj getDynamicScriptProp(String sPropertyName,
boolean bPropertyOverride, boolean bPeek) {
if (StringUtils.isEmpty(sPropertyName))
return null;
int nAvail = Schema.XFAAVAILABILITY_ALL;
int nVersion = Schema.XFAVERSION_10;
int eType = INVALID;
int eTag = XFA.getTag(sPropertyName);
if (eTag != XFA.INVALID_ELEMENT) {
// JavaPort: this code from C++ is a no-op
// if (aPropertyName == XFA.ASCENT) {
// eType = INVALID;
// }
AttributeInfo attrInfo = getNodeSchema().getAttributeInfo(eTag);
if (attrInfo != null) {
nAvail = attrInfo.getAvailability();
nVersion = attrInfo.getVersionIntroduced();
eType = ATTRIBUTE;
}
else {
ChildRelnInfo childInfo = getNodeSchema().getChildRelnInfo(eTag);
if (childInfo != null) {
nAvail = childInfo.getAvailability();
nVersion = childInfo.getVersionIntroduced();
ChildReln reln = childInfo.getRelationship();
if (reln != null) {
if (reln.getMax() == ChildReln.UNLIMITED)
eType = CHILD;
else if (reln.getOccurrence() == ChildReln.oneOfChild)
eType = ONEOF;
else
eType = ELEMENT;
}
}
}
}
// bPropertyOverride(#sPropertyName) means don't hunt for child nodes by name
// only get xfaproperty or child element(based on classname) or script property
ScriptDynamicPropObj desc = super.getDynamicScriptProp(sPropertyName, bPropertyOverride, bPeek, nVersion, nAvail);
if (desc != null)
return desc;
String sGetFunc;
if (eType == ONEOF) {
Node oneOf = getOneOfChild(bPeek, false);
if (oneOf != null && oneOf.getClassTag() == eTag)
sGetFunc = "locateOneOf";
else
return null;
}
// JavaPort: Why does XFA4J have a test for eType == TEXT here - eType is never set to that value!
else if (eType == ATTRIBUTE || eType == TEXT || eType == ELEMENT) {
if (bPeek && !isPropertySpecified(eTag, true, 0))
return null;
if (bPeek)
sGetFunc = "locatePropPeek";
else
sGetFunc = "locateProp";
} else
return null;
return new ElementScriptDynamicPropObj (
sGetFunc,
(eType == Element.ATTRIBUTE) ? "setProp" : null,
nVersion,
nAvail
);
}
private static class ElementScriptDynamicPropObj extends ScriptDynamicPropObj {
private final String msGetFunc; // interned
private final String msSetFunc; // interned
ElementScriptDynamicPropObj(
String sGetFunc,
String sSetFunc,
int nXFAVersion,
int nAvailability) {
super(nXFAVersion, nAvailability);
msGetFunc = sGetFunc;
msSetFunc = sSetFunc;
}
@FindBugsSuppress(code="ES")
public boolean invokeGetProp(Obj scriptThis, Arg retValue, String sPropertyName) {
if (msGetFunc == "locateOneOf")
return ElementScript.locateOneOf(scriptThis, retValue, sPropertyName);
else if (msGetFunc == "locatePropPeek")
return ElementScript.locatePropPeek(scriptThis, retValue, sPropertyName);
else if (msGetFunc == "locateProp")
return ElementScript.locateProp(scriptThis, retValue, sPropertyName);
else {
assert false;
return false;
}
}
@FindBugsSuppress(code="ES")
public boolean invokeSetProp(Obj scriptThis, Arg propertyValue, String sPropertyName) {
if (msSetFunc == "setProp")
return ElementScript.setProp(scriptThis, propertyValue, sPropertyName);
else {
assert false;
return false;
}
}
public boolean hasSetter() {
return msSetFunc != null;
}
}
// JavaPort: This is incompletely implemented in XFA4J, and is only used by Designer.
// void getDynamicScriptProps(List propNames, boolean bUseDynamic) {
//
// if (bUseDynamic)
// ElementScript.getScriptChildList(this, propNames);
// else {
// NodeSchema nodeSchema = getNodeSchema();
// SchemaPairs attrs = nodeSchema.getValidAttributes();
// if (attrs != null) {
// for (int i = 0; i < attrs.size(); i++) {
// propNames.add(XFA.getString(attrs.key(i)));
// }
// }
// // Special case: we don't make the renderAs children available to the scripting interface.
// if (getClassTag() == XFA.RENDERASTAG)
// return;
//
// SchemaPairs children = nodeSchema.getValidChildren();
// if (children != null) {
// for (int i = 0; i < children.size(); i++) {
// // only property children
// ChildReln reln = (ChildReln) children.value(i);
// String sChild = XFA.getString(children.key(i));
// // watson 671890: do not append #text, #xHTML and #xml
// // otherwise they
// // appear in intellisence and are not easily accessible
// // through SOM.
// if (reln.getMax() != ChildReln.UNLIMITED
// && reln.getOccurrence() != ChildReln.oneOfChild
// && sChild.charAt(0) != '#')
// propNames.add(sChild);
// }
// }
// }
// }
/**
* Gets this element's sub element whose element tag is given.
*
* To return the element, set the returnDefault
* argument to true.
* If the element is present, it is returned; otherwise
* the default element is created and returned.
*
* To peek at the element, set the peek argument to true.
* If the element is present, it is returned; otherwise null
* is returned.
* When set to true, default properties aren't created,
* and proto references are not expanded.
*
* To create the element, set the peek argument to false.
* If the element is absent, a default element is created
* and returned.
*
* If validation argument is true, and
* the validation fails, this method throws.
*
* If the occurrence argument is out of range, this method throws.
* @param eTag the tag of the element to retrieve.
* @param bPeek whether to peek at the element, or not.
* @param nOccurrence the n'th occurrence of the element to retrieve.
* @param bReturnDefault whether to create a default element, or not.
* @param bValidate whether to validate the element, or not.
* @return the element, or null.
*/
public Element getElement(int eTag,
boolean bPeek /* = false */,
int nOccurrence /* = 0 */,
boolean bReturnDefault /* = false */,
boolean bValidate /* = false */) {
return getElementLocal(eTag, bPeek, nOccurrence, bReturnDefault, bValidate);
}
/**
* get an element for a given node, The method will create a default if it
* doesn't exist. NON validating
*
* @exclude from published api.
*/
public final Element getElement(int eElementTag, int nOccurrence /* =0 */) {
return getElement(eElementTag, false, nOccurrence, false, false);
}
/**
* A variant of getElement that peeks for a non-Element and
* returns it if it exists, otherwise falls back on getElement
* with bPeek = true (don't create a default).
* Used by compareVersions below.
*
* @exclude from published api.
*/
public final Node getNode(int eTag, int nOccurrence /* =0 */) {
Node child = locateChildByClass(eTag, nOccurrence);
if (child != null)
return child;
if (eTag == XFA.TEXTNODETAG)
return getText(true, false, false);
else
return getElement(eTag, true, nOccurrence, false, false);
}
/**
* This method does the work of getElement(). It's been split out so that it can
* be called by derived classes who don't want proto resolution to step in.
* @exclude
*/
public final Element getElementLocal(int eTag,
boolean bPeek /* = false */,
int nOccurrence /* = 0 */,
boolean bReturnDefault /* = false */,
boolean bValidate /* = false */) {
// ensure we are dealing with an element
if (bValidate) {
ChildReln validChild = getChildReln(eTag);
// validate child relen
if (validChild == null
|| validChild.getOccurrence() == ChildReln.oneOfChild
|| validChild.getMax() == ChildReln.UNLIMITED) {
String aPropertyName = getAtom(eTag);
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidGetPropertyException);
message.format(getClassAtom());
message.format(aPropertyName);
throw new ExFull(message);
}
// Validate the occurrence number
if (validChild != null) {
if (validChild.getMax() <= nOccurrence)
throw new ExFull(new IndexOutOfBoundsException(""));
}
} else {
// guard against asking for properties on invalid nodes.
assert (getModel() != null);
}
Node child = locateChildByClass(eTag, nOccurrence);
if (child instanceof Element)
return (Element) child;
//
// Still not found? Create a default element.
//
if (!bPeek || bReturnDefault) {
child = defaultElement(eTag, nOccurrence);
if (child instanceof Element)
return (Element) child;
}
return null;
}
/**
* @exclude from published api.
*/
public final int getElementClass() {
return getClassTag();
}
/**
* @exclude from published api.
*/
public final int getEnum(int ePropertyTag) {
EnumValue eNum = (EnumValue) getAttribute(ePropertyTag);
return eNum.getInt();
}
/**
* @exclude from published api.
*/
public final EnumAttr getEnum(String sPropertyName) {
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Element#getEnum(String)");
}
/**
* @exclude from public api.
*/
public EventManager.EventTable getEventTable(boolean bCreate) {
if (bCreate && mEventTable == null) {
mEventTable = new EventManager.EventTable();
}
return mEventTable;
}
/** @exclude from published api. */
protected final String getEventScript(Element element) {
if (element == null) {
Attribute attr = findEventAttribute(this, XFA.SCRIPT);
return attr == null ? "" : attr.getAttrValue();
}
Node firstChild = element.getFirstXMLChild();
return firstChild == null ? "" : firstChild.getData();
}
/** @exclude from published api. */
protected final String getEventContentType(Element element) {
Element node = element == null ? this : element;
Attribute attr = findEventAttribute(node, STRS.CONTENTTYPE);
return attr == null ? "" : attr.getAttrValue();
}
/** @exclude from published api. */
protected final String getEvent() {
Attribute attr = findEventAttribute(this, XFA.EVENT);
return attr == null ? "" : attr.getAttrValue();
}
private Attribute findEventAttribute(Element element, String attrName) {
final int len = element.getNumAttrs();
for (int i = 0; i < len; i++) {
Attribute attr = element.getAttr(i);
if (attr.getLocalName().equals(attrName)) {
if (attr.getNS().startsWith(STRS.XFAEVENTSNS))
return attr;
}
}
return null;
}
/**
* Gets this element's first XML child.
* @return the first XML child.
*/
public Node getFirstXMLChild() {
return mFirstXMLChild;
}
/**
*
* a (not so) private method, overrides Node.getSibling()
*
* @exclude from published api.
*/
public final Node getSibling(int index, boolean bByName, boolean bExceptionIfNotFound) {
if (bByName)
assert(getName() != "");
Element parent = getXMLParent();
if (parent != null) {
boolean bError = false;
ChildReln reln = parent.getChildReln(getClassTag());
// Note: ALL Properties and oneof children will be referenced by their classname;
if (reln != null) {
// get the max occurrence of this node
int nMaxOccur = reln.getMax();
// o..n nodes delegate to the base class
if (nMaxOccur == ChildReln.UNLIMITED) {
return super.getSibling(index, bByName,bExceptionIfNotFound );
}
// if we can only have one instance, this includes oneOfChildren
else if (nMaxOccur == 1) {
if (index == 0)
return this;
// they are asking for an invalid index
bError = true;
}
else {
// first check occurrence info
if (nMaxOccur <= index)
bError = true;
else {
// watson bug 1544119
// ensure we call getElement to ensure template nodes are copied from
// the template model over to the form model when someone calls
// resolveNode("field.border.edge[2]");
// For an invalid index, this unilaterally throws an IndexOutOfBoundsException exception.
return parent.getElement(getClassTag(), index);
}
}
}
if (bError) {
// requested index does not exist
if (bExceptionIfNotFound)
throw new ExFull(new IndexOutOfBoundsException(""));
return null;
}
}
// default to base class
return super.getSibling(index, bByName, bExceptionIfNotFound);
}
/**
* Gets this element's first XFA child.
* @return the first XFA child.
*/
public Node getFirstXFAChild() {
if (mFirstXMLChild != null && mFirstXMLChild.getClassTag() == XFA.INVALID_ELEMENT) {
return mFirstXMLChild.getNextXFASibling();
}
return mFirstXMLChild;
}
/**
* @exclude from published api.
*/
final public String getID() {
if (isValidAttr(XFA.IDTAG, false, null)) {
Attribute attr = getAttribute(XFA.IDTAG);
return attr == null ? "" : attr.getAttrValue();
}
return "";
}
/**
* Return the position of this node in its collection of like-named,
* in-scope, like-child relationship, nodes.
* @return the 0-based position
*
* @exclude from published api.
*/
public final int getIndex(boolean bByName) {
// This part corresponds to the old XFANodeImpl::getIndex()
if (bByName && getName() == "") {
MsgFormatPos message = new MsgFormatPos(ResId.NoNameException);
message.format("all");
message.format("getIndex");
throw new ExFull(message);
}
Element parent = getXFAParent();
if (parent != null) {
// ALL Properties and oneof children will be referenced by there classname;
int eType = parent.getSchemaType(getClassTag());
if (eType == TEXT || eType == ELEMENT) {
int nFound = 0;
Node child = parent.getFirstXFAChild();
while (child != null) {
if (isSameClass(child)) {
if (child == this)
return nFound;
nFound++;
}
child = child.getNextXFASibling();
}
return nFound;
}
// can only be one one of child
else if (eType == ONEOF) {
return 0;
}
}
return super.getIndex(bByName);
}
/**
* Get our namespace by traversing to our ancestors if necessary.
* @return [inherited] namespace string
*
* @exclude from published api.
*/
final public String getInheritedNS() {
String ns = getNS();
Element check = this;
while (ns == null) {
if (check.getXFAParent() != null) {
check = check.getXFAParent();
ns = check.getNS();
}
}
return ns;
}
/**
* Gets this element's installed locale. This method checks
* all enclosing field, draw, or subform ancestors,
* looking for their locale attribute.
* @return the installed locale name.
*/
final public String getInstalledLocale() {
// Adobe patent application tracking # B136,
// entitled "Applying locale behaviors to regions of a form",
// inventors: Gavin McKenzie, Mike Tardif, John Brinkman".
//
// If the current XFA node is one that supports the locale attribute.
// No need to call isValidAttr(XFA::LOCALETAG) because
// isPropertySpecified
// will return FALSE if it's not a valid attribute.
if (isPropertySpecified(XFA.LOCALETAG, true, 0)) {
Attribute attr = getAttribute(XFA.LOCALETAG, true, false);
if (attr != null && ! attr.isEmpty()) {
String sLocale = attr.toString();
if (sLocale.equals("ambient"))
sLocale = getModel().getCachedLocale();
return sLocale;
}
}
// If the current node does not support/provide the
// locale attribute, see if its parent does
Element parent = getXFAParent();
if (parent != null)
return parent.getInstalledLocale();
//
// None found so return the ambient locale (cached in the model).
//
return getModel().getCachedLocale();
}
/**
* Determines if this element's installed locale
* is ambient.
* @return true if the installed locale name is "ambient", and
* false otherwise.
* @see #getInstalledLocale()
*/
final public boolean isInstalledLocaleAmbient() {
// If the current XFA node is one that supports the locale attribute.
if (isPropertySpecified(XFA.LOCALETAG, true, 0)) {
Attribute attr = getAttribute(XFA.LOCALETAG, true, false);
if (attr != null && ! attr.isEmpty()) {
return attr.toString().equals("ambient");
}
}
// If the current node does not support/provide the
// locale attribute, see if its parent does
Element parent = getXFAParent();
if (parent != null)
return parent.isInstalledLocaleAmbient();
//
// None found so return the ambient locale (cached in the model).
//
return false;
}
/** @exclude from published api. */
boolean getIsDataWindowRoot() {
return mbIsDataWindowRoot;
}
/**
* Determine if this node contains a null value.
* @return true if this node contains a null value, false otherwise.
*
* @exclude from published api.
*/
public boolean getIsNull() {
return false;
}
/**
* Gets this element's last XML child.
* @return the last XML child.
*/
final public Node getLastXMLChild() {
Node child = getFirstXMLChild();
while (child != null) {
if (child.getNextXMLSibling() == null)
return child;
child = child.getNextXMLSibling();
}
return null;
}
/**
* @exclude from published api.
*/
final public int getLineNumber() {
return mnLineNumber;
}
/**
* @return the local name as an interned string.
* @exclude from published api.
*/
public String getLocalName() {
return mLocalName;
}
/**
* Gets this element's model.
* @return the model.
*/
public final Model getModel() {
return mModel;
}
/**
* Gets this element's name.
*
* The name of an element is the value of its name attribute,
* or the element name if there is no name attribute.
* @return the name of the element.
*/
public String getName() {
if (maName != null)
return maName;
if (this instanceof ProtoableNode) {
ProtoableNode proto = ((ProtoableNode) this).getProto();
if (proto != null) {
return proto.getName();
}
}
//
// If this is a generic XFANode (i.e. not a derived implementation) allow
// the name to default to the element name, as long as we're not peered
// with a TEXT node.
//
if (getElementClass() == XFA.INVALID_ELEMENT) {
return getLocalName();
}
return "";
}
/**
* This version will not perform any notification or undo steps.
* Should only be called when a node is being created.
*
* @exclude from public api.
*/
public void privateSetName(String name){
if (!isValidAttr(XFA.NAMETAG, false, null))
return;
Attribute a = new StringAttr(XFA.NAME, name);
updateAttribute(a);
// Attribute constructor will have interned the value
maName = a.getAttrValue();
}
/**
* Gets this element's list of children.
* @return a node list of all child nodes.
*/
public NodeList getNodes() {
return new ElementNodeList(this);
}
/**
* Gets the Schema for this node.
* @return an NodeSchema object.
*
* @exclude from published api.
*/
final public NodeSchema getNodeSchema() {
if (mNodeSchema != null)
return mNodeSchema;
return calcNodeSchema();
}
/**
* calcNodeSchema is a private method called by getNodeSchema().
* It's only called when mpNodeSchema is not yet set (this is
* so that getNodeSchema() can be inlined for efficiency).
* @return a NodeSchema object.
*
* @exclude from published api.
*/
final private NodeSchema calcNodeSchema() {
if (mNodeSchema == null && mModel != null) {
if (getClassTag() == XFA.INVALID_ELEMENT)
return Schema.nullSchema();
mNodeSchema = mModel.getSchema().getNodeSchema(getClassTag());
}
if (mNodeSchema != null)
return mNodeSchema;
// The schema for a model would normally not be found
// in it's parent.
// Note: These two lines differ from C++
if (mModel instanceof AppModel && this instanceof Model)
return ((Model) this).getSchema().getNodeSchema(getClassTag());
return Schema.nullSchema();
}
/**
* Gets this element's namespace.
* @return the namespace URI.
*/
public String getNS() {
return mURI;
}
/**
* Gets this element's namespace.
*
* Unlike {@link #getNS()}, this method is not overridden by Model,
* so it always returns the URI associated with this element, whereas
* {@link Model#getNS()} overrides this and can return a modified
* namespace. This property should be used when emulating XML DOM
* functionality.
*
* @return the namespace URI as an interned String.
* @exclude from published API.
*/
final String getNSInternal() {
return mURI;
}
/**
* Gets this element's number of attributes.
* @return the number of attributes.
*/
public final int getNumAttrs() {
// This implementation deals with two differences from the C++:
// - An XFA Element may or may not have an XML peer
// - A TextNode peer doesn't support attributes, so it zero attributes.
//
// Most other attribute methods blindly assume that the peer is an element,
// but calling this method should serve as a guard to prevent attempting to
// cast a TextNode peer to Element in an attempt to access a parameter on it.
if (this instanceof DualDomNode) {
Node peerNode = ((DualDomNode)this).getXmlPeer();
if (peerNode instanceof Element)
return ((Element)peerNode).nAttrs;
else
return 0;
}
else {
return nAttrs;
}
}
/**
* 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!
* This method is the only reliable means to return a oneOf child. Other
* interfaces (e.g. getProperty()) will not interpret default values or
* handle prototypes correctly for oneOf children.
* @return the Node for this child. If this child has not been specified,
* this method will return the appropriate XFA default element.
*
* @exclude from published api.
*/
final public Node getOneOfChild() {
return getOneOfChild(false, false);
}
/**
* @exclude from published api.
*/
public Node getOneOfChild(boolean bPeek, boolean bReturnDefault /* = false */) {
// Find out if there's a local "oneof" child
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int eTag = child.getClassTag();
if (eTag == XFA.INVALID_ELEMENT)
return null;
ChildReln childReln = getChildReln(eTag);
if (childReln != null
&& childReln.getOccurrence() == ChildReln.oneOfChild) {
return child;
}
}
if (!bPeek || bReturnDefault) {
int eOneOfChild = getDefaultOneOfTag();
if (eOneOfChild == XFA.SCHEMA_DEFAULTTAG)
return null;
// JavaPort: #text is a special case for oneOfChild
// All other oneOf children are elements.
if (eOneOfChild == XFA.TEXTNODETAG)
// Javaport: instead of invoking
// return getText(bPeek, bReturnDefault, false);
// emulate broken C++ behaviour for now.
return getModel().createTextNode(this, getLastXMLChild(), "");
return defaultElement(eOneOfChild, 0);
}
return null;
}
/**
* Get all the processing instruction for this node. Note, the first token
* of each of the returned PIs will be the PI type.
* @param pis - an input/output parameter that is populated with the values
* of all the Processing Instructions found for PI name.
* with the same aPiName.
* @param bCheckProtos - if TRUE, check if this element is specified via
* prototype inheritance. Defaults to FALSE.
* @exclude from published api.
*/
void getPI(List pis, boolean bCheckProtos) {
for (Node node = getFirstXMLChild(); node != null; node = node.getNextXMLSibling()) {
if (node instanceof ProcessingInstruction) {
ProcessingInstruction pi = (ProcessingInstruction)node;
String sTemp = pi.getName() + ' ' + pi.getData();
pis.add(sTemp);
}
}
}
/**
* Get the processing instruction based on the aPiName. If multiple
* Processing Instructions exist for the same aPiName, all PI's found with
* that name will be returned.
* @param aPiName
* the processing instruction's target name. This String must be interned.
* @param pis
* an input/output parameter that is populated with the values of
* all the Processing Instructions found for PI name. with the
* same aPiName.
* @param bCheckProtos
* if TRUE, check if this element is specified via prototype
* inheritance. Defaults to FALSE.
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public void getPI(String aPiName, List pis,
boolean bCheckProtos /* = false */) {
assert (aPiName != null);
for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
if (child instanceof ProcessingInstruction) {
ProcessingInstruction pi = (ProcessingInstruction) child;
if (pi.getName() == aPiName)
pis.add(pi.getData());
}
}
}
/**
* For use with attributes that live in a non-standard namespace. Currently the
* only attribute for which this is true is XFA::RIDTAG. This method has no effect for
* other attributes.
* This method declares the namespace of the specified attribute on this node.
* The purpose of doing this is to optimize file size when the attribute is present
* in the document. Otherwise the namespace will be declared with each occurrence of
* the attribute, leading to file bloat.
* For the XFA::RIDTAG attribute, this method will declare the urn:oasis:names:tc:xliff:document:1.1
* namespace on the template model with the standard prefix "xliff".
* @param eAttributeTag - the attribute (currently only XFA::RIDTAG will have any effect).
* @param bDeleteIfNotNeeded - if FALSE, simply declare the namespace unconditionally. This
* is very efficient, but if the attribute is never used in the document then the declaration
* of the namespace is superfluous. If TRUE, search the descendants of this node to see
* if the attribute is in use; if it's not in use then delete any declarations of the
* attribute's namespace at any level of the tree under this node. If the attribute is in
* use then this will have the net effect of the FALSE setting. No superfluous namespace
* declaration will occur with the TRUE setting.
*
* @exclude from published api.
*/
public void optimizeNameSpace(int eAttributeTag, boolean bDeleteIfNotNeeded) {
// Currently only XFA::RIDTAG has any special namespace. This method has no
// effect for other attributes.
if (eAttributeTag == XFA.RIDTAG) {
String sNSAlias = "xmlns:xliff";
boolean bNeeded = true;
if (bDeleteIfNotNeeded)
bNeeded = pruneNameSpaceDefn(this, sNSAlias, STRS.XLIFFNS);
if (bNeeded)
setAttribute("", sNSAlias, "xliff", STRS.XLIFFNS);
}
}
/**
* Get the processing instruction based on the aPiName. If multiple
* Processing Instructions exist for the same aPiName, all PI's found with
* that name will be returned.
* @param aPiName
* the processing instructions target name
* @param sPropName
* the processing instructions property name
* @param pis
* an input/output parameter that is populated with the values of
* all the Processing Instructions found for PI name. with the
* same aPiName.
* @param bCheckProtos
* if TRUE, check if this element is specified via prototype
* inheritance. Defaults to FALSE.
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public void getPI(String aPiName,
String sPropName,
List pis,
boolean bCheckProtos /* = false */) {
assert (aPiName != null);
for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
if (child instanceof ProcessingInstruction) {
ProcessingInstruction pi = (ProcessingInstruction) child;
if (pi.getName() == aPiName) {
String sNodeValue = pi.getData();
String[] sProp = sNodeValue.split("[ \t]");
if (sProp[0].equals(sPropName)) {
int skip = StringUtils.skipUntil(sNodeValue, " \t", 0);
String sReturn = sNodeValue.substring(skip,
sNodeValue.length()).trim();
pis.add(sReturn);
}
}
}
}
}
/**
* Gets this element's namespace prefix.
* @return the namespace prefix.
*/
public final String getPrefix() {
String qName = getXMLName();
if (qName != null) {
int colon = qName.indexOf(':');
if (colon > 0)
return qName.substring(0, colon).intern();
}
return "";
}
/**
* @exclude from published api.
*/
final public Object getProperty(int ePropTag, int nOccurrence /* = 0 */) {
int ePropType = getSchemaType(ePropTag);
if (ePropType == ELEMENT) {
return getElement(ePropTag, false, nOccurrence, false, false);
}
else if (ePropType == TEXT) {
return getText(false, false, false);
}
else if (ePropType == ATTRIBUTE) {
return getAttribute(ePropTag);
}
else if (ePropType == ONEOF) {
String aPropertyName = XFA.getString(ePropTag);
// If using getProperty to get a OneOFChild, don't allow a default
// property to be created.
throw new ExFull(ResId.InvalidGetOneOfException, aPropertyName);
}
String aPropertyName = XFA.getString(ePropTag);
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidGetPropertyException);
message.format(getClassName());
message.format(aPropertyName);
throw new ExFull(message);
}
/**
* @exclude from published api.
*/
public final Object getProperty(String propertyName, int nOccurrence /* = 0 */) {
int eTag = XFA.getTag(propertyName.intern());
if (eTag != XFA.INVALID_ELEMENT) {
return getProperty(eTag, nOccurrence);
}
MsgFormatPos message = new MsgFormatPos(ResId.InvalidGetPropertyException);
message.format(getClassName());
message.format(propertyName);
throw new ExFull(message);
}
/** @exclude from published api. */
public boolean getSaveXMLSaveTransient() {
return mbSaveXMLSaveTransient;
}
Schema getSchema() {
// Javaport TODO: use findSchema()
// return getModel().getSchema().findSchema(getClassTag());
return getModel().getSchema();
}
/**
* @exclude from published api.
*/
public int getSchemaType(int eTag) {
//
// generic XFA nodes have no schema, so allow all attributes
// By default all attributes are allowed and all children/elements
// are disallowed. This allows us to use getProperty() to return
// attributes, and still allows us to access elements via the children
// list.
//
if (getElementClass() == XFA.INVALID_ELEMENT)
return ATTRIBUTE;
// guard against asking for properties on invalid nodes.
if (getModel() == null)
return INVALID;
ChildReln reln = getChildReln(eTag);
if (reln != null) {
if (reln.getMax() == ChildReln.UNLIMITED)
return CHILD;
else if (reln.getOccurrence() == ChildReln.oneOfChild)
return ONEOF;
else if (eTag == XFA.TEXTNODETAG)
return TEXT;
else
return ELEMENT;
}
// performance tuning
// make attr check here instead of calling isValidAttr
if (getNodeSchema().getAttributeInfo(eTag) != null)
return ATTRIBUTE;
return INVALID;
}
/**
* @exclude from published api.
*/
public ScriptFuncObj getScriptMethodInfo(String sFunctionName) {
ScriptFuncObj scriptFunc = null;
// validate the version and availability
Model model = getModel();
if (model != null)
scriptFunc = super.getScriptMethodInfo(sFunctionName);
return scriptFunc;
}
/**
* @exclude from published api.
*/
protected ScriptPropObj getScriptProp(String aPropertyName) {
ScriptPropObj scriptProp = null;
// validate the version and availability
Model model = getModel();
if (model != null)
scriptProp = super.getScriptProp(aPropertyName);
return scriptProp;
}
/**
* @exclude from published api.
*/
public ScriptTable getScriptTable() {
return ElementScript.getScriptTable();
}
/**
* Gets this element's text node child.
*
* To return the text node, set the returnDefault argument to true.
* If the text node is present, it is returned; otherwise
* the default text node is created and returned.
*
* To peek at the text node, set the peek argument to true.
* If the text 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.
*
* To create the text node, set the peek argument to false.
* If the text node is absent, a default text node is created
* and returned.
*
* If validation argument is true, and
* the validation fails, this method throws.
*
* @param bPeek whether to peek at the text node, or not.
* @param bReturnDefault whether to create a default text node, or not.
* @param bValidate whether to validate the text node, or not.
* @return the text node or null.
*/
public TextNode getText(boolean bPeek /* = false */,
boolean bReturnDefault /* = false */,
boolean bValidate /* = false */) {
//
// Ensure we are dealing with an test node.
//
if (bValidate) {
ChildReln validChild = getChildReln(XFA.TEXTNODETAG);
//
// validate the child relationship.
//
if (validChild == null) {
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidGetPropertyException);
message.format(getClassAtom());
message.format(XFA.TEXTNODE);
throw new ExFull(message);
}
}
else {
assert (getModel() != null);
}
TextNode child = (TextNode) locateChildByClass(XFA.TEXTNODETAG, 0);
if (child != null)
return child;
//
// Still not found? Create a default text node.
//
if (! bPeek || bReturnDefault) {
return (TextNode)defaultElement(XFA.TEXTNODETAG, 0);
}
return null;
}
/**
* @exclude from published api.
*/
final int getValidOccurrence(int eTag) {
ChildReln validChild = getChildReln(eTag);
if (validChild != null)
return validChild.getOccurrence();
else
return ChildReln.zeroOrMore;
}
/**
* Gets this element's XML name.
* @return the qualified name as an interned string.
*
* @exclude from published api.
*/
public String getXMLName() {
return mQName;
}
/**
* Sets this element's XML name.
*
* @exclude from published api.
*/
public void setXMLName(String name) {
mQName = name;
if (this instanceof DualDomNode)
((Element)((DualDomNode)this).getXmlPeer()).mQName = mQName;
setDirty();
}
/**
* @exclude from published api.
*/
public String getXPath(Map prefixList,
Element contextNode) {
final StringBuilder sPrefix = new StringBuilder();
Element common = null;
if (contextNode != null) {
common = getCommonAncestor(contextNode);
if (common != null) {
Element current = contextNode;
if (common != current) {
sPrefix.append("..");
current = current.getXFAParent();
}
// move up to the common node from the context node
while (common != current) {
sPrefix.append("/..");
current = current.getXFAParent();
}
}
}
// trace back up to the document node recording the path taken.
final StringBuilder sXPathExp = new StringBuilder();
Element current = this;
String sCurrentPrefix = "a";
// grab the current node name and added it to the XPath expression
while (current != common && !(current instanceof Document)) {
StringBuilder sPath = new StringBuilder("/");
// if (pCurrent instanceof Attribute) {
// sPath += '@'; // XPath char for attributes
// sPath += jfString(pCurrent->getNodeName()); //+= is more efficient than + operator
// }
// else
if (current instanceof Element) {
final Element element = (Element)current;
// get the info from the current node.
final String sNodePrefix = element.getPrefix();
final String sNodeNSURI = element.getNS();
// if the prefix is empty and the namespace isn't then the default namespace
// is defined thus create a prefix for the element.
if (sNodeNSURI != null && sNodeNSURI.length() != 0) {
// if there is a prefix defined for the namespace in the prefixlist then use it.
// else create a new one.
String sUsedPrefix = prefixList.get(sNodeNSURI);
if (sUsedPrefix != null) {
if (sNodePrefix.length() != 0) {
// generate a prefix.
while (true) {
if (prefixList.containsValue(sCurrentPrefix)) {
sCurrentPrefix = bumpString(sCurrentPrefix);
continue;
}
else
break;
}
sUsedPrefix = sCurrentPrefix;
}
else
sUsedPrefix = sNodePrefix;
prefixList.put(sNodeNSURI, sUsedPrefix);
}
if (sUsedPrefix != null && sUsedPrefix.length() != 0) {
sPath.append(sUsedPrefix);
sPath.append(':');
}
}
sPath.append(current.getLocalName());
// grab this nodes current likenamed index within it's parent
final int index = current.getIndex(false);
sPath.append('[');
sPath.append(index + 1);
sPath.append(']');
}
sXPathExp.insert(0, sPath);
current = current.getXFAParent();
}
sXPathExp.insert(0, sPrefix);
return sXPathExp.toString();
}
private static XPathFactory getXPathFactory() {
// Multiple initialization is benign
if (mXPathFactory == null)
mXPathFactory = XPathFactory.newInstance();
return mXPathFactory;
}
private Element getCommonAncestor(Element pOther) {
final List list1 = new ArrayList();
final List list2 = new ArrayList();
Element parent1 = this;
// populate list 1
while (parent1 != null) {
list1.add(parent1);
parent1 = parent1.getXFAParent();
}
// populate list 2
Element parent2 = pOther;
while (parent2 != null) {
list2.add(parent2);
parent2 = parent2.getXFAParent();
}
parent1 = list1.get(list1.size() - 1);
parent2 = list2.get(list2.size() - 1);
// there is no common root
if (parent1 != parent2)
return null;
Element common = null;
// walk down from the root node stop when there
// the nodes don't match anymore
while (parent1 == parent2) {
// set the common node
common = parent1;
// move down the tree
list1.remove(list1.size() - 1);
list2.remove(list2.size() - 1);
if (list1.size() == 0 || list2.size() == 0)
break;
// get the next descendant
parent1 = list1.get(list1.size() - 1);
parent2 = list2.get(list2.size() - 1);
}
// grab the common parent
return common;
}
String bumpString(String input) {
// bumps the string by one character
// ex "a"->"b" or "z"->"aa" or "bz"->"ca" ...
int nPos = input.length() - 1;
StringBuilder buffer = new StringBuilder(input);
while (nPos >= 0) {
if (buffer.charAt(nPos) == 'z') {
buffer.setCharAt(nPos, 'a');
nPos--;
}
else {
buffer.setCharAt(nPos, (char)(buffer.charAt(nPos) + 1));
return input;
}
}
input += 'a';
return input;
}
/**
* @exclude from published api.
*/
public Attribute getXsiNilAttribute() {
Element e = getXmlPeerElement();
for (int i = 0; i < e.nAttrs; i++) {
Attribute a = e.getAttr(i);
if (a.isXSINilAttr())
return a;
}
return null;
}
/**
* @exclude from published api.
*/
public void removeXsiNilAttribute() {
Element e = getXmlPeerElement();
for (int i = 0; i < e.nAttrs; i++) {
Attribute a = getAttr(i);
if (a.isXSINilAttr())
e.removeAttr(i);
}
}
/**
* @exclude from published api.
*/
public final void setXsiNilAttribute(String aValue) {
Element e = getXmlPeerElement();
for (int i = 0; i < e.nAttrs; i++) {
Attribute a = e.getAttr(i);
if (a.isXSINilAttr()) {
e.mAttrs[i] = new GenericAttribute(a.getName(), a.getLocalName(), a.getQName(), aValue, false);
return;
}
}
e.extendAttributes(new GenericAttribute(STRS.XSINS, STRS.NIL, STRS.XSINIL, aValue, false));
}
/**
* @exclude from published api.
*/
final public boolean inhibitPrettyPrint() {
return mbInhibitPrettyPrint;
}
/**
* @exclude from published api.
*/
final public void inhibitPrettyPrint(boolean bInhibit) {
mbInhibitPrettyPrint = bInhibit;
}
/**
* Inserts a child before a specific child in the child list.
* @param newChild
* the child to be inserted
* @param refNode
* the child to insert before
* @param bValidate
* if true, validate the insertion
*
* @exclude from published api.
*/
public void insertChild(Node newChild, Node refNode, boolean bValidate /* = true */) {
if (refNode != null)
assert refNode.getOwnerDocument() == getOwnerDocument();
if (bValidate) {
// just make sure that oNewChild is a valid child
isValidChild(newChild.getClassTag(), ResId.InvalidChildInsertException, true, false);
}
if (newChild == this || newChild == refNode)
throw new ExFull(ResId.HierarchyRequestException, newChild.getName());
final boolean bIsDefault = newChild.isDefault(false);
// don't notify of any changes getProperty makes, only do it when a default property is changed
// Mute also if dom peer is null - (watson 1824495 - crash due to notification with no dom peer)
try {
if (bIsDefault)
mute();
if (!newChild.isDefault(true))
makeNonDefault(false);
// remove the node from its parent first
if (newChild.getXMLParent() != null)
newChild.remove();
// Check to see if the new node is coming from a different model.
// If so, then we need to update it's model reference, as well as
// all of it's children nodes.
if (newChild.getModel() != getModel() ||
newChild.getOwnerDocument() != getOwnerDocument())
updateModelAndDocument(newChild);
boolean bAddedLocally = true;
if (refNode == null) {
appendChild(newChild);
bAddedLocally = false; // appendChild will take care of notifying
}
else {
if (this instanceof DualDomNode && newChild instanceof DualDomNode) {
Element domParentNode = (Element)((DualDomNode)this).getXmlPeer();
Node domNewNode = ((DualDomNode)newChild).getXmlPeer();
Node domRefNode = ((DualDomNode)refNode).getXmlPeer();
if (domNewNode.getModel() != getModel() ||
domNewNode.getOwnerDocument() != getOwnerDocument())
updateModelAndDocument(domNewNode);
if (domNewNode instanceof AttributeWrapper) {
AttributeWrapper wrapper = (AttributeWrapper)domNewNode;
Attribute attr = domParentNode.setAttribute(wrapper.getNS(), wrapper.getXMLName(), wrapper.getLocalName(), wrapper.getValue(), false);
AttributeWrapper xmlPeer = new AttributeWrapper(attr, domParentNode);
xmlPeer.setXfaPeer((Element)newChild);
((DualDomNode)newChild).setXmlPeer(xmlPeer);
}
else {
domParentNode.insertChild(domNewNode, domRefNode, false);
}
}
if (refNode == mFirstXMLChild) {
mFirstXMLChild = newChild;
newChild.setNextXMLSibling(refNode);
newChild.setXMLParent(this);
}
else {
Node child = mFirstXMLChild;
while (child != null) {
Node nextChild = child.getNextXMLSibling();
if (nextChild == refNode)
break;
child = nextChild;
}
if (child != null) {
child.setNextXMLSibling(newChild);
newChild.setNextXMLSibling(refNode);
newChild.setXMLParent(this);
}
}
}
// if (!bNotified && bNotify) {
// See comment in appendChild(node) about the bNotify parameter.
// If notification needs to become optional in this method,
// we'll add a bNotify parameter.
// notify child's peers that the parent has changed
if (bAddedLocally) {
//
// Check to see if the new node is coming from a different model.
// If so, then we need to update it's model reference, as well as
// all of it's children nodes.
//
if (newChild.getModel() != getModel())
updateModelAndDocument(newChild);
if (!newChild.isMute())
newChild.notifyPeers(Peer.PARENT_CHANGED, getClassAtom(), this);
// notify the peers that a child was added
if (!isMute())
notifyPeers(Peer.CHILD_ADDED, newChild.getClassAtom(), newChild);
}
}
finally {
if (bIsDefault)
unMute();
}
if (newChild instanceof Element)
getOwnerDocument().indexSubtree((Element)newChild, false);
setDirty();
}
/**
* Inserts a PI before a specific child node.
*
* @param aPiName - the processing instructions target name
* @param aPropName - the processing instructions property name
* @param sData - the processing instruction data
* @param refChildNode - the child node to insert before
* @exception InsertFailedException if the refNode is not a direct child node of this node
* @exclude from published api.
*/
void insertPI(String aPiName, String sPropName, String sData, Node refChild) {
// just make sure that poRefChild is a valid child
isValidChild(refChild.getClassTag(), ResId.InvalidChildInsertException, false, false);
if (refChild == this)
throw new ExFull(new MsgFormat(ResId.HierarchyRequestException,refChild.getName()));
if(refChild.getXFAParent() != this)
throw new ExFull(ResId.InsertFailedException);
// Add propName to the data
String sTemp = sPropName + " " + sData;
new ProcessingInstruction(this, refChild.getPreviousXMLSibling(), aPiName, sTemp);
// if (!oPI.isNull())
// {
// XFAPermsHandler::insertBefore(othisDomNode, oPI, oDomRefChild);
// UNDO(XFAInsertDomNodeUndo, (this, oPI));
// }
}
/**
* @exclude from published api.
*/
public boolean isContainer() {
return false;
}
/**
* Is this element here as a result of a fragment relationship?
* @return fragment state
*
* @exclude from published api.
*/
boolean isFragment() {
return mbIsFragment;
}
/**
* Set the fragment state of this node
* @param bFragment the fragment state
* @param bSetChildren if true, set this flag on all nested children
*
* @exclude from published api.
*/
public void isFragment(boolean bFragment, boolean bSetChildren/*false*/) {
mbIsFragment = bFragment;
// if setting to false, set parent node to false
if (!bFragment) {
Element parent = getXMLParent();
if ((parent != null) && (parent.isFragment() == true))
parent.isFragment(false, false);
}
if (!bSetChildren)
return;
Node child = getFirstXFAChild();
while (child != null) {
if (child instanceof Element)
((Element)child).isFragment(bFragment, true);
else if (child instanceof TextNode)
((TextNode)child).isFragment(bFragment);
child = child.getNextXFASibling();
}
for (int i = 0; i < getNumAttrs(); i++) {
setAttrProp(i, AttrIsFragment, bFragment);
}
}
/**
* Determine if this node is hidden or not.
* @return boolean hidden status.
*
* @exclude from published api.
*/
public final boolean isHidden() {
return mbIsHidden;
}
/**
* Set the hidden state of this node. Hidden nodes are not saved when
* the DOM is written out.
* @param bHidden
* the new hidden state.
*
* @exclude from published api.
*/
public final void isHidden(boolean bHidden) {
if (! bHidden && isHidden()) {
mbIsHidden = bHidden;
}
if (bHidden && ! isHidden()) {
mbIsHidden = bHidden;
// If we're hidden, then all of our children must be as well
Node child = getFirstXFAChild();
while (child != null) {
if (child instanceof Element) {
((Element) child).isHidden(true);
}
child = child.getNextXFASibling();
}
}
}
/**
* Specify whether this element can be indexed by id attribute.
*
* In the C++ implementation, some node classes (e.g., rich text)
* only exist as nodes in the XML DOM, so they are never indexed
* by id attribute.
* @return true if this Element can be indexed by id.
*
* @exclude from published API.
*/
protected boolean isIndexable() {
for (Element node = this; node != null; node = node.getXFAParent()) {
if (node.getClassTag() != XFA.INVALID_ELEMENT)
return node.childrenAreIndexable();
}
return true;
}
/**
* Indicates that the children of this node should be indexed by ID.
*
* Derived classes may override this to indicate that their children
* should not be indexed.
*
* @see #isIndexable()
* @exclude from published API.
*/
protected boolean childrenAreIndexable() {
return true;
}
/**
* @exclude from published api.
*/
public final boolean isIndexed() {
return mbIsIndexed;
}
/**
* Check if this node is a leaf node
* @return true if this node is a leaf, else false
*
* @exclude from published api.
*/
final public boolean isLeaf() {
return getFirstXFAChild() == null;
}
@Override
boolean isLikeNode(Node node, boolean bByName) {
if (this == node) // is it me?
return true;
// Make inexpensive check first.
if (!super.isLikeNode(node, bByName))
return false;
Element parent = getXFAParent();
if (parent != null) {
// must be the same type of nodes, eg prop and prop, child and child
if (parent.getSchemaType(getClassTag()) != parent.getSchemaType((node.getClassTag())))
return false;
}
return true;
}
/**
* Is this element's name namespaced.
* @return true if this element's name is namespaced.
*
* @exclude from published api.
*/
public boolean isNameSpaceAttr() {
return (getXMLName().startsWith(STRS.XMLPREFIX));
}
/**
* @exclude from published api.
*/
final public boolean isPropertySpecified(int ePropTag,
boolean bCheckProtos/* = true */,
int nOccurrence /* = 0 */) {
// Note that this method does not validate attributes or elements. If we're
// asked if an invalid property is specified, the result will be false,
// no exception thrown.
int eType = getSchemaType(ePropTag);
if (eType == ATTRIBUTE || eType == TEXT || eType == ELEMENT) {
return isSpecified(ePropTag, eType, bCheckProtos, nOccurrence);
}
return false;
}
/**
* @exclude from published api.
*/
final public boolean isPropertySpecified(String propertyName,
boolean bCheckProtos /* = true */,
int nOccurrence /* = true */) {
int ePropTag = XFA.getTag(propertyName.intern());
if (ePropTag == XFA.INVALID_ELEMENT) return false;
return isPropertySpecified(ePropTag, bCheckProtos, nOccurrence);
}
/**
* Check if a specified property is valid according to the schema.
* @param ePropTag
* The XFA tag (name) of the property to check for.
* @return not-null if valid. Boolean TRUE if property is an Element
*
* @exclude from published api.
*/
final public boolean isPropertyValid(int ePropTag) {
if (isValidAttr(ePropTag, false, null)) {
return true;
}
return isValidElement(ePropTag, false);
}
// string version of isPropertyValid. ***Less efficient than the int version
// ***.
final boolean isPropertyValid(String propertyName) {
int ePropTag = XFA.getTag(propertyName);
if (ePropTag == XFA.INVALID_ELEMENT) return false;
return isPropertyValid(ePropTag);
}
/**
* @exclude from published api.
*/
public boolean isSpecified(int eTag, boolean bCheckProtos/* true */,
int nOccurrence /* 0 */) {
int eType = getSchemaType(eTag);
return isSpecified(eTag,eType,bCheckProtos,nOccurrence);
}
/**
* @exclude from published api.
*/
public boolean isSpecified(int eTag, int eType, boolean bCheckProtos,
int nOccurrence) {
if (eType == INVALID)
return false;
else if (eType == ATTRIBUTE) {
String aPropName = getAtom(eTag);
return findSchemaAttr(aPropName) != -1;
}
if (getFirstXFAChild() == null)
return false;
if (nOccurrence > 0) { // performance tunning
// Validate the occurrence number
ChildReln validChild = getChildReln(eTag);
if (validChild != null) {
if (validChild.getMax() <= nOccurrence)
return false;
}
}
if (eType == ONEOF) {
if (nOccurrence > 0)
return false;
Node oneOf = getOneOfChild(true, false);
// must use get className because isSameClass does an impl ==
if (oneOf != null && oneOf.getClassTag() == eTag)
return true;
}
else { // (eType == TEXT || eType == ELEMENT || eType == CHILD)
Node child = locateChildByClass(eTag, nOccurrence);
return child != null && !child.isDefault(true);
}
return false;
}
/**
* @exclude from published api.
*/
final public boolean isSpecified(String sPropertyName, boolean bCheckProtos,
int nOccurrence) {
int ePropTag = XFA.getTag(sPropertyName);
if (ePropTag == XFA.INVALID_ELEMENT) return false;
return isSpecified(ePropTag, bCheckProtos, nOccurrence);
}
/**
* @see Node#isTransient(boolean, boolean)
* @exclude from published api.
*/
public final void isTransient(boolean bTransient,
boolean bSetChildren /* = false */) {
super.isTransient(bTransient, bSetChildren);
// Store setting in XML peer if present. For now at least, the setting
// is duplicated in the XFA node and the XML node (unlike C++).
if (this instanceof DualDomNode)
((DualDomNode)this).getXmlPeer().isTransient(bTransient, bSetChildren);
for (int i = 0; i < getNumAttrs(); i++) {
setAttrProp(i, AttrIsTransient, bTransient);
}
}
/**
* @exclude from published api.
*/
public boolean isTransparent() {
Element parent = getXFAParent();
if (parent != null) {
// ALL Properties and oneof children will be referenced by there classname and are not transparent;
int eType = parent.getSchemaType(getClassTag());
if (eType == TEXT || eType == ELEMENT || eType == ONEOF) {
return false;
}
}
if (isContainer()) {
if (getName() == "")
return true;
}
return mbTransparent;
}
/**
* Determine if a specified attribute tag is valid for this node.
* @param eTag
* the XFA tag to check
* @return true if valid.
*
* @exclude from published api.
*/
public boolean isValidAttr(int eTag, boolean bReport /* = false */, String value /* = null */) {
//
// generic XFA nodes have no schema, so allow all attributes
// By default all attributes are allowed and all children/elements
// are disallowed. This allows us to use getProperty() to return
// attributes, and still allows us to access elements via the children
// list.
//
if (getElementClass() == XFA.INVALID_ELEMENT)
return false;
Model model = getModel();
// guard against asking for properties on invalid nodes.
if (model == null) {
assert(false); // invalid node if the model is null
return false;
}
// check against schema
AttributeInfo info = getNodeSchema().getAttributeInfo(eTag);
if (info == null)
return false;
// by default use the attribute version and availability
int nVersionIntroduced = info.getVersionIntroduced();
int nAvailability = info.getAvailability();
// if we have an enum, the version introduced of the value could be less than the attribute
// this was done to allow new attributes to be added in a backwards compatible way.
if (value != null) {
EnumValue eTest = null;
final Attribute defaultAttribute = info.getDefault();
if (defaultAttribute instanceof EnumValue) {
// Get default so we can get the type to create
// an enum from the attribute.
try {
eTest = (EnumValue)defaultAttribute.newAttribute(value);
}
catch (ExFull ex) {
}
}
if (eTest != null) {
// we have a valid enum, so don't look at the attribute's version number,
// only look at the version for the enumerated value
nVersionIntroduced = eTest.getAttr().getVersionIntro();
nAvailability = eTest.getAttr().getAvailability();
}
}
if (!model.validateUsage(nVersionIntroduced, nAvailability, bReport)) {
if (bReport) {
MsgFormatPos reason = new MsgFormatPos(ResId.InvalidAttributeVersionException);
reason.format(getAtom(eTag));
reason.format(getClassAtom());
ExFull ex = new ExFull(reason);
if (model.validateUsageFailedIsFatal(nVersionIntroduced, nAvailability))
throw ex;
else
model.addErrorList(ex, LogMessage.MSG_WARNING, this);
}
else if (model.validateUsageFailedIsFatal(nVersionIntroduced, nAvailability))
return false;
// when loading make sure we report the correct value
if (model.isLoading())
return false;
}
if (bReport) {
// check if deprecated
if (info.getVersionDeprecated() != 0) {
int nTargetVer = model.getCurrentVersion();
if (info.getVersionDeprecated() <= nTargetVer) {
MsgFormatPos reason = new MsgFormatPos(ResId.DeprecatedAttributeException, getAtom(eTag));
reason.format(getClassAtom());
ExFull ex = new ExFull(reason);
model.addXMLLoadErrorContext(this, ex);
}
}
}
return true;
}
/**
* @param eTag the class tag of the node that will be or has been appended
* @param nError
* @param bBeforeInsert if true
, then the child has not yet been inserted
* into the child list of this element.
* @param bOccurrenceErrorOnly
* @return true
if eTag is a valid child.
* @exclude from published api.
*/
public boolean isValidChild(int eTag,
int nError /* = 0 */,
boolean bBeforeInsert /* = false */,
boolean bOccurrenceErrorOnly /* = false */) {
//
// generic nodes don't have a schema, so return a default value true.
// (Changed from C++ version where default was false)
//
if (getElementClass() == XFA.INVALID_ELEMENT)
return true;
Model model = getModel();
if (model == null) {
assert(false); // invalid node if the model is null
return false;
}
// Make sure this XFA node can have children
ChildRelnInfo info = getNodeSchema().getChildRelnInfo(eTag);
if (info == null) {
if (nError != 0 && !bOccurrenceErrorOnly) {
MsgFormatPos message = new MsgFormatPos(nError, getClassAtom());
String name = null;
if (eTag == XFA.INVALID_ELEMENT)
name = getClassName();
else
name = getAtom(eTag);
message.format(name);
throw new ExFull(message);
}
return false;
}
// check the version
if (!model.validateUsage(info.getVersionIntroduced(), info.getAvailability(), true)) {
MsgFormatPos reason = new MsgFormatPos(ResId.InvalidChildVersionException, getAtom(eTag));
reason.format(getClassAtom());
// when loading make sure we don't say someting is valid when it isn't
// if we do then we may append a node that should be there.
if (model.validateUsageFailedIsFatal(info.getVersionIntroduced(), info.getAvailability()) || model.isLoading()) {
if (nError != 0 && !bOccurrenceErrorOnly) {
MsgFormatPos message = new MsgFormatPos(nError, getClassAtom());
message.format(getAtom(eTag));
ExFull error = new ExFull(message);
error.insert(new ExFull(reason), true);
throw error;
}
return false;
}
else // warn
model.addErrorList(new ExFull(reason), LogMessage.MSG_WARNING, this);
}
if (model.isLoading()) {
// check if deprecated
if (info.getVersionDeprecated() != 0) {
int nTargetVer = model.getCurrentVersion();
if (info.getVersionDeprecated() <= nTargetVer) {
MsgFormatPos reason = new MsgFormatPos(ResId.DeprecatedChildException, getAtom(eTag));
reason.format(getClassAtom());
ExFull ex = new ExFull(reason);
model.addXMLLoadErrorContext(this, ex);
}
}
}
ChildReln validChild = info.getRelationship();
// If we are here we need to make sure that this is
// a valid XFA tag taking into account the permitted
// occurrence for this tag
if (validChild.getOccurrence() == ChildReln.zeroOrOne) {
// we are only interested in className
int index = 0;
// If we're validating after the child has been inserted, then
// we're checking if there are two instances
if (!bBeforeInsert)
index++;
if (locateChildByClass(eTag, index) == null) {
// No nodes of this type yet...OK
return true;
}
if (nError != 0) {
MsgFormatPos e1 = new MsgFormatPos(nError, getClassAtom());
e1.format(getAtom(eTag));
ExFull error = new ExFull(e1);
ExFull message2 = new ExFull(
ResId.OccurrenceViolationException, getAtom(eTag));
error.insert(message2, true);
throw error;
}
// We already have a node of this type
return false;
} else if (getFirstXFAChild() != null
&& validChild.getOccurrence() == ChildReln.oneOfChild) {
//
// if there are existing OneOf children, this element isn't legal
//
// If we're validating after insert, check for a second oneOf child
boolean bOneOfAllowed = !bBeforeInsert;
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int childTag = child.getClassTag();
if (childTag != XFA.INVALID_ELEMENT &&
getValidOccurrence(childTag) == ChildReln.oneOfChild) {
if (bOneOfAllowed) {
bOneOfAllowed = false; // Next one found is an error
}
else {
if (nError != 0) {
MsgFormatPos message = new MsgFormatPos(nError, getClassName());
message.format(getAtom(eTag));
ExFull error = new ExFull(message);
ExFull message2 = new ExFull(
ResId.OccurrenceViolationException, XFA.getAtom(eTag));
error.insert(message2, true);
throw error;
}
return false;
}
}
}
return true;
}
// ZERO_OR_MORE, ONE_OR_MORE
return true;
}
/**
* @exclude from published api.
*/
public boolean isValidElement(int eTag, boolean bReport /* = false */) {
Model model = getModel();
if (model == null) {
assert(false); // invalid node if the model is null
return false;
}
// Check the most common case first (for performance)
ChildRelnInfo info = getNodeSchema().getChildRelnInfo(eTag);
if (info != null) {
if (!model.validateUsage(info.getVersionIntroduced(), info.getAvailability(), bReport)) {
if (bReport) {
MsgFormatPos reason = new MsgFormatPos(ResId.InvalidChildVersionException, getAtom(eTag));
reason.format(getClassAtom());
if (model.validateUsageFailedIsFatal(info.getVersionIntroduced(), info.getAvailability()))
throw new ExFull(reason);
else // warn
model.addErrorList(new ExFull(reason), LogMessage.MSG_WARNING, this);
}
else if (model.validateUsageFailedIsFatal(info.getVersionIntroduced(), info.getAvailability()))
return false;
}
if (bReport) {
// check if deprecated
if (info.getVersionDeprecated() != 0) {
int nTargetVer = model.getCurrentVersion();
if (info.getVersionDeprecated() <= nTargetVer) {
MsgFormatPos reason = new MsgFormatPos(ResId.DeprecatedChildException, getAtom(eTag));
reason.format(getClassAtom());
ExFull ex = new ExFull(reason);
model.addXMLLoadErrorContext(this, ex);
// #if _DEBUG
// #ifdef WIN32
// jfString * sMsg = oEx.String();
// jfString sOut(*sMsg);
//
// sOut = "\n\n** " + sOut + "\n";
// OutputDebugString((const char*)sOut);
// delete sMsg;
// #endif
// #endif
}
}
}
return true;
}
//
// generic XFA nodes have no schema, so return a default value true
// Note! this default has been changed in Java! Was false in C++
// JB August 12, 2005
//
if (getClassTag() == XFA.INVALID_ELEMENT) {
return true;
}
//
// XFA App Model has no schema, so return a default value false
//
if (isSameClass(XFA.XFATAG)) {
if (eTag == XFA.DSIGDATATAG || eTag == XFA.PACKETTAG)
return true;
return false;
}
return false;
}
/**
* Loads and appends the specified XML fragment (or document)
* to this element.
* @param is
* the input stream of XML fragment.
* @param bIgnoreAggregatingTag
* ignore the root node of the XML fragment, when true,
* in which case, the children of the root node will be
* appended to this element. Append the root node of the
* XML fragment to this element, when false.
* @param bReplaceContent
* replace the content of this element with the content of
* the root node of the XML fragment, when true.
*/
public void loadXML(InputStream is,
boolean bIgnoreAggregatingTag /* = true */,
boolean bReplaceContent /* = false */) {
loadXML(is, bIgnoreAggregatingTag, bReplaceContent ? ReplaceContent.AllContent : ReplaceContent.None);
}
/**
* Loads and appends the specified XML fragment (or document)
* to this element.
* @param is
* the input stream of XML fragment.
* @param bIgnoreAggregatingTag
* ignore the root node of the XML fragment, when true,
* in which case, the children of the root node will be
* appended to this element. Append the root node of the
* XML fragment to this element, when false.
* @param eReplaceContent
* specifies handling of existing node content.
*
* @exclude from published api.
*/
public void loadXML(InputStream is,
boolean bIgnoreAggregatingTag /* = true */,
ReplaceContent eReplaceContent /* = ReplaceContent.None */) {
Model model = getModel();
assert (model != null);
model.loadXMLImpl(this, is, bIgnoreAggregatingTag, eReplaceContent);
}
/**
* @see Node#makeDefault()
* @exclude from published api.
*/
public void makeDefault() {
super.makeDefault();
// Update the attributes
int n = getNumAttrs();
for (int i = 0; i < n; i++) {
setAttrProp(i, AttrIsDefault, true);
}
}
/**
* @see Node#makeNonDefault(boolean)
* @exclude from published api.
*/
public void makeNonDefault(boolean bRecursive /* = false */) {
super.makeNonDefault(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();
if (isFragment()) {
// If we're setting a property which is currently a result of an external
// proto (a.k.a. fragment) load, then we're effectively setting a local property
// override in the referencing doc and therefore need to clear the fragment flag
// from ancestors to allow saving of the override property.
isFragment(false, false);
// Since fragments can cause dom nodes to have both fragment and transient flags
// (loading with externalProtosAreTransient), clearing a 'fragment' flag (e.g. in order to
// override a fragment property while editing in Designer) then the transient flag also
// needs to clear or else the override property will be lost on save.
isTransient(false, false);
}
}
/**
* @exclude from published api.
*/
public void setDefaultFlag(boolean bDefaultNode, boolean bSetChildren) {
if (isDefault(false) != bDefaultNode || bSetChildren) {
super.setDefaultFlag(bDefaultNode, bSetChildren);
// Update the attributes
int n = getNumAttrs();
for (int i = 0; i < n; i++)
setAttrProp(i, AttrIsDefault, false);
}
}
/**
* @exclude from published api.
*/
public Attribute newAttribute(int eTag, String value) {
//
// Generic nodes don't have a schema, so return an StringAttr
//
if (getClassTag() == XFA.INVALID_ELEMENT || isSameClass(XFA.DSIGDATATAG))
return new StringAttr("", value);
return getModel().getSchema().newAttribute(eTag, value, getClassTag());
}
/**
* get an attribute value only if it exists. NON validating
*
* @exclude from published api.
*/
final public Attribute peekAttribute(int eAttributeTag) {
return getAttribute(eAttributeTag, true, false);
}
/**
* get an element only if it exists NON validating READ ONLY VERSION
*
* @exclude from published api.
*/
final public Element peekElement(int eElementTag,
boolean bReturnDefault /* = false */, int nOccurrence/* = 0 */) {
return getElement(eElementTag, true, nOccurrence, bReturnDefault, false);
}
/**
* @deprecated Use {@link #getOneOfChild(boolean, boolean)
* getOneOfChild(true, bReturnDefault)} instead.
*
* @exclude from published api.
*/
final public Node peekOneOfChild(boolean bReturnDefault /* = false */) {
return( getOneOfChild(true, bReturnDefault));
}
/**
* @exclude from published api.
*/
final public Object peekProperty(int ePropTag, int nOccurrence) {
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Element#peekProperty(int, int)");
}
/**
* @exclude from published api.
*/
final public Object peekProperty(String propertyName, int nOccurrence) {
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Element#peekProperty(String, int)");
}
/**
* @see Node#postSave()
*
* @exclude from published api.
*/
public void postSave() {
}
/**
* @see Node#preSave(boolean)
*
* @exclude from published api.
*/
public void preSave(boolean bSaveXMLScript /* = false */) {
// do nothing
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public final void removeAttr(int index) {
assert index >= 0 || index < getNumAttrs();
final Element e = getXmlPeerElement();
final Attribute attr = e.getAttr(index);
if (getOwnerDocument() != null &&
getOwnerDocument().isId(getNSInternal(), getLocalName(), attr.getNS(), attr.getLocalName()))
getOwnerDocument().deindexNode(this, false);
if (attr.getLocalName() == XFA.NAME) {
maName = null;
}
if (index != e.nAttrs - 1) {
System.arraycopy(e.mAttrs, index + 1, e.mAttrs, index, e.nAttrs - index - 1);
System.arraycopy(e.mAttrProperties, index + 1, e.mAttrProperties, index, e.nAttrs - index - 1);
}
e.nAttrs--;
e.mAttrs[e.nAttrs] = null;
e.mAttrProperties[e.nAttrs] = 0;
// JavaPort: This is a slightly different from C++ in that the 3rd argument
// is this Element, instead of the Attribute.
notifyPeers(ATTR_CHANGED, attr.getLocalName(), this);
setDirty();
}
/**
* Remove an attribute as specified by the combination of URI and name.
* @param URI
* the namespace for this attribute. If null, don't worry about
* namespace matching. If non-null, this String must be interned.
* @param name
* the name of the attribute. This String must be interned.
*
* @exclude from published api.
*
*/
@FindBugsSuppress(code="ES")
public final void removeAttr(String URI, String name) {
if (Assertions.isEnabled) assert (URI == null || URI == URI.intern());
if (Assertions.isEnabled) assert (name == name.intern());
int attr = findAttr(URI, name);
if (attr != -1)
removeAttr(attr);
}
/**
* Removes a child from the child list
*
* @param child
* the node to be removed
*
* @exclude from published api.
*/
public final void removeChild(Node child) {
if (child == null)
return;
Element parent = getXFAParent();
// if (parent == null)
// throw new ExFull(ResId.ObjectNotFoundException);
// Mark this node as dirty
setDirty();
if (child instanceof Element) {
if (getOwnerDocument() != null)
getOwnerDocument().deindexSubtree((Element)child, false);
}
// Watson 1450763. Reset the event manager on nodes that are removed,
// but only if we are truly removing the child (hence if (bChangeRefCount)).
EventManager.resetEventTable(child.getEventTable(false));
// Find the previous child
Node previous = null;
Node next = null;
for (Node iter = getFirstXMLChild(); iter != null; ) {
next = iter.getNextXMLSibling();
if (next == child) {
previous = iter;
break;
}
iter = next;
}
if (previous == null && getFirstXMLChild() != child)
throw new ExFull(ResId.RemoveFailedException); // child not found!
// remove poChild
next = child.getNextXMLSibling();
if (previous != null) {
previous.setNextXMLSibling(next);
}
else {
setFirstChild(next);
}
if (child instanceof DualDomNode) {
DualDomNode dualDomChild = (DualDomNode)child;
Node peer = dualDomChild.getXmlPeer();
if (peer instanceof AttributeWrapper) {
AttributeWrapper attr = (AttributeWrapper)peer;
Element elem = (Element)((DualDomNode)parent).getXmlPeer();
elem.removeAttr(attr.getNS(), attr.getLocalName());
}
else if (peer != null) {
Element parent2 = peer.getXMLParent();
if (parent2 != null) {
parent2.removeChild(peer);
}
}
}
// Remove poChild's references to tree
child.setNextXMLSibling(null);
child.setXMLParent(null);
setChildListModified(true);
}
/**
* Remove all processing instruction based on the aPiName.
* @param aPiName
* the processing instructions target name
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
final public void removePI(String aPiName) {
assert (aPiName != null);
for (Node node = getFirstXMLChild(); node != null;) {
if (node instanceof ProcessingInstruction) {
ProcessingInstruction pi = (ProcessingInstruction) node;
if (pi.getName() == aPiName) {
// UNDO(XFARemoveDomNodeUndo, (this, oNode));
node = node.getNextXMLSibling();
pi.getXMLParent().removeChild(pi);
continue;
}
}
node = node.getNextXMLSibling();
}
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
final public void removePI(String aPiName, String sPropName) {
assert (aPiName != null);
Node node = getFirstXMLChild();
while (node != null) {
if (node instanceof ProcessingInstruction) {
ProcessingInstruction pi = (ProcessingInstruction) node;
if (pi.getName() == aPiName) {
String sNodeValue = pi.getData();
String[] vals = sNodeValue.split(" ");
if (vals[0].equals(sPropName)) {
node = node.getNextXMLSibling();
pi.getXMLParent().removeChild(pi);
continue;
}
}
}
node = node.getNextXMLSibling();
}
}
/**
* Remove all #text children that consist of whitespace
*
* @exclude from published api.
*/
public final void removeWhiteSpace() {
boolean bSetLoading = getWillDirty();
if (bSetLoading)
setWillDirty(false);
try {
Node nextSibling;
for (Node childNode = getFirstXMLChild(); childNode != null; childNode = nextSibling) {
nextSibling = childNode.getNextXMLSibling();
if (childNode instanceof Chars) {
if (((Chars) childNode).isXMLSpace()) {
// Remove without adding to the orphan list to save some
// processing.
removeChild(childNode);
}
}
}
}
finally {
if (bSetLoading)
setWillDirty(true);
}
}
/**
* @exclude from published api.
*/
public Node replaceChild(Node newChild, Node oldChild) {
if (oldChild == null)
return null;
Node nextChild = oldChild.getNextXMLSibling();
//
// important to remove old child first to avoid exception
// caused by having two children of the document node
//
removeChild(oldChild);
insertChild(newChild, nextChild, false);
// setDirty(); // insertChild will dirty
return oldChild;
}
/**
* @exclude from published api.
*/
public void resetPostLoadXML() {
// default implementation does nothing
}
/**
* @exclude from published api.
*/
protected void resolveAndEnumerateChildren(NodeList properties, NodeList children, boolean bAllProperties, boolean bFirstDefaultOnly) {
//
// collect the element-based properties based on schema
//
SchemaPairs validChildren = getNodeSchema().getValidChildren();
for (int i = 0; validChildren != null && i < validChildren.size(); i++) {
int eTag = validChildren.key(i);
ChildReln childR = (ChildReln)validChildren.value(i);
int nMax = childR.getMax();
if (nMax != ChildReln.UNLIMITED && childR.getOccurrence() != ChildReln.oneOfChild) {
int nProtoIndex = 0;
while (nProtoIndex < nMax) {
Node child;
if (eTag == XFA.TEXTNODETAG)
child = getText(true, false, false);
else
child = getElement(eTag, true, nProtoIndex, false, false);
if (child == null && bAllProperties) {
// watson bug 1765397, don't create any more than one default
if (bFirstDefaultOnly && nProtoIndex == 1)
break;
// watson bug 1614438 create the default without appending it to the doc
// to avoid unwanted nodes on save
child = createDefaultElement(eTag, nProtoIndex);
}
if (child != null) {
properties.append(child);
// move the index and ensure it is valid
nProtoIndex++;
}
else
break; // null node or not a protoable node, in this case stop
}
}
}
// add in the [0..n] and [1..n] children
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int eType = getSchemaType(child.getClassTag());
if (eType == CHILD) {
children.append(child);
}
}
}
/**
* @exclude from published api.
*/
protected NodeList enumerateChildren() {
// collect all element-based properties and children ([0..1], [0..n] and [1..n])
NodeList children = null;
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int eType = getSchemaType(child.getClassTag());
if (eType == CHILD) {
if (children == null)
children = new ArrayNodeList();
children.append(child);
}
}
return children;
}
/**
* @exclude from published api.
*/
protected NodeList enumerateProperties() {
// collect all element-based properties and children ([0..1], [0..n] and [1..n])
NodeList properties = null;
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int eType = getSchemaType(child.getClassTag());
if (eType == TEXT || eType == ELEMENT) {
if (properties == null)
properties = new ArrayNodeList();
properties.append(child);
}
}
return properties;
}
/**
* Construct a list of element-based properties and children. The list is fully
* resolved (ie: it will have children from any prototypes in it).
* @exclude from published api.
*/
public NodeList resolveAndEnumerateChildren(boolean bAllProperties /* = false */, boolean bFirstDefaultOnly /* = false */) {
NodeList list = new ArrayNodeList();
Node oneOfChild = getOneOfChild(true, false);
resolveAndEnumerateChildren(list, list, bAllProperties, bFirstDefaultOnly);
if (oneOfChild != null)
list.append(oneOfChild);
else if (bAllProperties) {
// add the one of child
int eTag = getDefaultOneOfTag();
if (eTag != XFA.SCHEMA_DEFAULTTAG) {
Node defaultElement = getModel().createElement(eTag, null);
list.append(defaultElement);
}
}
return list;
}
/**
* @exclude from public api.
*/
@FindBugsSuppress(code="ES")
public SOMParser.SomResultInfo resolveNodeCreate(String somNodesInput,
int eAction,
boolean bLeaf /* = true */ ,
boolean bDefault /* = false */,
boolean bNoProperties /* = false */) {
// Watson 1426596. A SOM expression of the form !extra is syntactically
// equivalent to xfa.datasets.extra. To avoid trying to create a node
// called "!extra", expand it here.
String somNodes;
if (somNodesInput.startsWith("!")) {
somNodes = "xfa.datasets." + somNodesInput.substring(1);
}
else {
somNodes = somNodesInput;
}
List result = new ArrayList();
SOMParser parser = new SOMParser(null);
parser.setOptions(true, true, bNoProperties);
parser.resolve(this, somNodes, null, result, null);
//
// remove any non xfa node results
//
for (int i = result.size(); i > 0; i--) {
if (! (result.get(i - 1).object instanceof Node)) {
result.remove(i - 1);
}
}
//
// if it has a * section and resolves to a node ~= appendaction
//
int nFoundAt = somNodes.indexOf('*');
boolean bHasStar = (nFoundAt >= 0);
if (bHasStar && result.size() == 1 && eAction == CREATEACTION)
eAction = APPENDACTION;
if (eAction == APPENDACTION ||
(result.size() == 0 && eAction == CREATEACTION)) {
String sNodesExist = null;
Element parent = this;
nFoundAt = 0;
int nNextFoundAt = 0;
if (somNodes.length() > 2 && somNodes.charAt(0) == '$' && somNodes.charAt(1) != '.') {
// This is something like $data, so let the model handle it.
// This is because any subsequent nodes must be created by
// the proper model.
// First check to see if the specified model exists yet.
// If not, create it.
nNextFoundAt = somNodes.indexOf('.', 1);
if (nNextFoundAt < 0)
nNextFoundAt = somNodes.length();
//
// eg. "$data"
//
String sShortCutName = somNodes.substring(0, nNextFoundAt);
parent = (Element) resolveNode(sShortCutName, false, false, false);
if (parent == null) {
//
// Create a model such as $data if there's an
// installed factory for it.
// Strip off the "$", e.g., "data"
//
String sModelAlias = sShortCutName.substring(1);
AppModel appModel = getAppModel();
List factories = appModel.factories();
for (int i = 0; i < factories.size(); i++) {
ModelFactory factory = factories.get(i);
if (factory.rootName().equals(sModelAlias)) {
factory.createDOM((Element)appModel.getXmlPeer());
parent = (Element) resolveNode(sShortCutName, false, false, false);
assert (parent != null); // we just created it...
break;
}
}
if (parent == null) {
MsgFormatPos message = new MsgFormatPos(ResId.CantCreateSOMExpression);
message.format(somNodes);
throw new ExFull(message);
}
}
//
// If the node is not in our model, pass control over
// to that node so that subsequent
// nodes are created by the correct model.
//
if (parent.getModel() != getModel())
return parent.resolveNodeCreate(somNodes, eAction, bLeaf, false, false);
}
boolean bLookUp = true;
// watson bug 1325319
// use XFASOMParser::findDot instead of jfString::CharInString because
// the XFASOMParser has logic to handle escaped chars
while ((nNextFoundAt = SOMParser.findDot(somNodes, nFoundAt + 1)) > 0) {
sNodesExist = somNodes.substring(0, nNextFoundAt);
//
// if last * section, and eAction = APPENDACTION,
// then break so we create a new instance
//
if (bHasStar && eAction == APPENDACTION &&
somNodes.indexOf('*', nNextFoundAt) < 0 &&
somNodes.indexOf('*', nFoundAt) >= 0) {
sNodesExist = somNodes.substring(0, nFoundAt);
break;
}
Element tmpNode = parent;
parent = (Element) resolveNode(sNodesExist, true, true, false);
if (parent == null) {
sNodesExist = somNodes.substring(0, nFoundAt);
parent = tmpNode;
break;
}
bLookUp = false;
nFoundAt = nNextFoundAt;
}
int nStart = 0;
if (sNodesExist != null && sNodesExist.length() > 0)
nStart = sNodesExist.length() + 1;
String sNodesCreate = somNodes.substring(nStart, somNodes.length());
boolean bIsLeaf = false;
// Create the node(s)
while (sNodesCreate.length() > 0) {
String aNewNode = null;
// watson bug 1325319
// use XFASOMParser::findDot instead of jfString::CharInString because
// the XFASOMParser has logic to handle escaped chars
if ((nFoundAt = SOMParser.findDot(sNodesCreate, 0)) > 0) {
aNewNode = sNodesCreate.substring(0, nFoundAt).intern();
sNodesCreate = sNodesCreate.substring(aNewNode.length() + 1, sNodesCreate.length());
}
else {
aNewNode = sNodesCreate.intern();
sNodesCreate = "";
if (bLeaf)
bIsLeaf = true;
}
if (aNewNode == "$")
continue;
int absNumToCreate = 0;
int nBraceStart = aNewNode.indexOf('[');
if (nBraceStart >= 0) {
nFoundAt = aNewNode.indexOf(']');
if (nFoundAt >= 0) {
String sNum = aNewNode.substring(nBraceStart + 1, nFoundAt);
// * will return 0;
try {
absNumToCreate = Integer.parseInt(sNum);
} catch (NumberFormatException e) {
nFoundAt = 0;
}
}
else {
MsgFormatPos message = new MsgFormatPos(ResId.CantCreateSOMExpression);
message.format(somNodes);
throw new ExFull(message);
}
aNewNode = aNewNode.substring(0, nBraceStart).intern();
}
// watson bug 1325319
// we must convert escaped "\." chars back to "."
if (aNewNode.indexOf("\\.") != -1)
aNewNode = SOMParser.unescapeSomName(aNewNode).intern();
// FIND context
while (bLookUp && parent != null && !parent.canCreateChild(bIsLeaf, aNewNode)) {
parent = parent.getXFAParent();
}
bLookUp = false;
// no context so throw error
if (parent == null) {
MsgFormatPos message = new MsgFormatPos(ResId.CantCreateSOMExpression);
message.format(somNodes);
throw new ExFull(message);
}
// compute the num of nodes to create
boolean bFoundTransient = false;
for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
if (! (child instanceof Element))
continue;
Element element = (Element)child;
if (element.getName() == aNewNode) {
// If the node is marked transient, then we
// will reset the flag and use this node.
if (element.isDefault(false)) {
element.makeNonDefault(false);
parent = element;
bFoundTransient = true;
break;
}
if (absNumToCreate > 0)
absNumToCreate--;
}
}
if (bFoundTransient)
continue;
int numToCreate = 1 + absNumToCreate;
int eTag;
if (! parent.canCreateChild(bIsLeaf, aNewNode)
&& numToCreate == 1
&& (eTag = XFA.getTag(aNewNode)) != XFA.INVALID_ELEMENT
&& parent.isValidAttr(eTag, false, null)) {
Arg initValue = new Arg();
return new SOMParser.SomResultInfo(parent, aNewNode,
0, initValue);
}
Element newNode = null;
while (numToCreate > 0 && parent.canCreateChild(bIsLeaf, aNewNode)) {
newNode = (Element) parent.createChild(bIsLeaf, aNewNode);
numToCreate--;
if (newNode == null) {
MsgFormatPos message = new MsgFormatPos( ResId.CantCreateSOMExpression);
message.format(somNodes);
throw new ExFull(message);
}
if (bDefault)
newNode.makeDefault();
}
parent = newNode;
}
return new SOMParser.SomResultInfo(parent);
}
if (result.size() != 1) // more than one element returned
throw new ExFull(ResId.SOMTypeException);
return (SOMParser.SomResultInfo) result.get(0);
}
/**
* Restore a delta for this Element
* @param delta the delta to restore.
* @exclude
*/
void restoreDelta(Element delta) {
// Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
// Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
Element parent = getXFAParent();
if (parent != null) {
// remove the delta from the parent
delta.remove();
// insert the delta before this node
parent.insertChild(delta, this, false);
delta.makeNonDefault(false);
// remove this node from the parent
remove();
}
}
/**
* Serializes this element (and all its children) to an output stream.
* @param outFile an output stream.
* @param options the XML save options
*/
public void saveXML(OutputStream outFile, DOMSaveOptions options) {
saveXML(outFile, options, false);
}
/**
* Serializes this element (and all its children) to an output stream.
* @param outFile an output stream.
* @param options the XML save options
*
* @exclude from published api.
*/
public void saveXML(OutputStream outFile, DOMSaveOptions options, boolean bSaveXMLScript /* = false */) {
Document doc = getOwnerDocument();
AppModel appModel = getAppModel();
if (appModel != null)
appModel.preSaveXML();
preSave(bSaveXMLScript);
if (options == null) {
options = new DOMSaveOptions();
options.setSaveTransient(true);
}
doc.saveAs(outFile, this, options);
}
/**
* @exclude from published api.
*/
public void saveFilteredXML(NodeList nodeList, OutputStream outFile, DOMSaveOptions options) {
Element clonedRoot = filterClone(nodeList);
if (clonedRoot != null) {
clonedRoot.saveXML(outFile, options);
}
}
/**
* this function is used to look over node and its siblings to
* see if there is anything but empty text nodes
* this function is used in conjunction with saveToStreamHelper
* to determine if we should save out a self contained element tag
* or use a start and end tag.
*/
private boolean isElementEmpty(DOMSaveOptions options) {
for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
if (child instanceof Element) {
if (options.canBeSaved(((Element)child).isFragment(), child.isDefault(false), child.isTransient()))
return false;
}
else if (child instanceof Chars) {
Chars chars = (Chars)child;
if (!StringUtils.isEmpty(chars.getText()) &&
options.canBeSaved(false, child.isDefault(false), child.isTransient()))
return false;
}
else {
return false; // comment or pi
}
}
return true;
}
/**
* @see Node#serialize(OutputStream, DOMSaveOptions, int, Node)
*
* @exclude from published api.
*/
public void serialize(OutputStream outStream, DOMSaveOptions options, int level, Node prevSibling) throws IOException {
if (!options.canBeSaved(isFragment(), isDefault(false), isTransient()))
return;
final int eDisplayFormat = options.getDisplayFormat();
// Hopelessly twisted logic copied from C++
if (level != 0 || prevSibling != null) {
if (options.getDisplayFormat() == DOMSaveOptions.PRETTY_OUTPUT) {
if ((prevSibling == null) ||
!(prevSibling instanceof Chars) ||
((Chars) prevSibling).isXMLSpace())
options.writeIndent(outStream, level);
}
else if ((level == 0) &&
(options.getFormatOutside() ||
options.getDisplayFormat() == DOMSaveOptions.SIMPLE_OUTPUT)) {
outStream.write(Document.MarkupReturn);
}
}
outStream.write(Document.MarkupStartTag);
final String qName = getXMLName();
final String ns = getNSInternal();
final String prefix = getPrefix();
outStream.write(qName.getBytes(Document.Encoding));
// For consistency with C++, attributes are emitted in the order:
// 1. namespace declarations for attributes in a non-default namespace (if needed)
// 2. attribute values (including any namespace declarations)
// 3. namespace declaration for this element (if needed)
//
// Note that if this Element has been canonicalized, any namespace attributes will
// already be present (and in the correct order), so no additional attributes are
// needed (as in #1 and #3), so the canonicalized attributes are not disturbed.
final int nAttrs = getNumAttrs();
for (int i = 0; i < nAttrs; i++) {
final Attribute a = getAttr(i);
if (a.isNameSpaceAttr() || a.getPrefix() == "")
continue;
addNamespaceDef(outStream, options, this, a.getPrefix(), a.getNS());
}
saveAttributesToStream(outStream, options);
addNamespaceDef(outStream, options, this, prefix, ns);
if (isElementEmpty(options)) {
if (options.getDisplayFormat() == DOMSaveOptions.SIMPLE_OUTPUT) {
outStream.write(Document.MarkupReturn);
}
if (options.getExpandElement()) {
outStream.write(Document.MarkupEndTag);
outStream.write(Document.MarkupCloseTag);
outStream.write(qName.getBytes(Document.Encoding));
if (options.getDisplayFormat() == DOMSaveOptions.SIMPLE_OUTPUT)
outStream.write(Document.MarkupReturn);
outStream.write(Document.MarkupEndTag);
}
else {
outStream.write(Document.MarkupEndTag2);
}
}
else {
if (options.getDisplayFormat() == DOMSaveOptions.SIMPLE_OUTPUT) {
outStream.write(Document.MarkupReturn);
}
outStream.write(Document.MarkupEndTag);
if (inhibitPrettyPrint() && options.getDisplayFormat() == DOMSaveOptions.PRETTY_OUTPUT) {
options.setDisplayFormat(DOMSaveOptions.RAW_OUTPUT);
}
Node prevChild = null;
for (Node child = getFirstXMLChild() ; child != null; prevChild = child, child = child.getNextXMLSibling()) {
child.serialize(outStream, options, level + 1, prevChild);
}
// as the first child is not NULL, last child is not NULL by definition
if (options.getDisplayFormat() == DOMSaveOptions.PRETTY_OUTPUT) {
if (prevChild instanceof Element ||
(prevChild instanceof Chars &&
prevChild.getPreviousXMLSibling() != null && // if only one text node which is a space then don't print Indent
((Chars) prevChild).isXMLSpace())) {
if (prevSibling == null ||
!(prevSibling instanceof Chars) ||
((Chars) prevSibling).isXMLSpace())
options.writeIndent(outStream, level);
}
}
outStream.write(Document.MarkupCloseTag);
outStream.write(qName.getBytes(Document.Encoding));
if (options.getDisplayFormat() == DOMSaveOptions.SIMPLE_OUTPUT) {
outStream.write(Document.MarkupReturn);
}
outStream.write(Document.MarkupEndTag);
removeNamespaceDef(this, prefix);
for (int i = 0; i < nAttrs; i++) {
final Attribute attr = getAttr(i);
if (attr.isNameSpaceAttr() || attr.getPrefix() == "")
continue;
removeNamespaceDef(this, attr.getPrefix());
}
}
// Restore the original display format if inhibitPrettyPrint()
// was enabled and we switched to RAW_OUTPUT format.
options.setDisplayFormat(eDisplayFormat);
}
private void saveAttributesToStream (OutputStream outStream, DOMSaveOptions options)
throws IOException {
final int nAttrs = getNumAttrs();
if (options.getCanonicalizeNamespaceOrder()) {
int nDefaultNamespaceIndex = -1;
List namespaceAttrIndexes = null;
for (int i = 0; i < nAttrs; i++) {
Attribute attr = getAttr(i);
if (attr.isNameSpaceAttr()) {
if (attr.getQName() == STRS.XMLNS)
nDefaultNamespaceIndex = i;
else {
if (namespaceAttrIndexes == null)
namespaceAttrIndexes = new ArrayList(nAttrs);
namespaceAttrIndexes.add(Integer.valueOf(i));
}
}
}
// First write out the default namespace (if any):
if (nDefaultNamespaceIndex >= 0) {
saveAttributeToStream(nDefaultNamespaceIndex, outStream, options);
}
// ... then the namespace prefixes (in alphabetical order):
if (namespaceAttrIndexes != null) {
Integer[] indexes = new Integer[namespaceAttrIndexes.size()];
namespaceAttrIndexes.toArray(indexes);
if (indexes.length > 1) {
Arrays.sort(indexes, new Comparator() {
public int compare(Integer o1, Integer o2) {
return StringUtils.UCS_CODEPOINT_COMPARATOR.compare(
getAttrName(o1.intValue()),
getAttrName(o2.intValue()));
}
});
}
for (int i = 0; i < indexes.length; i++) {
saveAttributeToStream(indexes[i].intValue(), outStream, options);
}
}
// ... and then all the non-namespace attributes:
for (int i = 0; i < nAttrs; i++) {
Attribute attr = getAttr(i);
if (!attr.isNameSpaceAttr())
saveAttributeToStream(i, outStream, options);
}
}
else {
// We don't care about order, just dump them all out
for (int i = 0; i < nAttrs; i++) {
saveAttributeToStream(i, outStream, options);
}
}
}
private void saveAttributeToStream(int attrIndex, OutputStream outStream, DOMSaveOptions options)
throws IOException {
final Attribute a = getAttr(attrIndex);
if (!options.canBeSaved(getAttrProp(attrIndex, AttrIsFragment),
getAttrProp(attrIndex, AttrIsDefault),
getAttrProp(attrIndex, AttrIsTransient)))
return;
if (!a.isNameSpaceAttr() || displayNamespace(a, this, options, false)) {
outStream.write(Document.MarkupSpace);
outStream.write(a.getQName().getBytes(Document.Encoding));
outStream.write(Document.MarkupAttrMiddle);
// If the attribute value was parsed as an empty value then serialize it
// that way even if a default value was substituted in the XFA DOM.
String aValue = StringUtils.toXML(a.getAttrValue(), true);
if (aValue.length() > 0)
outStream.write(aValue.getBytes(Document.Encoding));
outStream.write(Document.MarkupDQuoteString);
}
}
/**
* Sets (adds) an attribute to this element.
* @param attr
* the attribute.
* @param eTag
* The XFA tag name of the attribute being set.
*/
@FindBugsSuppress(code="ES")
public void setAttribute(Attribute attr, int eTag) {
String aPropertyName = getAtom(eTag);
// A very important check... ID is an immutable attribute. It may be
// added, but never modified.
if (eTag == XFA.IDTAG && isPropertySpecified(XFA.IDTAG, true, 0)) {
throw new ExFull(new MsgFormat(ResId.ImmutableAttributeException,
aPropertyName));
}
if (attr == null) {
// The undo must be logged after validation, but before actually setting
// the property (which might cause an exception, which means there's no
// need for an undo). If the property is null, there's no validation.
// UNDO(XFASetPropUndo, (this, XFAProperty(Object()), eTag));
// This call should clear the attribute
removeAttr(null, XFA.getString(eTag));
makeNonDefault(false);
setDirty();
return;
}
// validate attribute and value
if (!isValidAttr(eTag, true, null)) {
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidSetPropertyException);
message.format(getClassAtom());
message.format(aPropertyName);
throw new ExFull(message);
}
// get default to use in validating the value
Attribute defaultAttribute = defaultAttribute(eTag);
Attribute newValue = attr;
// verify node types
if ((defaultAttribute.getClass() != attr.getClass()) || // invalid type
(defaultAttribute instanceof EnumValue && // enums are not the same type
((EnumValue) attr).getType() != ((EnumValue) defaultAttribute).getType())) {
newValue = defaultAttribute.newAttribute(attr.getNS(), aPropertyName, aPropertyName, attr.toString());
// TODO error message
/*
* throw new ExFull(new MsgFormat(InvalidAttributeException, " (" +
* getAtom(eTag) + EqualsString() + (String)oAttr +
* STRS.RIGHTBRACE) ));
*/
}
// UNDO(XFASetPropUndo, (this, oNewValue, eTag));
// set name
if (eTag == XFA.NAMETAG) {
maName = attr.toString(); // values of name attributes are guaranteed to be interned.
}
String aAttrName = getAtom(eTag);
if (newValue.getName() != aAttrName) {
newValue = newValue.newAttribute(attr.getNS(), aPropertyName, aPropertyName, attr.toString());
}
//
// Javaport: ensure attributes are fully normalized.
//
newValue.normalize();
updateAttribute(newValue);
// turn off transientFlag
makeNonDefault(false);
// JavaPort: TODO this code isn't here in the C++, why?
// notifyPeers will do nothing if loading, so avoid constructing objects needlessly
if ( ! isMute() && ! getModel().isLoading() ) {
// notify the parent container about the attribute value change
notifyPeers(Peer.ATTR_CHANGED, aAttrName, newValue);
}
setDirty();
}
/**
* Sets (adds) an enumerated attribute value to this element.
* @param eVal
* the enumerated attribute value.
* See {@link EnumAttr} for valid values.
* @param eTag
* the XFA tag name of the attribute being set.
* See {@link XFA} for valid tag names.
*/
public final void setAttribute(int eVal, int eTag) {
EnumAttr value = EnumAttr.getEnum(eVal);
setAttribute(EnumValue.getEnum(eTag, value), eTag);
// setDirty(); // setAttribute(Attribute, int) will dirty
}
/**
* Sets (adds) an attribute to this element.
* @param nameSpace namespace of the attribute.
* @param qName the qualified name of the attribute.
* @param localName the local name of the attribute.
* @param value the value of the attribute.
* @return the attribute we just updated or created
*/
public final Attribute setAttribute(
String nameSpace,
String qName,
String localName,
String value) {
return setAttribute(nameSpace, qName, localName, value, true);
}
/**
* Sets (adds) an attribute to this element.
*
* The internSymbols parameter specifies whether the nameSpace, qName and localName parameters
* need to be interned. If it is known that these symbols are already interned (eg, they were
* retrieved from another Attribute, or are literal values), then specifing false for internSymbols
* saves the overhead for interning.
* @param nameSpace namespace of the attribute.
* @param qName the qualified name of the attribute.
* @param localName the local name of the attribute.
* @param value the value of the attribute.
* @param internSymbols indicates whether the namespace, qName and localName argument need to be interned.
* @return the attribute we just updated or created
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public final Attribute setAttribute(
String nameSpace,
String qName,
String localName,
String value,
boolean internSymbols) {
if (internSymbols) {
if (nameSpace != null) nameSpace = nameSpace.intern();
if (qName != null) qName = qName.intern();
if (localName != null) localName = localName.intern();
}
else {
if (Assertions.isEnabled) assert nameSpace == null || nameSpace == nameSpace.intern();
if (Assertions.isEnabled) assert qName == null || qName == qName.intern();
if (Assertions.isEnabled) assert localName == null || localName == localName.intern();
}
// Check if this attribute exists already.
int n = findAttr(nameSpace, qName); // Don't search using localName!
Attribute a;
Element e = getXmlPeerElement();
if (n != -1) {
Attribute existingAttribute = e.mAttrs[n];
// verify that someone isn't trying to change an ID
if (getOwnerDocument().isId(e.getNSInternal(), e.getLocalName(), existingAttribute.getNS(), existingAttribute.getLocalName()))
throw new ExFull(ResId.DOM_MODIFY_ID_ERR);
value = internAttributeValue(existingAttribute, value);
a = existingAttribute.newAttribute(nameSpace, localName, qName, value, internSymbols);
// We only need to check this here since createAttribute does this check
if (a.getLocalName() == XFA.NAME) {
maName = a.toString();
}
e.mAttrs[n] = a;
}
else {
a = createAttribute(localName, nameSpace, qName, value, getNodeSchema());
e.extendAttributes(a);
}
// FIXME: Shouldn't we call notifyPeers as for setAttribute(Attribute, int)?
setDirty();
return a;
}
/**
* Set one of the volatile attribute properties. Since attributes are immutable, we can't store these
* properties in the attributes themselves.
* @param attrIndex The offset into the attribute array.
* @param eProp The property to return. One of AttrIsDefault, AttrIsFragment, AttrIsTransient.
* @param bValue the boolean value of the property
* @exclude from published api.
*/
public final void setAttrProp(int attrIndex, int eProp, boolean bValue) {
Element e = getXmlPeerElement();
e.mAttrProperties[attrIndex] &= ~eProp;
if (bValue) e.mAttrProperties[attrIndex] |= eProp;
}
/**
* Set the class name for this node instance to
* the given tag, given its parent.
*
* The default implementation does nothing. Derived classes may
* override this method to do context-sensitive re-classification.
* @param parent the parent of the node to reclassify.
* @param eTag the XFA tag.
*
* @exclude from published api.
*/
protected void setClass(Element parent, int eTag) {
}
/**
* All name properties (including those in attributes) must be interned strings.
* @exclude from published api.
*/
public void setDOMProperties(String uri, String localName,
String qName, Attributes attributes) {
if (uri != null)
setNameSpaceURI(uri, false, false, false);
setLocalName(localName);
setXMLName(qName);
if (attributes != null)
assignAttrs(attributes);
//setDirty(); // setLocalName and setXMLName will dirty
}
/**
* Set an element value. Must be a valid 0..1 element, (not a oneOfChild or
* a 0..n child)
* @param child
* the child to add or set.
* @param eTag
* only used if oChild isNull -- in which case we remove the
* element
*
* @exclude from published api.
*/
public Node setElement(Node child,
int eTag /* = XFA.SCHEMA_DEFAULT */,
int nOccurrence/* = 0 */) {
// ignore eTag if pChild isn't NULL
if (child != null)
eTag = child.getClassTag();
// Validate the occurrence number and releationship
ChildReln validChild = getChildReln(eTag);
if (validChild == null || validChild.getMax() == ChildReln.UNLIMITED) {
String aPropertyName = getAtom(eTag);
MsgFormatPos message = new MsgFormatPos(
ResId.InvalidSetPropertyException);
message.format(getClassAtom());
message.format(aPropertyName);
throw new ExFull(message);
} else if (validChild.getOccurrence() == ChildReln.oneOfChild) {
String aPropertyName = getAtom(eTag);
// If using setProperty to set a OneOFChild and error is thrown
throw new ExFull(new MsgFormat(ResId.InvalidSetOneOfException,
aPropertyName));
} else if (validChild.getMax() <= nOccurrence) {
throw new ExFull(new IndexOutOfBoundsException(""));
}
if (child == null) {
// The undo must be logged after validation, but before actually setting
// the property (which might cause an exception, which means there's no
// need for an undo). If the property is NULL, there's no validation.
// UNDO(XFASetPropUndo, (this, XFAProperty(jfObjWrap()), eTag));
// Must be an element. Remove it.
Node existingChild = getElementLocal(eTag, true, nOccurrence, false,
false);
if (existingChild != null)
existingChild.remove();
// turn off transientFlag
makeNonDefault(false);
setDirty();
return null;
}
// get the old value
Node oldChild = locateChildByClass(child.getClassTag(), nOccurrence);
// setting the same node, return
if (oldChild == child)
return null;
// UNDO(XFASetPropUndo, (this, XFAProperty(child), eTag));
// create any intermediate nodes
if (nOccurrence > 0 && oldChild == null) {
getElement(child.getClassTag(), false, nOccurrence - 1, false,
false);
}
boolean bCloned = false;
if (child.getXFAParent() != null) {
// if child has a parent clone child so that the parent isn't
// changed
child = child.clone(this);
// UNDO(XFAInsertUndo, (NULL, child));
bCloned = true;
}
if (oldChild == null) {
// append the new node if it wasn't cloned
if (!bCloned)
appendChild(child, false);
} else {
// move child to correct spot
insertChild(child, oldChild, false);
oldChild.remove();
}
if (child instanceof Element)
((Element) child).makeNonDefault(true);
// turn off transientFlag
makeNonDefault(false);
setDirty();
return child;
}
/**
* Sets this element's first child.
* @param child the child.
*/
final void setFirstChild(Node child) {
mFirstXMLChild = child;
//setDirty(); // caller will dirty
}
/**
* @exclude from published api.
*/
final void setID(String sId) {
setAttribute(new StringAttr(XFA.ID, sId), XFA.IDTAG);
}
/**
* Set whether this node has an XMLID
* @param bIsIndexed
*
* @exclude from published api.
*/
public final void setIsIndexed(boolean bIsIndexed) {
mbIsIndexed = bIsIndexed;
}
/**
* @exclude from published api.
*/
final public void setLineNumber(int nLineNumber) {
mnLineNumber = nLineNumber;
}
/**
* Sets this element's local name.
* @param name the new local name.
*/
@FindBugsSuppress(code="ES")
public void setLocalName(String name) {
if (mLocalName != name) {
mLocalName = name != null ? name.intern() : null;
if (this instanceof DualDomNode)
((Element)((DualDomNode)this).getXmlPeer()).mLocalName = mLocalName;
}
setDirty();
}
/**
* Sets this element's model.
* @param model the model this element belongs to.
* @exclude from published api.
*/
public final void setModel(Model model) {
mModel = model;
}
/**
* Sets this element's name attribute.
* @param name the name attribute value.
*/
public void setName(String name) {
// maName is set by setAttribute
setAttribute(new StringAttr(XFA.NAME, name), XFA.NAMETAG);
//setDirty(); // setAttribute will dirty
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
protected final void setNameSpaceURI(String uri,
boolean bBypassPrefixChecks /* = false */,
boolean bApplyToChildren /* = false */,
boolean bRemovePrefix /* = false */) {
// this code makes the assumption that if you pass in one attribute
// owned by an element, you need to do this for ALL attributes which
// share the same prefix, otherwise on output, this will result in
// a duplicate attribute error on loading
boolean bScrubPrefix = bRemovePrefix;
mURI = uri;
if (this instanceof Model)
((Element)((Model)this).getXmlPeer()).mURI = mURI;
if (!bBypassPrefixChecks) {
// need to make sure any existing namespace attribute
// is in sync with namespace of current node
// Fix for Watson #1412524 - as the method jfNamedNodeMapImpl::getNamedItemNS
// only uses local name for matches, pass the prefix name (or xmlns when it is empty)
// and check that the returned attribute is marked as a namespace
// build the namespace attribute to look for
String aPrefix = getPrefix();
String aNameAtom = STRS.XMLNS; // "xmlns"
if (aPrefix != "") {
// fix up other attributes on this element which use this prefix
final int nAttrs = getNumAttrs();
for (int i = 0; i < nAttrs; i++) {
Attribute poAttr = getAttr(i);
if (!poAttr.isNameSpaceAttr() && poAttr.getPrefix() == aPrefix) {
// prevent checks on prefixes when we come back into code
// for attribute
getXmlPeerElement().mAttrs[i] =
poAttr.newAttribute(mURI, poAttr.getLocalName(), poAttr.getQName(), poAttr.getAttrValue(), false);
}
}
aNameAtom = aPrefix;
}
int index = findAttr(null, aNameAtom);
// only fix up namespace attribute if it is there
if (index != -1) {
Attribute poNSAttr = getAttr(index);
if (poNSAttr.isNameSpaceAttr()) {
// JavaPort: Since we Model.getNS() overrides Element.getNS(), we
// could end up with the wrong namespace if we simply removed the
// namespace from a model.
if (bScrubPrefix) {
// force saveToStream to regenerate the namespace prefix
removeAttr(index);
}
else {
setAttribute(poNSAttr.getNS(), poNSAttr.getQName(), poNSAttr.getLocalName(), mURI, false);
}
}
}
if (bScrubPrefix && (aNameAtom != STRS.XMLNS)) {
index = findAttr(null, STRS.XMLNS);
if (index != -1) {
Attribute poDefaultNSAttr = getAttr(index);
if (poDefaultNSAttr.isNameSpaceAttr()) {
// force saveToStream to regenerate the default namespace prefix
removeAttr(index);
}
}
}
if (bScrubPrefix) {
// Fix for Watson #1412524 - turf the namespace prefix
// so that everything will be in the default namespace
mQName = mLocalName;
if (this instanceof Model)
((Element)((Model)this).getXmlPeer()).mQName = mQName;
}
if (bApplyToChildren) {
Node poChild;
for (poChild = getFirstXFAChild(); poChild != null; poChild = poChild.getNextXFASibling()) {
if (poChild instanceof Element) {
((Element)poChild).setNameSpaceURI (mURI, bBypassPrefixChecks, bApplyToChildren, bRemovePrefix);
}
}
}
}
setDirty();
}
/**
* Sets this element's children null namespace to the given uri.
* @param sNS the namespace uri. This string must be interned.
* Javaport: added.
*/
@FindBugsSuppress(code="ES")
public final void setNS(String sNS) {
if (Assertions.isEnabled) assert sNS == null || sNS == sNS.intern();
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
if (child instanceof Element) {
Element e = (Element) child;
e.setNS(sNS);
if (e.mURI == null)
e.mURI = sNS;
}
}
if (mURI == null)
mURI = sNS;
}
/**
* Sets this element's children null namespace to
* the given model's namespace.
* @param model the model.
* Javaport: added for performance. Model.getNS() is expensive.
* @exclude from published api.
*/
final void setNS(Model model) {
String sNS = null;
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
if (child instanceof Element) {
Element e = (Element) child;
if (sNS == null)
sNS = model.getNS();
e.setNS(sNS);
if (e.mURI == null)
e.mURI = sNS;
}
}
if (mURI == null) {
if (sNS == null)
sNS = model.getNS();
mURI = sNS;
}
}
@FindBugsSuppress(code="ES")
final void setNSPrefix(String aPrefix, String aNS) {
// possibly throw an exception if the prefix/namespace is empty
if (aPrefix != "" && aNS != "") {
String sAttr = "xmlns:" + aPrefix;
setAttribute("", sAttr, aPrefix, aNS);
}
}
/**
* In the case where an element may contain a "OneOf" child, this method
* will set the child element that has the XFA::oneOfChild relationship. If
* a "OneOf" child already exists, this method will replace it.
* @param child
* the child to set. If this is a null object, any any existing
* "oneOf" child will be deleted.
*
* @exclude from published api.
*/
public Node setOneOfChild(Node child) {
// validate child
if (child != null) {
ChildReln validChild = getChildReln(child.getClassTag());
if (validChild == null
|| validChild.getOccurrence() != ChildReln.oneOfChild) {
// Invalid one of child
throw new ExFull(new MsgFormat(ResId.InvalidSetOneOfException,
child.getClassAtom()));
}
}
// remove all other one of children
Node nextSibling;
for (Node otherChild = getFirstXFAChild(); otherChild != null; otherChild = nextSibling) {
nextSibling = otherChild.getNextXFASibling(); // capture next before remove
// encounterd the node we are setting.
// don't remove it and set child to null so we don't
// add it again
if (otherChild == child) {
child = null;
continue;
}
ChildReln childReln = getChildReln(otherChild.getClassTag());
if (childReln.getOccurrence() == ChildReln.oneOfChild) {
otherChild.remove();
break;
}
}
// add the child
if (child != null) {
if (child.getXFAParent() != null) {
// if child has a parent clone child so that the parent isn't changed
child = child.clone(this);
// JavaPort: UNDO(XFAInsertUndo, (null, poNewChild));
}
else {
// append the new node
appendChild(child);
}
}
// turn off transientFlag
makeNonDefault(false);
return child;
}
/**
* Set a property for this node.
* The parameter property must be either a valid child that occurs
* 0..1 times (i.e. an Node) or a valid attribute (i.e. an
* Attribute or derivative). If this parameter is a Null object,
* then the corresponding attribute or element will be removed.
*
* There is a special case for the handling of pcdata. Technically,
* pcdata is a child node relationship, but it is set using an
* attribute - usually XFAString. The property name in this case is
* XFA::textNodeTag()
.
*
* @param property
* The XFA element that defines this property.
* @param ePropertyTag
* The XFA tag (name) of the property.
* @exclude from published api.
*/
public final void setProperty(Object property, int ePropertyTag /* = XFA.SCHEMA_DEFAULT */) {
boolean bElement = false;
if (property == null) {
// value is null, determine if it is an attribute or element
// isPropertyValid(ePropertyTag, &bElement);
if (isValidAttr(ePropertyTag, false, null))
bElement = false;
else if (isValidElement(ePropertyTag, false))
bElement = true;
// JavaPort: C++ code falls through here with an uninitialized bElement - looks like a bug
}
else if (property instanceof Element)
bElement = true;
else if (property instanceof Attribute)
bElement = false;
else
return;
if (bElement) {
Element node = (Element)property;
setElement(node, ePropertyTag, 0);
}
else {
Attribute attr = (Attribute)property;
setAttribute(attr, ePropertyTag);
}
}
/**
* string version of setProperty. ***Less efficient than the int version ***.
* @exclude from public api.
*/
public final void setProperty(Object property, String propertyName) {
String aPropertyName = propertyName.intern();
int eTag = XFA.getTag(aPropertyName);
if (eTag != XFA.INVALID_ELEMENT)
setProperty(property, eTag);
else {
MsgFormatPos message = new MsgFormatPos(ResId.InvalidSetPropertyException);
message.format(getClassName());
message.format(propertyName);
throw new ExFull(message);
}
}
/**
* Sets this element's qualified name.
* @param name the new qualified name.
*/
@FindBugsSuppress(code="ES")
public final void setQName(String name) {
if (mQName != name) {
mQName = name != null ? name.intern() : null;
if (this instanceof DualDomNode)
((Element)((DualDomNode)this).getXmlPeer()).mQName = mQName;
}
setDirty();
}
/** @exclude from published api */
public void setSaveXMLSaveTransient(boolean bSaveTransient) {
mbSaveXMLSaveTransient = bSaveTransient;
}
/**
* @exclude from published api.
*/
public final void setTransparent(boolean isTransparent) {
mbTransparent = isTransparent;
}
/**
* @exclude from published api.
*/
protected final void updateAttribute(Attribute newValue) {
final int index = findAttr(null, newValue.getLocalName());
if (newValue.getLocalName() == XFA.RID)
newValue = newValue.newAttribute(STRS.XLIFFNS, XFA.RID, "xliff:rid", newValue.getAttrValue());
Element e = getXmlPeerElement();
if (index != -1) {
// IDs are immutable
if (getOwnerDocument() != null &&
getOwnerDocument().isId(e.getNSInternal(), e.getLocalName(), newValue.getNS(), newValue.getLocalName()))
throw new ExFull(ResId.DOM_MODIFY_ID_ERR);
e.mAttrs[index] = newValue;
}
else
e.extendAttributes(newValue);
}
/**
* Update an existing attribute in place bypassing any checking or
* id maintenance.
* @exclude from published api.
*/
protected final void updateAttributeInternal(Attribute newValue) {
final int index = findAttr(null, newValue.getLocalName());
getXmlPeerElement().mAttrs[index] = newValue;
}
/**
* @see Obj#updateFromPeer(Object, int, String, Object)
* @exclude from published api.
*/
public void updateFromPeer (Object peer, int eventType, String arg1, Object arg2) {
if (eventType != Peer.PARENT_CHANGED)
makeNonDefault(false);
super.updateFromPeer (peer, eventType, arg1, arg2);
}
private Element filterClone(NodeList keepNodes) {
NodeList ancestors = new ArrayNodeList();
ancestors.append(this);
// loop through oKeepNodes and populate oAncestors
int nKeepNodes = keepNodes.length();
for (int i = 0; i < nKeepNodes; i++) {
Element keepNodesParent = ((Node)keepNodes.item(i)).getXFAParent();
while (keepNodesParent != null) {
boolean found = false;
int nAncestors = ancestors.length();
for (int j = 0; j < nAncestors; j++) {
Obj ancestor = ancestors.item(j);
if (keepNodesParent == ancestor)
found = true;
}
if (! found)
ancestors.append(keepNodesParent);
keepNodesParent = keepNodesParent.getXFAParent();
}
}
NodeList leaf = (NodeList)keepNodes.clone();
return cloneHelper(null, true, ancestors, leaf);
}
/**
* @exclude from published api.
*/
final private void updateModelAndDocument(Node newChild) {
if (newChild instanceof Element) {
Element e = (Element) newChild;
e.setModel(getModel());
e.setDocument(getOwnerDocument());
// Iterate over XML children (and not XFA children) specifically so that
// we can use the XFA side of the DOM to house generic XML used by WSDL and SOAP.
for (Node child = e.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
updateModelAndDocument(child);
}
}
}
final boolean useNameInSOM() {
Element oParent = getXFAParent();
if (oParent != null) {
// ALL Properties and oneof children will be referenced by there
// classname;
int eType = oParent.getSchemaType(getClassTag());
if (eType == TEXT || eType == ELEMENT || eType == ONEOF) {
return false;
}
}
return getName() != "";
}
/**
* Determines whether child text nodes should be processed (check validity, consolidate)
* at parse time. Some schemas (or parts of schemas) may require an open schema that
* cannot be resolved at parse time, and where processing of text nodes must be
* delayed until load processing when the entire tree is available. Where possible,
* text child nodes are processed at parse time since that allows us to avoid
* creating many text nodes (e.g., ignorable whitespace).
* @return true
if child text nodes should be processed at parse time.
* @exclude from published api.
*/
public boolean processTextChildrenDuringParse() {
return getClassTag() != XFA.INVALID_ELEMENT;
}
// Helper function for optimizeNameSpace. Traverse the tree, exiting early
// with TRUE return code if the specified namespace alias (aNSAlias, eg.
// "xmlns:xliff") is found to be in use. As long as the alias is not found
// to be in use, this method will recurse, delete the alias definition,
// and eventually return FALSE. The fact that it can trim some alias
// definitions and still return TRUE is immaterial since the DOM save
// code optimizes that away anyway.
private boolean pruneNameSpaceDefn(Element element, String sNSAlias, String sNS) {
int len = element.getNumAttrs();
int nAttrToRemove = -1; // assumes only one is possible
for (int i = 0; i < len; i++) {
Attribute attr = element.getAttr(i);
if (attr.getNS() != null && attr.getNS().equals(sNS))
return true; // As soon as we find the namespace in use we can exit.
if (attr.getQName().equals(sNSAlias))
nAttrToRemove = i;
}
// Recursively check children.
Node child = element.getFirstXMLChild();
while (child != null) {
if (child instanceof Element) {
if (pruneNameSpaceDefn((Element)child, sNSAlias, sNS))
return true; // exit early if the namespace is in use in a child
}
child = child.getNextXMLSibling();
}
// Remove the namespace alias (i.e. xmlns:alias with the value being the namespace) if found.
if (nAttrToRemove > -1)
element.removeAttr(nAttrToRemove);
return false; // NS not needed
}
/**
* Override of the corresponding Node.compareVersions.
*
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
protected boolean compareVersions(Node rollbackElement, Node container, Node.ChangeLogger changeLogger, Object userData) {
boolean bMatches = true;
//
// COMPARE ELEMENT TAG
//
if (getClassTag() != rollbackElement.getClassTag()) {
// 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 (changeLogger != null) {
if (isContainer())
changeLogger.logChildChange(container, this, userData);
else
changeLogger.logPropChange(container, getPropName(getXFAParent(), XFA.INVALID_ELEMENT), getNodeAsXML(this), userData);
}
// 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;
}
assert rollbackElement instanceof Element;
Element rollback = (Element) rollbackElement;
Node containerNode = isContainer() ? this : container;
//
// COMPARE ATTRIBUTES
//
SchemaPairs attrs = getNodeSchema().getValidAttributes();
if (attrs != null) {
for (int i = 0; i < attrs.size(); i++) {
int eTag = attrs.key(i);
if (!compareVersionsAttrHelper(rollback, eTag)) {
bMatches = false;
if (changeLogger != null)
changeLogger.logPropChange(containerNode, getPropName(this, eTag), getAttribute(eTag).getAttrValue(), userData);
}
}
}
if (bMatches == false && changeLogger == null)
return false; // if we're not logging then there's no need to wait for the fat lady to sing...
//
// COMPARE ELEMENT-BASED PROPERTIES (all [0..1] and [0..x] children, where x != unlimited)
//
SchemaPairs children = getNodeSchema().getValidChildren();
boolean bSchemaDefinesValidOneOf = false;
if (children != null) {
int nChildren = children.size();
for (int i = 0; i < nChildren; i++) {
int eTag = children.key(i);
ChildReln childR = (ChildReln) children.value(i);
if (childR.getOccurrence() == ChildReln.oneOfChild) {
bSchemaDefinesValidOneOf = true;
}
else if (childR.getMax() != ChildReln.UNLIMITED) {
long nMax = childR.getMax();
for (int nOccurrenceIndex = 0; nOccurrenceIndex < (int)nMax; nOccurrenceIndex++) {
// Note: these getElement() calls are the performance problem as they're On^2 over
// the number of children that don't exist, and worse than Ologn over those which
// do exist
// Java port: dont' call getElement as it ignores non-Elements.
Node thisChild = getNode(eTag,nOccurrenceIndex);
Node rollbackChild = rollback.getNode(eTag, nOccurrenceIndex);
if (thisChild == null && rollbackChild == null) {
// Optimization: if both are defaulted then there's no need to compare
break;
}
// At least one or the other was found, so we need to default in any that weren't found
if (thisChild == null) {
thisChild = defaultElement(eTag, nOccurrenceIndex);
}
if (rollbackChild == null) {
rollbackChild = rollback.defaultElement(eTag, nOccurrenceIndex);
}
// If we failed at defaulting, then they can't be equal (since at least one was specified)
if (thisChild == null || rollbackChild == null) {
bMatches = false;
if (changeLogger != null) {
if (thisChild == null)
changeLogger.logPropChange(containerNode, getPropName((Element)rollbackChild, XFA.INVALID_ELEMENT), "", userData);
else
changeLogger.logPropChange(containerNode, getPropName(this, XFA.INVALID_ELEMENT), getNodeAsXML(thisChild), userData);
}
break;
}
// Recurse
bMatches &= thisChild.compareVersions(rollbackChild, containerNode, changeLogger, userData);
}
if (bMatches == false && changeLogger == null)
return false; // if we're not logging then there's no need to wait for the fat lady to sing...
}
}
}
//
// COMPARE ONE-OF CHILDREN
//
// Note: one-of-children's defaults are often based on other factors, so we can't
// assume that if both are defaulted that they must be equal. We'll do this one the
// slow way.
//
if (bSchemaDefinesValidOneOf)
{
Node sourceOneOf = getOneOfChild(false, true);
Node rollbackOneOf = rollback.getOneOfChild(false, true);
if ((sourceOneOf == null) != (rollbackOneOf == null)) {
bMatches = false;
if (changeLogger != null) {
if (sourceOneOf == null)
changeLogger.logPropChange(containerNode, getPropName((Element)rollbackOneOf, XFA.INVALID_ELEMENT), "", userData);
else
changeLogger.logPropChange(containerNode, getPropName(this, XFA.INVALID_ELEMENT), getNodeAsXML(sourceOneOf), userData);
}
}
else if (sourceOneOf != null) {
// Recurse
bMatches &= sourceOneOf.compareVersions(rollbackOneOf, containerNode, changeLogger, userData);
}
if (bMatches == false && changeLogger == null)
return false; // if we're not logging then there's no need to wait for the fat lady to sing...
}
//
// COMPARE NON-PROPERTY CHILDREN ([0..n], [1..n] and any one-of children)
//
// Note: there is no proto lookup here: these must have been resolved earlier.
//
NodeList sourceChildList = new ArrayNodeList();
NodeList rollbackChildList = new ArrayNodeList();
boolean bFoundSourceOneOf = false;
boolean bFoundRollbackOneOf = false;
// First, collect lists of all children and one-of children of this node...
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int eType = getSchemaType(child.getClassTag());
if (eType == CHILD)
sourceChildList.append(child);
else if (eType == ONEOF) {
if (!bFoundSourceOneOf)
bFoundSourceOneOf = true;
else
sourceChildList.append(child);
}
else if (eType == INVALID)
sourceChildList.append(child);
}
// Do it again for the rollback node...
for (Node child = rollback.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
int eType = getSchemaType(child.getClassTag());
if (eType == CHILD)
rollbackChildList.append(child);
else if (eType == ONEOF) {
if (!bFoundRollbackOneOf)
bFoundRollbackOneOf = true;
else
rollbackChildList.append(child);
}
else if (eType == INVALID)
rollbackChildList.append(child);
}
// Now compare the various lists...
bMatches &= compareVersionsListHelper(sourceChildList, rollbackChildList, containerNode, changeLogger, userData);
//
// COMPARE PROCESSING INSTRUCTIONS
//
List sourcePIs = new ArrayList();
List rollbackPIs = new ArrayList();
getPI(sourcePIs, true);
rollback.getPI(rollbackPIs, true);
bMatches &= compareVersionsPIHelper(sourcePIs, rollbackPIs, containerNode, changeLogger, userData);
//
// SPECIAL CASES
//
// All leaf nodes (textnodeimpl, richtextnodeimpl, renderAs, etc.) must override this method
// and provide additional logic for comparing their content.
//
return bMatches;
}
/**
* WARNING: use this only if you have to. It's MUCH SLOWER than compareVersions().
*
* @exclude from published api.
*/
protected boolean compareVersionsCanonically(Node rollbackElement, Node container, Node.ChangeLogger changeLogger, Object userData) {
String sCanonicalSource = "";
String sCanonicalRollback = "";
Canonicalize c = new Canonicalize(this, false, true);
byte [] buffer = c.canonicalize(Canonicalize.EXCLUSIVEWITHOUT, null);
try {
sCanonicalSource = new String(buffer, "UTF-8");
}
catch (UnsupportedEncodingException ex) {
// not possible - UTF-8 is always supported
}
Canonicalize cRollBack = new Canonicalize(rollbackElement, false, true);
buffer = cRollBack.canonicalize(Canonicalize.EXCLUSIVEWITHOUT, null);
try {
sCanonicalRollback = new String(buffer, "UTF-8");
}
catch (UnsupportedEncodingException ex) {
// not possible - UTF-8 is always supported
}
if ( !sCanonicalSource.equals(sCanonicalRollback) )
{
if (changeLogger != null) {
String sPropName = getModel().getSchema().getAtom(XFA.XMLMULTISELECTNODETAG); // "#xml"
changeLogger.logPropChange(container, sPropName, sCanonicalSource, userData);
}
return false;
}
return true;
}
/**
* Helper routine for compareVersions()
* @exclude from published api.
*/
protected boolean compareVersionsAttrHelper(Element oRollback, int eTag) {
if (eTag == XFA.USETAG || eTag == XFA.USEHREFTAG)
{
// we're interested in the results, not the mechanics that got us there
return true;
}
if (eTag == XFA.CHECKSUMTAG) {
// data is included in checksum, and we don't always want to compare the data
// along with the template
return true;
}
// The simple algorithm here would be to just get a source and rollback XFAAttribute,
// convert them to stings, and then compare them. But to do that, we'd be creating
// a lot of XFAMeasurements (complete with jfUnitSpans), etc. from the jfDOM string
// values, and then converting them back to strings. It's much (much) faster to just
// compare the attribute strings if we can.
String aAttrName = getAtom(eTag);
Attribute sourceAttr = getAttributeByName(aAttrName, true);
Attribute rollbackAttr = oRollback.getAttributeByName(aAttrName, true);
if (sourceAttr == null && rollbackAttr == null) {
// both null; no need to create two defaults as they'd just be equal anyway
return true;
}
// It's very tempting at this point to get any default values we need from the
// XFAAttributeInfo and be done with it, but there are several classes which
// override getAttribute to do context-sensitive stuff. Since we need the results
// of those overrides, we have to go all the way up to the XFA level at this point.
String sSourceValue, sRollbackValue;
if (sourceAttr != null)
sSourceValue = sourceAttr.getAttrValue();
else
sSourceValue = getAttribute(eTag, false, false).getAttrValue();
if (rollbackAttr != null)
sRollbackValue = rollbackAttr.getAttrValue();
else
sRollbackValue = oRollback.getAttribute(eTag, false, false).getAttrValue();
return sSourceValue.equals(sRollbackValue);
}
/**
* Helper routine for compareVersions()
* @exclude from published api.
*/
private boolean compareVersionsListHelper(NodeList sourceList, NodeList rollbackList, Node container,
Node.ChangeLogger changeLogger, Object userData) {
boolean bMatches = true;
int nSourceChildren = sourceList.length();
int nRollbackChildren = rollbackList.length();
for (int i = 0; i < nSourceChildren || i < nRollbackChildren; i++) {
if (i >= nSourceChildren) {
bMatches = false;
if (changeLogger != null)
changeLogger.logChildChange(container, (Node) rollbackList.item(i), userData);
}
else if (i >= nRollbackChildren) {
bMatches = false;
if (changeLogger != null)
changeLogger.logChildChange(container, (Node) sourceList.item(i), userData);
}
else {
// Recurse
Node sourceChild = (Node) sourceList.item(i);
Node rollbackChild = (Node) rollbackList.item(i);
bMatches &= sourceChild.compareVersions(rollbackChild, container, changeLogger, userData);
}
if (bMatches == false && changeLogger == null)
return false; // if we're not logging then there's no need to wait for the fat lady to sing...
}
return bMatches;
}
/**
* Helper routine for compareVersions()
* @exclude from published api.
*/
private boolean compareVersionsPIHelper(List sourcePIs, List rollbackPIs, Node container,
Node.ChangeLogger changeLogger, Object userData) {
boolean bMatches = true;
int nSourcePIs = sourcePIs.size();
int nRollbackPIs = rollbackPIs.size();
for (int i = 0; i < nSourcePIs || i < nRollbackPIs; i++) {
if (i >= nSourcePIs) {
bMatches = false;
if (changeLogger != null)
changeLogger.logPropChange(container, getPIName(this, rollbackPIs.get(i)), "", userData);
}
else if (i >= nRollbackPIs) {
bMatches = false;
if (changeLogger != null)
changeLogger.logPropChange(container, getPIName(this, sourcePIs.get(i)), getPIAsXML(sourcePIs.get(i)), userData);
}
else if (!sourcePIs.get(i).equals(rollbackPIs.get(i))) {
bMatches = false;
if (changeLogger != null)
changeLogger.logPropChange(container, getPIName(this, sourcePIs.get(i)), getPIAsXML(sourcePIs.get(i)), userData);
}
if (bMatches == false && changeLogger == null)
return false; // if we're not logging then there's no need to wait for the fat lady to sing...
}
return bMatches;
}
/**
* connectPeerToDocument() is used to rearrange the DOM tree when inserting or appending a node.
* It is provided here as a utility function for derived classes that could be adding child nodes,
* when peered against an orphan node. In particular, this happens with xfa:datasets and
* xfa:data, both of which may be created during the load process.
*
* On the C++ side, this method is only used by the data model. Due to the differences between
* the relationship of Documents and AppModels in Java, on the Java side it is also called when
* creating a DOM from scratch, from any ModelFactory's createDOM method.
*
* In the DataModel context at least, it should only be called if the DOM peer is an orphan.
* It takes that orphan and connects it to the document, and then moves the original peer of
* this node to be a child of the node that previously was orphaned.
*
* @exclude from published api.
*/
public void connectPeerToDocument() {
DualDomNode dualDomNode = (DualDomNode)this;
assert dualDomNode.getXmlPeer() != null;
// Previously, this method was only ever called on DataModel or the top-most DataGroup.
// Now, it may be called from the createDOM method of any factory. This differs from C++
// because of the difference in the relationship between Documents and AppModels.
if (Assertions.isEnabled && this instanceof com.adobe.xfa.data.DataModel) {
assert dualDomNode.getXmlPeer().getXMLParent() == null;
}
connectPeerToParent();
Element parent = getXFAParent();
Node peer;
if (parent != null)
peer = ((DualDomNode)parent).getXmlPeer();
else
peer = dualDomNode.getXmlPeer();
Document doc = peer.getOwnerDocument();
Node oldRoot = doc.getDocumentElement();
if (oldRoot != null) {
doc.removeChild(oldRoot);
}
doc.appendChild(peer);
}
/**
* @exclude from published api.
*/
protected void connectPeerToParent() {
// Take our DOM peer and connect it to the document, and then
// add our original DOM peer as a child of it.
assert this instanceof DualDomNode;
Element domPeer = (Element)((DualDomNode)this).getXmlPeer();
for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
if (!(child instanceof DualDomNode))
continue;
assert child instanceof Element;
Node domChild = ((DualDomNode)child).getXmlPeer();
// Call getRealParentNode, not getParentNode, because attributes
// return NULL from getParentNode. So an attribute on the top-most
// element could cause us trouble. Vantive 589908.
// JAVAPORT_DATA: AttributeWrapper provides the correct parent via its getXMLParent override
Element domParent = domChild.getXMLParent();
// Does this child need to be connected too?
if (domParent == null) {
((Element)child).connectPeerToParent();
}
else if (domParent != domPeer) {
domPeer.appendChild(domChild);
}
}
Element parent = getXFAParent();
if (parent != null) {
assert !(domPeer instanceof AttributeWrapper);
Element destination = (Element)((DualDomNode)parent).getXmlPeer();
if (destination != domPeer.getXMLParent()) {
destination.appendChild(domPeer);
}
}
}
/** @exclude from published api. */
public final void setIsDataWindowRoot(boolean bIsRoot) {
mbIsDataWindowRoot = bIsRoot;
}
/**
* Constructs a key (either primary or foreign) for an element given a node
* address list. A context node must be provided for evaluating any namespace
* prefixes found in the node address list. (The context node is normally the
* data description node from which the node address list came.)
*
* @exclude from published api.
*/
public Key constructKey(List nodeAddressList, Node namespaceContextNode) {
List keyList = new ArrayList();
constructKeys(nodeAddressList, namespaceContextNode, keyList);
if (keyList.size() > 0)
return keyList.get(0);
else
return new Key();
}
/**
* @exclude from published api.
* */
public void constructKeys(List nodeAddressList, Node namespaceContextNode, List keys) {
// Quick primer:
//
// A key can be multi-valued (for instance, a vehicle identified by a state and a license
// plate number). Each value is stored in a node, and the nodeAddressList is a comma-separated
// list of XPath fragments to these nodes.
//
// An element can also contain multiple keys. For instance, consider an employee with
// permission to park several cars in the employee garage:
//
//
//
//
// ...
//
//
XPath evaluator = getXPathFactory().newXPath();
org.w3c.dom.Node contextNode = DOM.attach(this);
if (namespaceContextNode != null)
evaluator.setNamespaceContext(new NamespaceContextImpl(DOM.attach(namespaceContextNode)));
for (int i = 0; i < nodeAddressList.size(); i++) {
org.w3c.dom.NodeList nodeList;
try {
nodeList = (org.w3c.dom.NodeList)evaluator.evaluate(
nodeAddressList.get(i), contextNode, XPathConstants.NODESET);
} catch (XPathExpressionException ex) {
throw new ExFull(ResId.XPATH_ERROR, ex.getMessage());
}
if (i == 0) {
for (int j = 0; j < nodeList.getLength(); j++)
keys.add(new Key(nodeAddressList.size()));
}
else if (nodeList.getLength() < keys.size()) {
// Keys at end missing at least one node; trim key list size
trimToSize(keys, nodeList.getLength());
}
for (int j = 0; j < nodeList.getLength() && j < keys.size(); j++) {
org.w3c.dom.Node node = nodeList.item(j);
if (node instanceof org.w3c.dom.Attr) {
keys.get(j).appendValue(node.getNodeValue());
}
else if (node instanceof org.w3c.dom.Element) {
org.w3c.dom.Node text = node.getFirstChild();
if (text == null)
trimToSize(keys, j); // Keys at end missing at least one node; trim key list size
else
keys.get(j).appendValue(text.getNodeValue());
}
else {
trimToSize(keys, j); // Keys at end missing at least one node; trim key list size
}
}
}
}
private static void trimToSize(List list, int length) {
while (list.size() > length)
list.remove(list.size() - 1);
}
/**
* @exclude from published api.
* */
public static void explodeQName(String aQName, StringHolder aPrefix, StringHolder aLocalName) {
int nOffset = aQName.indexOf(':');
if (nOffset > -1) {
aPrefix.value = aQName.substring(0, nOffset).intern();
aLocalName.value = aQName.substring(nOffset + 1).intern();
}
else {
aPrefix.value = "";
aLocalName.value = aQName;
}
}
/**
* @exclude from published api.
* */
@FindBugsSuppress(code="ES")
public String resolvePrefix(String aPrefix) {
final int nSize = getNumAttrs();
for (int nIndex = 0; nIndex < nSize; nIndex++) {
Attribute attr = getAttr(nIndex);
if (attr.isNameSpaceAttr() && aPrefix == attr.getLocalName())
return attr.getAttrValue();
}
if (getXFAParent() == null)
return "";
else
return getXFAParent().resolvePrefix(aPrefix);
}
}