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

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

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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy