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

com.adobe.xfa.data.DataNode 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.data;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import org.xml.sax.Attributes;

import com.adobe.xfa.Arg;
import com.adobe.xfa.Attribute;
import com.adobe.xfa.Chars;
import com.adobe.xfa.Comment;
import com.adobe.xfa.DOMSaveOptions;
import com.adobe.xfa.Document;
import com.adobe.xfa.Element;
import com.adobe.xfa.EnumAttr;
import com.adobe.xfa.EnumType;
import com.adobe.xfa.Generator;
import com.adobe.xfa.Model;
import com.adobe.xfa.ModelPeer;
import com.adobe.xfa.Node;
import com.adobe.xfa.Obj;
import com.adobe.xfa.ProcessingInstruction;
import com.adobe.xfa.Schema;
import com.adobe.xfa.ScriptDynamicPropObj;
import com.adobe.xfa.ScriptTable;
import com.adobe.xfa.STRS;
import com.adobe.xfa.TextNode;
import com.adobe.xfa.XFA;
import com.adobe.xfa.data.DataModel.AttributeWrapper;
import com.adobe.xfa.ut.BooleanHolder;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.ut.PictureFmt;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.StringHolder;
import com.adobe.xfa.ut.StringUtils;


/**
 * DataNode is a class that combines what used to be two classes in the C++ code
 * base: XFADataGroup and XFADataValue.
 * 
 * Because of the revised DOM structure we're using in Java, we can't tell when
 * an element is first created whether it's a dataGroup or dataValue. The
 * easiest answer is to not differentiate. We'll set the classId after we load
 * and rely on it to tell the difference.
 *
 * @exclude from published api -- Mike Tardif, May 2006.
 */

public final class DataNode extends Element implements Element.DualDomNode {
	
	private static class PictureFormatInfo {
		
		final String msPictureFormat;
		final String msLocale;
		
		PictureFormatInfo(String sPictureFormat, String sLocale) {
			msPictureFormat = sPictureFormat;
			msLocale = sLocale;
		}
	}

	private String maName;	// interned

	private boolean mbContainsData; // default to false

	private boolean mbIsDDPlaceholder; // default false. node is placeholder
										// created

	// from dataDescription

	private boolean mbIsNull; // default false

	private boolean mbIsNullDetermined; // default false

	private boolean mbIsTextNode; // default false. for text picture
									// formatting.

	private DataNode mDataDescription;

	private int mnWeight; // default 0

	/**
	 * The child DOM node where the child represents the DataValue for it's
	 * parent element and is not a data node of its own
	 */
	private TextNode mSingleTextChild;

	/**
	 * Data picture format to use on this data value during getValue/setValue()
	 * Use pointer to minimize footprint - 99% of data values won't have picture
	 * format.
	 */
	private PictureFormatInfo mPictureFormatInfo;
	
	/**
	 * Constructor for DataGroups and DataNodes
	 */
	public DataNode(Element parent, Node prevSibling, String uri,
			String localName, String qName, Attributes attributes) {
		super(parent, prevSibling, null, null, null, null,
				XFA.DATAGROUPTAG, XFA.DATAGROUP);
		if (localName != null) {	// If not created during the load process.
			Element peerParent = (parent instanceof DualDomNode) ? (Element) ((DualDomNode)parent).getXmlPeer() : null;
			Element peerPrevSibling = null;// JAVAPORT_DATA revisit this
			Element xmlPeer = new Element(peerParent, peerPrevSibling, uri, localName, qName, attributes, XFA.INVALID_ELEMENT, null);
			setXmlPeer(xmlPeer);
			xmlPeer.setXfaPeer(this);
		}
	}

	public DataNode(Element parent, Node prevSibling) {
		super(parent, prevSibling, null, null, null, null,
				XFA.DATAGROUPTAG, XFA.DATAGROUP);
		Element peerParent = (parent instanceof DualDomNode) ? (Element) ((DualDomNode)parent).getXmlPeer() : null;
		Element peerPrevSibling = null;// JAVAPORT_DATA revisit this
		Element xmlPeer = new Element(peerParent, peerPrevSibling, "", XFA.DATA, STRS.XFADATA, null, XFA.INVALID_ELEMENT, null);
		setXmlPeer(xmlPeer);
		xmlPeer.setXfaPeer(this);
	}
	
	/**
	 * Constructor for (text-only) DataValues. Note the empty localName.
	 */
	public DataNode(Element parent, Node prevSibling, String value) {
		super(parent, prevSibling, "", "", "", null,
				XFA.DATAVALUETAG, XFA.DATAVALUE);
		Element peerParent = (parent instanceof DualDomNode) ? (Element) ((DualDomNode)parent).getXmlPeer() : null;
		Element peerPrevSibling = null;// JAVAPORT_DATA revisit this
		TextNode xmlPeer = new TextNode(peerParent, peerPrevSibling, value);
		setXmlPeer(xmlPeer);
		xmlPeer.setXfaPeer(this);
	}

	/**
	 * Constructor that creates a DataNode that is represented by an attribute.
	 * Note that this flavor of DataNode doesn't fit very well (being derived
	 * from Element), But we're forced to go this route to provide compatibility
	 * with the C++ code base.
	 * 
	 * @param attrName -
	 *            The name of the source attribute
	 * @param attrValue -
	 *            The value of the source attribute
	 *            
	 * @exclude from published api.
	 */
	/*
	// TODO: remove this
	DataNode(String uri, String localName, String qName, String attrValue) {
		super(null, null, uri, localName, qName, null, XFA.DATAVALUETAG, XFA.DATAVALUE);
		isTransient(true, false);
		
		TextNode textNode = new TextNode(this, null, attrValue);
		singleTextChild(textNode);
		mbIsAttr = true;
	}
	*/

	/** @exclude */
	public void appendChild(Node oChild, boolean bValidate) {
		
		if (oChild.getClassTag() == XFA.DATAGROUPTAG) {
		
			if (getXmlPeer() != null &&
					getXmlPeer().getXMLParent() == null &&
					getLocalName().equals(XFA.DATA) &&
					getModel().isCompatibleNS( ((Element)getXmlPeer()).getNS()))
					connectPeerToDocument();
			
			super.appendChild(oChild, bValidate);
			
			// notify the datawindow of any changes.
			DataModel model = (DataModel) getModel();
			DataWindow oDataWindow = model.getDataWindow();
			if (oDataWindow != null) {
				oDataWindow.dataGroupAddedOrRemoved((DataNode) oChild, true);
			}
		}
		else if (oChild.getClassTag() == XFA.DATAVALUETAG) {
			super.appendChild(oChild, bValidate);			
			resetAfterUpdate((DataNode)oChild);
		}
	}

	protected boolean canCreateChild(boolean bIsLeaf, String aName) {
		return ((DataModel)getModel()).canCreateChild(this, bIsLeaf, aName);
	}

	/**
	 * @exclude from public api.
	 */
	public void clearNull() {
		mbIsNullDetermined = false;
		mbIsNull = false;
		
		Node domPeer = getXmlPeer();

		// remove xsi:nil attr
		if (domPeer instanceof Element && !(domPeer instanceof AttributeWrapper))
			((Element)domPeer).removeXsiNilAttribute();

		// reset transient flag
		int eNullType = getNullType();
		if (eNullType == EnumAttr.NULLTYPE_EXCLUDE)
			isTransient(false, false);
	}
	
	public Element clone(Element parent, boolean deep) {
		
		if (getClassTag() == XFA.DATAGROUPTAG) {
			
			DataNode clone = (DataNode)super.clone(parent, deep);
			
			// TODO: Why doesn't C++ implementation copy these fields too?
		    clone.maName = maName;
			clone.mbIsDDPlaceholder = mbIsDDPlaceholder;
			clone.mDataDescription = mDataDescription;
			clone.mnWeight = mnWeight;
			
			return clone;
		}
		
		Node clonedDomNode;
		if (parent != null) {
			Element parentPeer = (Element)((DualDomNode)parent).getXmlPeer();
			
			if (getXmlPeer() instanceof AttributeWrapper) {
				Attribute attr = ((AttributeWrapper)getXmlPeer()).mAttribute;
				attr = parentPeer.setAttribute(attr.getNS(), attr.getQName(), attr.getLocalName(), attr.getAttrValue(), false);
				clonedDomNode = new AttributeWrapper(attr, parentPeer);
			}
			else {
				// check if a import is needed
				if (parentPeer.getOwnerDocument() != getXmlPeer().getOwnerDocument())
					clonedDomNode = parentPeer.getOwnerDocument().importNode(getXmlPeer(), deep);
				else
					// temp fix since jfDomNode:clone doesn't work correctly
					clonedDomNode = getXmlPeer().getOwnerDocument().importNode(getXmlPeer(), deep);
				
				parentPeer.appendChild(clonedDomNode);
			}
		}
		else {
			if (getXmlPeer() instanceof AttributeWrapper) {
				Attribute attr = ((AttributeWrapper)getXmlPeer()).mAttribute;
				clonedDomNode = new AttributeWrapper(attr, null);
			}
			else {
				// temp fix since jfDomNode:clone doesn't work correctly
				clonedDomNode = getXmlPeer().getOwnerDocument().importNode(getXmlPeer(), deep);				
			}
		}
		

		DataModel dataModel = (DataModel)getModel();

		// First make a copy of this node.
		DataNode newNode = new DataNode(parent, null, null, null, null, null);
		newNode.setClass(XFA.DATAVALUE, XFA.DATAVALUETAG);
		newNode.setModel(dataModel);
		newNode.setDocument(getOwnerDocument());
		newNode.setXmlPeer(clonedDomNode);
		clonedDomNode.setXfaPeer(newNode);

		if (deep && clonedDomNode instanceof Element) {
			
			Element clonedDomElement = (Element)clonedDomNode;
			
			// Process attributes which should be treated as children
			if (dataModel.attributesAreValues()) {
				int numAttrs = clonedDomElement.getNumAttrs();

				// Process sub-nodes.
				for (int i = 0; i < numAttrs; i++) {
					Attribute attr = clonedDomElement.getAttr(i);
					String aLocalName = attr.getLocalName();

					// This attribute processing is similar to that of xfdatamodelimpl's loadNode
					// routine.  They must be kept in sync.
					boolean bSkipAttr = dataModel.loadSpecialAttribute(attr, 
																	   aLocalName, 
																	   newNode,
																	   true);	// check only, don't load

					if (attr.isNameSpaceAttr())
						bSkipAttr = true;

					// Don't load attributes in our data namespace
					String aNamespaceURI = attr.getNS();
					if (( ! bSkipAttr) && (dataModel.isCompatibleNS(aNamespaceURI)))
						bSkipAttr = true;

					// Don't load attributes in the dataDescription namespace
					if (( ! bSkipAttr) && (DataModel.isDataDescriptionNS(aNamespaceURI)))
						bSkipAttr = true;

					if (bSkipAttr)
						continue;
		
					dataModel.loadNode(newNode, new AttributeWrapper(attr, clonedDomElement), new Generator("", ""));
				}
			}
		}

		Node child = clonedDomNode.getFirstXMLChild();
		
		// If we're a DOM ELEMENT and have just one child, that child
		// will be our data value... i.e. don't treat it as a nested
		// datavalue child.
		if (child instanceof TextNode && child.getNextXMLSibling() == null) {
			newNode.singleTextChild((TextNode)child);
		}
		else {
			Node lastChild = clonedDomNode.getLastXMLChild();
	
			// Now clone all our children
			while (child != null) {
				Node next = child.getNextXMLSibling();
				if (child != null && 
						(child instanceof Element || child instanceof TextNode)) {
					dataModel.loadNode(newNode, child, new Generator("", ""));
				}
	
				// looped through all the children
				if (child == lastChild)
					break;
					
				child = next;
			}
		}

		if (getLocked())
			newNode.setLocked(true);
		
		return newNode;
	}

	/**
	 * create a new child that is appended to this node.
	 * @exclude from published api.
	 */
	public Node createChild(boolean bIsLeaf, String aName) {
		boolean bCreateDataValue = (getClassTag() == XFA.DATAGROUPTAG) ? bIsLeaf : true;
		return getModel().createNode(bCreateDataValue ? XFA.DATAVALUETAG : XFA.DATAGROUPTAG, this, aName, "", true);
	}
	
	/**
	 * return a value formatted according to the picture clause that may be
	 * stored with this dataValue.
	 * 
	 * @param sVal
	 *            The value to format
	 * @return The formatted string
	 */
	String formatDataValue(String sVal) {
		String sStr = sVal;
		if (mPictureFormatInfo != null) {
			//
			// Adobe patent application tracking # B136,
			// entitled "Applying locale behaviors to regions of a form",
			// inventors: Gavin McKenzie, Mike Tardif, John Brinkman"
			//
			String sLocale = mPictureFormatInfo.msLocale;
			if (StringUtils.isEmpty(sLocale))
				sLocale = getInstalledLocale();
			boolean bSuccess = false;
			StringBuilder sFormatted = new StringBuilder(); 
			if (mbIsTextNode && PictureFmt.isTextPicture(mPictureFormatInfo.msPictureFormat)) {
				bSuccess = PictureFmt.formatText(sVal, mPictureFormatInfo.msPictureFormat, sLocale, sFormatted);
			}
			if (! bSuccess) {
				PictureFmt oPict = new PictureFmt(sLocale);
				bSuccess = oPict.format(sVal, mPictureFormatInfo.msPictureFormat, sFormatted);
			}
			if (bSuccess)
				sStr = sFormatted.toString();
			//
			// return sVal if formatting fails.
			//
		}
		return sStr;
	}

	/**
	 * Determine if a DataValue participates in its parent's value or is an
	 * attribute of the parent. The value returned is "data" or "metaData".
	 * 
	 * @return String that determines if this data value should be included in
	 *         its parent's value result or as an attribute of the parent.
	 * @exclude from public api.
	 */
	public String getContains() {
		return mbContainsData ? XFA.DATA : XFA.METADATA;
	}

	/**
	 * Determine the content type for a DataValue, e.g., text/html.
	 * 
	 * @return String that determines the content type.
	 */
	public String getContentType() {
		
		if (getXmlPeer() instanceof AttributeWrapper ||
			getXmlPeer() instanceof TextNode)
			return "";
		
		// if the xfa:contentType attribute is set, return its value
		Attribute attr = ((DataModel) getModel()).findAttrInNS(this, XFA.CONTENTTYPE);
		if (attr != null)
			return attr.getAttrValue();

		// Attribute not set; look in first element child node to see if its
		// namespace is equal to STRS.XHTMLNSTAG ("http://www.w3.org/1999/xhtml").
		
		for (Node child = getXmlPeer().getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
			if (child instanceof Element) {
				if (((Element) child).getNS() == STRS.XHTMLNS)
					return STRS.TEXTHTML;
				break;
			}
		}
		
		return "";
	}

	// get and set the data description for this node
	public DataNode getDataDescription() {
		return mDataDescription;
	}

	// dataDescription merge management
	public boolean getIsDDPlaceholder() {
		return mbIsDDPlaceholder;
	}

	/**
	 * @see Element#getIsNull()
	 */
	public boolean getIsNull() {
		if (getClassTag() == XFA.DATAGROUPTAG)
			return false;		
		
		if (mbIsNullDetermined)
			return mbIsNull;

		mbIsNullDetermined = true;
		mbIsNull = false;
		
		Node domPeer = getXmlPeer();

		// remove xsi:null attribute
		if (domPeer instanceof Element && !(domPeer instanceof AttributeWrapper)) {
			
			Element elementPeer = (Element)domPeer;		
			Attribute attr = elementPeer.getXsiNilAttribute();
			// check the xsi:nil attr
			if (attr != null) {
				boolean bIsNull = attr.getAttrValue().equals("true");
				updateIsNull(bIsNull);
				return bIsNull;
			}
		}

		int eNullType = getNullType();

		// if empty null check
		if (eNullType == EnumAttr.NULLTYPE_EMPTY && StringUtils.isEmpty(getValue(false)))
			mbIsNull = true;
		// check if excluded
		else if (eNullType == EnumAttr.NULLTYPE_EXCLUDE && isTransient())
			mbIsNull = true;

		return mbIsNull;
	}

	/**
	 * See {@link Element#getName()}
	 */
	public String getName() {
		if (maName != null) {
			return maName;
		}

		return getLocalName();
	}

	/**
	 * See {@link Element#getLocalName()}
	 */
	public String getLocalName() {
		if (getXmlPeer() instanceof Element)
			return ((Element)getXmlPeer()).getLocalName();
		if (getXmlPeer() instanceof TextNode)
			return "";
		assert("Unexpected case in DataNode.getLocalName" == "");
		return "";
	}

	/**
	 * See {@link Element#setLocalName(String)}
	 */
	public void setLocalName(String name) {
		if (getXmlPeer() instanceof Element)
			((Element)getXmlPeer()).setLocalName(name);
		else if (getXmlPeer() instanceof TextNode) {
			// do nothing - text nodes do not have a name that can be set
		}
		else
			assert("Unexpected case in DataNode.setLocalName" == "");
		
		// setDirty();	// Element.setLocalName will dirty
	}
	
	/**
	 * See {@link Element#getXMLName()}
	 */
	public String getXMLName() {
		if (getXmlPeer() instanceof Element)
			return ((Element)getXmlPeer()).getXMLName();
		if (getXmlPeer() instanceof TextNode)
			return "";	// 	text nodes do not have meaningful names
		assert("Unexpected case in DataNode.getXMLName" == "");
		return "";
	}

	/**
	 * See {@link Element#setXMLName(String)}
	 */
	public void setXMLName(String name) {
		if (getXmlPeer() instanceof Element)
			((Element)getXmlPeer()).setXMLName(name);
		else
			assert("Unexpected case in DataNode.setXMLName" == "");
		
		//setDirty();	// Element.setXMLName will dirty
	}
	
	/**
	 * See {@link Element#getNS()}
	 */
	public String getNS() {
		if (mXmlPeer instanceof Element)
			return ((Element)getXmlPeer()).getNS();
		if (mXmlPeer instanceof TextNode)
			return "";	// 
		assert("Unexpected case in DataNode.getNS" == "");
		return "";
	}

	/**
	 * Find out how null is represented for this data value
	 * 
	 * @return The enumerated null type.
	 * @see EnumType.NULLTYPE
	 */
	private int getNullType() {

		// default;
		int eNullType = EnumAttr.NULLTYPE_EMPTY;

		if (mDataDescription != null) {
			Element dataDesc = mDataDescription;

			while (dataDesc instanceof DataNode) {
				// Get the XML DOM node
				
				DataNode dataNode = (DataNode)dataDesc;
				
				Node domPeer = dataNode.getXmlPeer();

				if ((domPeer instanceof Element && !(domPeer instanceof AttributeWrapper)) || domPeer instanceof TextNode) {
					// get the null type attribute from the DataDescription, if there is one.
					int attr = dataNode.findAttr(STRS.DATADESCRIPTIONURI, XFA.NULLTYPE);
					if (attr != -1) {
						// Construct an enum, this will validate the attribute value
						EnumAttr oEnum = EnumAttr.getEnum(EnumType.getEnum(EnumType.NULLTYPE), dataNode.getAttrVal(attr));
						eNullType = oEnum.getInt();
						break;
					}
				}

				// move to parent Data description
				dataDesc = dataDesc.getXFAParent();
			}
		}

		return eNullType;
	}
	
	public String getData() {
		// JAVAPORT_DATA revisit this.  This is a quick-and-dirty solution to accessing attribute values.
		Node peer = getXmlPeer();
		if (peer instanceof DataModel.AttributeWrapper)
			return ((DataModel.AttributeWrapper) peer).getValue();
		if (peer instanceof TextNode)
			return ((TextNode) peer).getValue();
		
		return "";
	}

	/**
	 * Gets this data element's first XFA child.
	 * Text nodes in the data model can't be
	 * exposed to counting, scripting, etc...
	 * @return the first XFA child.
	 */
	public Node getFirstXFAChild() {
		Node child = mFirstXMLChild;
		while (child != null && ! isScriptable(child)) {
			child = child.getNextXFASibling();
		}
		return child;
	}

	/**
	 * Gets this data element's next XFA sibling.
	 * Text nodes in the data model can't be
	 * exposed to counting, scripting, etc...
	 * @return the next XFA sibling.
	 */
	public Node getNextXFASibling() {
		Node sibling = mNextXMLSibling;
		while (sibling != null && ! isScriptable(sibling)) {
			sibling = sibling.getNextXMLSibling();
		}
		return sibling;
	}

	/**
	 */
	public ScriptTable getScriptTable() {
		return DataNodeScript.getScriptTable();
	}

	/**
	 * Get the value of the node.
	 * 
	 * @return The string value of this node.
	 */
	public String getValue() {
		return getValue(true);
	}

	/**
	 * Get the value of the node.
	 * 
	 * @return The string value of this node.
	 * 
	 * @exclude from public api.
	 */
	public String getValue(boolean bUseNull) {
		
		if (bUseNull && getIsNull()) {			
			// JavaPort: the C++ implementation suggests that the default jfString
			// constructor results in an empty string, but it is actually null.
			// This port omits bogus logic and comments and implements the
			// actual effect of the C++ code!			
			return unformatDataValue(null);
		}
		
		StringBuilder textValue = new StringBuilder();  
		
        // check for RichText
		// Watson 1405622 Check for rich text first. There seem to be cases where the single text child may
		// be present (in other words, singleTextChild() != null) even though it's xhtml.
		if (getContentType().equals(STRS.TEXTHTML)) {
			getValuesFromDom(textValue, getXmlPeer());
			return unformatDataValue(textValue.toString());
		}

		// Use picture format (if present) to convert to
		// canonical format before returning (unformat)
        if (singleTextChild() != null)
			return unformatDataValue(singleTextChild().getValue());

        textValue.append(getData());
        
		for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
			
			if (child.isSameClass(XFA.DATAVALUETAG)) {
				//
				// Concatenate the text of all child nodes
				//
				DataNode dChild = (DataNode) child;
				if (!dChild.isAttribute()) {
					String sValue = dChild.getValue(true);
					if (sValue != null)
    					textValue.append(sValue);
				}
			}
		}
		
		return unformatDataValue(textValue.toString());
	}
	
	/**
	 * Does this data node correspond to an XML Attribute?
	 * 
	 * @return true if this node corresponds to an XML Attribute.
	 * @exclude from published api
	 */
	public boolean isAttribute() {
		return !mbContainsData;
	}

	/**
	 * Get the value of all our child nodes concatenated
	 * 
	 * @param textValue
	 *            The current text value
	 * @param node
	 *            The node to include in the value
	 */
	private void getValuesFromDom(StringBuilder textValue, Node node) {
		if (node instanceof TextNode) {
			TextNode t = (TextNode)node;
			textValue.append(t.getValue());
		}
		
		for (Node child = node.getFirstXMLChild(); child != null; child = child.getNextXMLSibling())
			getValuesFromDom(textValue, child);
	}

	public int getWeight() {
		return mnWeight;
	}
	
	public void insertChild(Node oChild, Node refChild, boolean bValidate) {
		if (oChild.getClassTag() == XFA.DATAGROUPTAG) {
			
			if (getXmlPeer() != null &&
				getXmlPeer().getXMLParent() == null &&
				getLocalName().equals(XFA.DATA) &&
				getModel().isCompatibleNS(((Element)getXmlPeer()).getNS()))
				connectPeerToDocument();
			
			super.insertChild(oChild, refChild, bValidate);			
			
			// notify the datawindow of any changes.
			DataModel model = (DataModel) getModel();
			DataWindow oDataWindow = model.getDataWindow();
			if (oDataWindow != null) {
				oDataWindow.dataGroupAddedOrRemoved((DataNode) oChild, true);
			}
		}
		else if (oChild.getClassTag() == XFA.DATAVALUETAG) {
			super.insertChild(oChild, refChild, bValidate);			
			resetAfterUpdate((DataNode)oChild);
		}
	}

	public boolean isContainer() {
		return true;
	}

	/**
	 * Determines if the given node is scriptable (visible to the data model).
	 * @param node The node to include in the value
	 * @return true if this node is scriptable.
	 */
	static boolean isScriptable(Node node) {
		assert(node != null);
		if (node.getClassTag() == XFA.INVALID_ELEMENT) {
			return false;
		}
		else if (node.getClassTag() == XFA.TEXTNODETAG) {
			return false;
		}
		else if (node instanceof Element && ((Element) node).getNS() == STRS.XHTMLNS) {
			return false;
		}
		Element parent = node.getXFAParent();
		assert(parent != null);
		int contentTypeAttr = parent.findAttr(STRS.XFADATANS_CURRENT, XFA.CONTENTTYPE);
		if (contentTypeAttr != -1 && parent.getAttrVal(contentTypeAttr).equals(STRS.TEXTHTML)) {
			return false;
		}
		return true;
	}

	/**
	 * @see Element#isValidAttr(int, boolean, String)
	 * @exclude
	 */
	public boolean isValidAttr(int eTag, boolean bReport /* = false */, String value /* = null */) {
		// We have no interest in validating attributes in the data dom.
		return true;
	}

	/**
	 * @see Element#isValidChild(int, int, boolean, boolean)
	 * @exclude
	 */
	public boolean isValidChild(int eTag, int nResId /* = 0 */,
									boolean bBeforeInsert /* = false */,
									boolean bOccurrenceErrorOnly /* = false */) {
		if (! isValidElement(eTag, false)) {
			if (nResId != 0) {
				MsgFormatPos oMessage = new MsgFormatPos(nResId);
				oMessage.format(getClassAtom());
				oMessage.format(XFA.getAtom(eTag));
				throw new ExFull(oMessage);
			}
			return false;
		}
		return true;
	}

	/**
	 * @see Element#isValidElement(int, boolean)
	 * @exclude
	 */
	public boolean isValidElement(int eTag, boolean bReport /* = false */) {
		int eClassTag = getClassTag();
		if (eClassTag == XFA.DATAGROUPTAG) {
			if (eTag == XFA.DATAVALUETAG || eTag == XFA.DATAGROUPTAG
					|| eTag == XFA.DSIGDATATAG) {
				// only datavalues and datagroups are valid elements of a
				// datagroup
				return true;
			}
		} else if (eClassTag == XFA.DATAVALUETAG) {
			// metadata (attributes) can't have children
			if (!mbContainsData)
				return false;

			if (eTag == XFA.DATAVALUETAG || eTag == XFA.DSIGDATATAG
					|| eTag == XFA.TEXTNODETAG)
				return true;
		}

		return false;
	}

	public void makeNonDefault(boolean bRecursive) {
		// Javaport: prevent makeNonDefault() from rippling up
		// into data and dataset nodes, for otherwise it will
		// mess up their transientness.
		if (getXMLName() != STRS.XFADATA && getXMLName() != STRS.XFADATASETS)
    		super.makeNonDefault(bRecursive);
	}
	
	protected boolean notifyParent() {
		if (getClassTag() == XFA.DATAVALUETAG) {
			Node parent = getXFAParent();
			
			if (parent != null && parent.getClassTag() == XFA.DATAVALUETAG)
				return true;
		}
		
		return super.notifyParent();
	}
	
	/**
	 * Text children of data cannot be processed at parse time.
	 * This processing is deferred to postLoad processing. 
	 * 
	 * @exclude from published api.
	 */
	public boolean processTextChildrenDuringParse() {
		return false;
	}

	public void preSave(boolean bSaveXMLScript /* = false */) {
		
		if (getClassTag() == XFA.DATAGROUPTAG) {
			
			// don't need to worry about ambiguous nodes if we have a data description
			// plus we don't want to add XFA nodes to third party data if they have given 
			// a schema
			
			if (getDataDescription() == null) {
				//
				// Check if our dom node needs an attribute to clarify whether
				// we're a dataValue or a dataGroup.  e.g. if we're a dataGroup with
				// no children, or a dataGroup with mixed content, we might be mistaken 
				// for a dataValue.
				//
				
				Node node = getXmlPeer().getFirstXMLChild();
				Node parent = getXmlPeer().getXMLParent();
				
				boolean bAmbiguous = getFirstXFAChild() == null;
				if (node == null && parent != null && !(parent instanceof ModelPeer))
					bAmbiguous = true;
			
				while (node != null && !bAmbiguous) {
					
					if (node instanceof TextNode) {
						bAmbiguous = true;
						break;
					}
					node = node.getNextXMLSibling();
				}
	
				DataModel model = (DataModel)getModel();
			
				Element domElement = (Element)getXmlPeer();
				Attribute domAttr = model.findAttrInNS(domElement, XFA.DATANODE);
				
				boolean bHasDDAttr = false; // if this is dd:dataNode - leave it alone
	
				if (domAttr != null) {
					
					if (domAttr.getNS() == STRS.DATADESCRIPTIONURI)
						bHasDDAttr = true;
					else {
						
						// Mark the dom document to ensure that the nodes
						// are not marked as dirty.
						final boolean previousWillDirty = getWillDirty();
						setWillDirty(false);
						try {
							domElement.removeAttr(domAttr.getNS(), domAttr.getLocalName());
						}
						finally {
							setWillDirty(previousWillDirty);
						}
					}
				}
				if (bAmbiguous && !bHasDDAttr) {
					// Mark the dom document as loading to ensure that the nodes
					// are not marked as dirty. 
					final boolean previousWillDirty = getWillDirty();
					setWillDirty(false);
					try {
						domElement.setAttribute(STRS.XFADATANS_CURRENT, STRS.XFADATANODE, XFA.DATANODE, XFA.DATAGROUP, false);
					}
					finally {
						setWillDirty(previousWillDirty);
					}
				}
			}
	
			boolean bIsNull = true;
			// Now recursively call preSave for all children
			for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
				child.preSave(false);
				bIsNull &= child.isTransient();
			}
	
			// if all the children are transient then we can see 
			// if we should also mark this node as transient
			if (bIsNull) {
				
				// get the null type for this data group
				int eNullType = EnumAttr.NULLTYPE_EMPTY;
				if (mDataDescription != null) {
					
					Element dataDesc = mDataDescription;
					while (dataDesc != null && !(dataDesc instanceof DataModel)) {
						// Get the XML DOM node
						Node domPeer = ((DataNode)dataDesc).getXmlPeer();
	
						if (domPeer instanceof Element) {
							
							Element element = (Element)domPeer;
							// get the null type attribute from the DataDescription, if there is one.
							int index = element.findAttr(STRS.DATADESCRIPTIONURI, XFA.NULLTYPE);
							if (index != -1) {
								
								// Construct an enum, this will validate the attribute value
								EnumAttr enumAttr = EnumAttr.getEnum(EnumType.NULLTYPE_TYPE, element.getAttrVal(index));
								eNullType = enumAttr.getInt();
								break;
							}
						}
	
						// move to parent Data description
						dataDesc = dataDesc.getXFAParent();
					}
				}
	
				// check if excluded, if so mark as transient
				if (eNullType == EnumAttr.NULLTYPE_EXCLUDE )
					isTransient(true, false);
			}
		}
		else { // if (getClassTag() == XFA.DATAVALUETAG)
			
			// don't need to worry about ambiguous nodes if we have a data description
			// plus we don't wan to add XFA nodes to third party data if they have given 
			// a schema
			if (getDataDescription() == null) {
				
				Node domPeer = getXmlPeer();
				
				if ((domPeer instanceof Element) && !((Element)domPeer).isTransient()) {
					// Check if our dom node needs an attribute to clarify whether
					// we're a dataValue or a dataGroup.  e.g. if we're a dataValue with
					// children which are all dataValues, we might be mistaken for a
					// dataGroup.
					Node child = getXmlPeer().getFirstXMLChild();

					// we have a child, by default it is Ambiguous
					boolean bAmbiguous = child != null;

					// Check if the first child is in the xhtml namespace.  If so, it's
					// rich text and is not ambiguous.
					if (bAmbiguous && (child instanceof Element) && ((Element)child).getNS() == STRS.XHTMLNS)
						bAmbiguous = false;
					
					// still don't know, check the rest of the children
					if (bAmbiguous) {
						
						while (child != null) {
							
							if (child instanceof TextNode) {
								bAmbiguous = false;
								break;
							}
						
							child = child.getNextXMLSibling();
						}
					}
					
					DataModel model = (DataModel)getModel();

					Element element = (Element)domPeer;
					Attribute domAttr = model.findAttrInNS(element, XFA.DATANODE);
					
					boolean bHasDDAttr = false;  // if it's dd:dataNode - leave it alone
			
					if (domAttr != null) {
						
						if (domAttr.getNS() == STRS.DATADESCRIPTIONURI)
							bHasDDAttr = true;
						else {
							// Mark the dom document to ensure that the nodes
							// are not marked as dirty. 
							final boolean previousWillDirty = getWillDirty();
							setWillDirty(false);

							try {
								// this needs to bypass permissions
								element.removeAttr(domAttr.getNS(), domAttr.getLocalName());
							}
							finally {
								setWillDirty(previousWillDirty);
							}
						}
					}
					if (bAmbiguous && !bHasDDAttr) {
						// Mark the dom document to ensure that the nodes
						// are not marked as dirty. 
						final boolean previousWillDirty = getWillDirty();
						setWillDirty(false);

						try {
							// this needs to bypass permissions
							element.setAttribute(STRS.XFADATANS_CURRENT, STRS.XFADATANODE, XFA.DATANODE, XFA.DATAVALUE, false);
						}
						finally {
							setWillDirty(previousWillDirty);
						}
					}
				}
			}

			// Now recursively call preSave for all children
			for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
				child.preSave(false);
			}
		}
	}

	/**
	 * Removes this node fron its parent.
	 * @exclude from published api.
	 */
	public void remove() {
		/*
		 * Note that in the C++ code base this method had a "boolean bDestroy"
		 * parameter that would control whether we decremented the refcount of this
		 * node or not. Now that refcounting is outside our control, I've remove
		 * the parameter. JB 15 Aug 2005
		 */
		
		if (getClassTag() == XFA.DATAGROUPTAG) {
			DataModel model = (DataModel) getModel();
			// notify the datawindow of any changes.
			DataWindow oDataWindow = model.getDataWindow();
			if (oDataWindow != null) {
				oDataWindow.dataGroupAddedOrRemoved(this, false);
			}
		}

		// this may result in "this" being destroyed
		super.remove();
	}

	protected void removeTextNodes() {
		Node child = getFirstXMLChild();
		while (child != null) {
			Node nextSibling = child.getNextXMLSibling();
			if (child instanceof TextNode)
				removeChild(child);
			child = nextSibling;
		}
	}

	/*
	 * Called after a child is added or removed.
	 */
	private void resetAfterUpdate(DataNode oAffectedChild) {
		
		Node oAffectedChildDomPeer = oAffectedChild.getXmlPeer();

		// If the child is an attribute then changes don't affect us.
		if (oAffectedChildDomPeer == null || 
			oAffectedChildDomPeer instanceof AttributeWrapper)
			return;

		// don't do anything if a signature was added or removed. 

		if (oAffectedChildDomPeer instanceof Element) {
			if (((Element)oAffectedChildDomPeer).getNS() == STRS.XMLDSIGNS)
				return;
		}
		
		mbIsNullDetermined = false; // reset null
		//
		// Store our single text child in case we need it again.
		//
		TextNode oldSingleChild = singleTextChild();
		TextNode newSingleChild = null;	// look for a single text or CDATA node.
		
		for (Node child = getXmlPeer().getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
			
			if (child instanceof TextNode) {
				if (newSingleChild == null)
					newSingleChild = (TextNode) child;
				else {
					newSingleChild = null;	// found a second one.
					break;
				}
			}
			else if (!(child instanceof Comment) && !(child instanceof ProcessingInstruction)) {
				DataModel oDataModel = (DataModel) getModel();
				if (! oDataModel.loadSpecialNode(this, (Element) child, true)) {	// check only, don't load
					newSingleChild = null;
					break;
				}
			}			
		}
		
		//
		// If we have just one child, that child
		// will be our data value... i.e. don't treat it as a nested
		// datavalue child.
		//
		singleTextChild(newSingleChild);
		if (oldSingleChild != null && newSingleChild == null) {
			//
			// Create a new child DataValue from what used to be our
			// single text child.
			//
			DataNode oNew = new DataNode(null, null, "");
			oNew.setXmlPeer(oldSingleChild);
			oldSingleChild.setXfaPeer(oNew);
			
			if (oldSingleChild == getXmlPeer().getFirstXMLChild()) {
				// Insert at the beginning of our list
				super.insertChild(oNew, getFirstXFAChild(), false);
			}
			else {
				// add to the end of our list
				super.appendChild(oNew, false);
			}
		}
		// notify xfa peers
		notifyPeers(VALUE_CHANGED, "", null);
	}

	/** @exclude */
	public void resetPostLoadXML() {
		
		if (getClassTag() != XFA.DATAGROUPTAG)
			return;
		
		DataModel dataModel = (DataModel) getModel();

		if (dataModel != null) {
			DataNode desc = getDataDescription();
			// update data description
			if (desc != null)
				dataModel.connectDataNodesToDataDescription(desc, this);
			else
				dataModel.processDataDescription(this);

			DataWindow dw = dataModel.getDataWindow();
			if (dw != null) {
				// Tell the data window that we have loaded xml
				dw.updateAfterLoad();
			}
		}
	}

	/**
	 * @see Element#saveXML(OutputStream, DOMSaveOptions)
	 */
	public void saveXML(OutputStream sOutFile, DOMSaveOptions options) {
		// TODO JavaPort: Do something with the options
		if (!mbContainsData)
			return;
		super.saveXML(sOutFile, options);
	}
	
	public void serialize(OutputStream outStream, DOMSaveOptions options, int level, Node prevSibling) throws IOException {

		// JAVAPORT_DATA - we no longer need to attempt to serialize a DataNode
		// directly - just serialize the XML peer in the same way that the
		// C++ implementation would.

		getXmlPeer().serialize(outStream, options, level, prevSibling);
	}

	/**
	 * Set the behavior for a DataValue; determine if it should participate in
	 * its parent's value or as an attribute of the parent.
	 * 
	 * @param containsType -
	 *            a String that determines if this data value should be included
	 *            in its parent's value result or as an attribute of the parent.
	 *            It must be "data" or "metaData".
	 * @exception UnsupportedOperationException
	 *                if an attempt is made to set a node that has children or
	 *                has no name to be an attribute.
	 */
	void setContains(String containsType) {
		if (XFA.DATA.equals(containsType)) {
			if (mbContainsData) // Already in the requested state
				return;
			
			mbContainsData = false;
			
			String attrValue = ((AttributeWrapper)getXmlPeer()).getValue();
			String name = getName();
			boolean isNull = getIsNull();
			getXmlPeer().remove();
			
			// Create a new text element and add it to the parent
			Element oParent = getXmlPeer().getXMLParent();
			Element text = new Element(oParent, null, "", name, name, null, XFA.INVALID_ELEMENT, "");

			// link the text with our data node
			setXmlPeer(text);
			text.setXfaPeer(this);

			if (isNull)
				setIsNull(true, false);
			else
				setValue(attrValue, true);
			
		}
		else if (XFA.METADATA.equals(containsType)) {
			if (!mbContainsData) // Already in the requested state.
				return;
			
			String name = getName();
			String value = getValue(true);
			Node original = getXmlPeer();
			Element parent = original.getXMLParent();
			
			// only allow a node to be changed to an attribute if it has no children.
			if (getFirstXFAChild() != null) {
				// "%1 is an unsupported operation for the %2 object%3" 
				// Note: %3 is extra text if necessary
				MsgFormatPos oMessage = new MsgFormatPos(ResId.UnsupportedOperationException);
				oMessage.format("setContains").format(getClassAtom()).format(ResId.NodeHasChildren);
				throw new ExFull(oMessage);
			}
			// must have a valid name
			if (getName().length() == 0) {
				// "%1 is an unsupported operation for the %2 object%3" 
				// Note: %3 is extra text if necessary
				MsgFormatPos oMessage = new MsgFormatPos(ResId.UnsupportedOperationException);
				oMessage.format("setContains").format(getClassAtom()).format(ResId.InvalidName);
				throw new ExFull(oMessage);
			}
			
			// remove the old node from the tree.
			parent.removeChild(original);
			
			// Create a new attribute and add it to the parent
			Attribute attr = parent.setAttribute("", "", name, value, false);
			
			// link the attribute with our data node
			AttributeWrapper xmlPeer = new AttributeWrapper(attr, parent);
			xmlPeer.setXfaPeer(this);
			setXmlPeer(xmlPeer);
			mbContainsData = false;
		}
		else {
			MsgFormatPos oMessage = new MsgFormatPos(ResId.InvalidPropertyValueException);
			oMessage.format(containsType).format("setContains");
			throw new ExFull(oMessage);
		}
	}
	
	public void setContentType(String contentType) {
		if (isAttribute())
			throw new ExFull(ResId.CantSetContentType);
    	// JavaPort: remove all DOM children if 
		// existing content type is richtext.
    	if (getContentType().equals(STRS.TEXTHTML)) {
    		Node child = getFirstXMLChild();
    		while (child != null) {
    			child.remove();
    			child = getFirstXMLChild();
    		}
    	}
		setAttribute(STRS.XFADATANS_CURRENT, STRS.XFANAMESPACE + STRS.CONTENTTYPE, STRS.CONTENTTYPE, contentType, false);
	}

	public void setDataDescription(DataNode dataDescription) {
		mDataDescription = dataDescription;
	}

	public void setIsDDPlaceholder(boolean bIsDDPlaceholder) {
		mbIsDDPlaceholder = bIsDDPlaceholder;
	}

	/**
	 * Set the null status for this node
	 * 
	 * @param bNull
	 * @param bNotify
	 */
	public void setIsNull(boolean bNull, boolean bNotify /* = true */) {
		if (getClassTag() != XFA.DATAVALUETAG)
			return;
		
		if (bNotify)
			 deafen();
		
		// reset
		clearNull();
		
		updateIsNull(bNull);
		
		if (bNotify) {
			notifyPeers(Peer.VALUE_CHANGED, "", null);
			unDeafen();
		}
	}

	public void setName(String sName) {
		maName = null;
		setLocalName(sName);
	}

	/**
	 * Proprietary: set the data picture format for this data value Applicable
	 * to dataValue nodes
	 * 
	 * @param sFormat
	 * @param sLocale
	 * @param bIsTextNode
	 * 
	 * @exclude from public api.
	 */
	// Adobe patent application tracking # B136, entitled "Applying locale behaviors to regions of a form", inventors: Gavin McKenzie, Mike Tardif, John Brinkman"
	public void setPictureFormat(String sFormat,
			String sLocale /* = "" */, boolean bIsTextNode /* = false */) {
		
		if (null != mPictureFormatInfo && ! mPictureFormatInfo.msPictureFormat.equals(sFormat)) {
			MsgFormatPos oMessage = new MsgFormatPos(ResId.ConflictingDataPictureException);
			oMessage.format(getName());
			oMessage.format(sFormat);
			oMessage.format(mPictureFormatInfo.msPictureFormat);
			throw new ExFull(oMessage);
		}

		if (null == mPictureFormatInfo) {
			mPictureFormatInfo = new PictureFormatInfo(sFormat, sLocale);
			if (!StringUtils.isEmpty(sFormat))
				mbIsTextNode = bIsTextNode;
		}
	}

	// Proprietary: setPrivateName is used to set maName, which is returned
	// from getName when maName is not NULL. String version.
	public void setPrivateName(String sName) {
		// Proprietary: setPrivateName is used to set maName, which is returned
		// from getName when maName is not NULL. String version.
		maName = sName.intern();
	}

	/**
	 * Set the value of the node.
	 
	 * Note! if a DataValue has child DataValues, setting the value
	 * will delete all children.
	 * 
	 * @param sValue -
	 *            the new value for this node.
	 * @exclude from public api.
	 */
	public void setValue(String sValue, boolean bNotify /* = true */) {
		
		if (bNotify)
			deafen();
		
		clearNull();

		// Use picture format(if present) to convert from
		// canonical format 'sValue' to the desired format
		// 'sFormattedVal' .
		String sFormattedVal = formatDataValue(sValue);

		if (singleTextChild() == null) {
			
			if (getXmlPeer() instanceof AttributeWrapper) {
				AttributeWrapper wrapper = (AttributeWrapper)getXmlPeer();
				
				// Attributes are immutable in XFA4J, so we can't set the value directly
				// so create a new attribute, re-wrap it and re-peer this DataNode to it.

				Element parent = wrapper.getXMLParent();
				parent.setAttribute(wrapper.getNS(), wrapper.getXMLName(), wrapper.getLocalName(), sValue);
				// Now find the attribute that was just created
				int index = parent.findAttr(wrapper.getNS(), wrapper.getLocalName());
				assert index != -1;
				Attribute a = parent.getAttr(index);
				AttributeWrapper xmlPeer = new AttributeWrapper(a, parent);
				xmlPeer.setXfaPeer(this);
				setXmlPeer(xmlPeer);
			}
			else if (getXmlPeer() instanceof TextNode) {
				TextNode textNode = (TextNode)getXmlPeer();
				textNode.setValue(sValue, false, false);
			}
			else if (getContentType().equals(STRS.TEXTHTML)) {
				
				Element peer = (Element)getXmlPeer();
				
				// remove all dom children
				while (peer.getFirstXMLChild() != null)
					peer.getFirstXMLChild().remove();
				
				// Add a new text node.
				if (sFormattedVal.length() > 0) {
					TextNode t = new TextNode(peer, null, sFormattedVal);
					singleTextChild(t);
				}

				// if the content type was set to text/html, remove it
				int attr = peer.findAttr("", XFA.CONTENTTYPE);
				if (attr != -1 && peer.getAttrVal(attr).equals(STRS.TEXTHTML))
					peer.removeAttr(attr);
			} 
			else {

				// Must be an element node.
				// Has multiple dataValue children.
				
				Node nextChild;
				for (Node child = getFirstXFAChild(); child != null; child = nextChild) {
					nextChild = child.getNextXFASibling();

					if (child.isSameClass(XFA.DATAVALUETAG)) {
						// remove all data nodes leave any metadata nodes.
						DataNode dChild = (DataNode) child;
						if (!dChild.isAttribute())
							dChild.remove();
					}					
				}

				// Add a new text node.
				// Set it even if the data value is empty so that a workaround
				// in xfaformfieldimpl functions correctly. DJB June 12 2002
				// formerly: if (sValue.Length() > 0)
				TextNode t = new TextNode((Element)getXmlPeer(), null, sFormattedVal);
				singleTextChild(t);
			}
		} 
		else {
			// JavaPort: for consistency with C++, don't modify the bDefault flag.
			TextNode textNode = singleTextChild();
			textNode.setText(sFormattedVal);

			if (getNullType() == EnumAttr.NULLTYPE_EXCLUDE) {
				if (StringUtils.isEmpty(sFormattedVal))
					isTransient(true, false);				
				// JavaPort: I'm hoping that we can get away from the idea that
				// #text nodes can be transient. If so, saves a bit in the
				// private area of the Node class... JB 1 sept 2005
				// else if (singleTextChild().isTransient())
				// singleTextChild().isTransient(false);
			}
		}
		
		if (bNotify) {
			notifyPeers(Peer.VALUE_CHANGED, "", null);
			unDeafen();
		}
	}

	// set and get the weight of this node
	public int setWeight(int nWeight) {
		// set the weight
		mnWeight = nWeight;

		// add one for the next node.
		nWeight++;

		Node pChild = getFirstXFAChild();
		while (pChild != null) {
			if (pChild instanceof DataNode) {
				// set and record the weight for the next node
				nWeight = ((DataNode) pChild).setWeight(nWeight);
			}
			pChild = pChild.getNextXFASibling();
		}
		return nWeight;
	}

	/**
	 * Get the single text child
	 * 
	 * @return Our child that contains the value for this data node
	 * 
	 * @exclude from published api.
	 */
	TextNode singleTextChild() {
		return mSingleTextChild;
	}

	/**
	 * Set our single text child. Valid only for dataValues
	 * 
	 * @param child
	 */
	protected void singleTextChild(TextNode child) {
		mSingleTextChild = child;
	}

	/**
	 * return this value unformatted -- according to any defined picture format
	 * 
	 * @param sVal
	 *            the input value to unformat
	 * @return The unformatted result.
	 */
	String unformatDataValue(String sVal) {
		String sStr = sVal;
		if (mPictureFormatInfo != null) {
// Adobe patent application tracking # B136, entitled "Applying locale behaviors to regions of a form", inventors: Gavin McKenzie, Mike Tardif, John Brinkman"
			String sLocale = mPictureFormatInfo.msLocale;
			if (StringUtils.isEmpty(sLocale))
				sLocale = getInstalledLocale();
			boolean bSuccess = false;
			String sUnFormatted = null;
			if (mbIsTextNode && PictureFmt.isTextPicture(mPictureFormatInfo.msPictureFormat)) {
				StringHolder unformatted = new StringHolder();
				bSuccess = PictureFmt.parseText(sVal, mPictureFormatInfo.msPictureFormat, sLocale, unformatted);
				if (bSuccess)
					sUnFormatted = unformatted.value;
			}
			if (! bSuccess) {
				PictureFmt oPict = new PictureFmt(sLocale);
				BooleanHolder pbSuccess = new BooleanHolder();
				sUnFormatted = oPict.parse(sVal, mPictureFormatInfo.msPictureFormat, pbSuccess);
				bSuccess = pbSuccess.value;
			}

			// return sVal if unformatting fails.
			if (bSuccess)
				sStr = sUnFormatted;
		}	
		return sStr;
	}

	/**
	 * Determine if this node is transient or not. Transient nodes are not saved
	 * when the DOM is written out.
	 * @return boolean transient status.
	 *
	 * @exclude from published api.
	 */
	public final boolean isTransient() {
		Node domPeer = getXmlPeer();
		return domPeer.isTransient();
	}
	
	void updateIsNull(boolean bIsNull) {

		int eNullType = getNullType();
		Node domPeer = getXmlPeer();

		if (bIsNull) {
			// clear existing value
			// will remove exdata
			setValue("", false);

			if (eNullType == EnumAttr.NULLTYPE_EXCLUDE)
				domPeer.isTransient(true, false);
			else if (eNullType == EnumAttr.NULLTYPE_XSI && (domPeer instanceof Element && !(domPeer instanceof AttributeWrapper))) {
				((Element)domPeer).setXsiNilAttribute("true");
			}
		}
		// not null, set xsi attr
		else {
			if (eNullType == EnumAttr.NULLTYPE_EXCLUDE)
				isTransient(false, false);
			else if (eNullType == EnumAttr.NULLTYPE_XSI && (domPeer instanceof Element && !(domPeer instanceof AttributeWrapper))) {
				((Element)domPeer).setXsiNilAttribute("false");
			}
		}

		mbIsNull = bIsNull;
		mbIsNullDetermined = true;
	}
	
	/**
	 * Helper routine for compareVersions()
	 * @exclude from published api.
	 */
	private String getDataNodeAsXML(DataNode oNode) {
		if (oNode == null)
			return "";
		
		ByteArrayOutputStream oStream = new ByteArrayOutputStream();
		
		if (oNode.getXmlPeer() instanceof AttributeWrapper) {
			Attribute attribute = ((AttributeWrapper)oNode.getXmlPeer()).mAttribute;
			try {
				// TODO: need to use options, as specified below
				oStream.write(Document.MarkupSpace);
				oStream.write(attribute.getQName().getBytes("UTF-8"));
				oStream.write(Document.MarkupAttrMiddle);
				oStream.write(StringUtils.toXML(attribute.getAttrValue(), true).getBytes("UTF-8"));
				oStream.write(Document.MarkupDQuoteString);
			}
			catch (IOException ignored) {
				// IOException is not possible writing to ByteArrayOutputStream
				// UnsupportedEncodingException is not possible since UTF-8 is always supported
			}
		}
		else {		
			DataNode tempNode = (DataNode)oNode.clone(null);
			if (tempNode.getXmlPeer() instanceof Element)
				tempNode.getModel().normalizeNameSpaces((Element)tempNode.getXmlPeer(), "");
			
			DOMSaveOptions options = new DOMSaveOptions();
			// XFAPlugin Vantive bug#595482  Convert CR and extended characters to entity
			//	references.  Acrobat prefers these to raw utf8 byte data.
			options.setDisplayFormat(DOMSaveOptions.RAW_OUTPUT);
			options.setSaveTransient(true);
			options.setEntityChars("\r");
			options.setRangeMin('\u007f');
			options.setRangeMax('\u00ff');
			options.setExcludePreamble(true);
			tempNode.saveXML(oStream, options);
		}
		
		try {
			return new String(oStream.toByteArray(), "UTF-8");
		}
		catch (UnsupportedEncodingException ignored) {
			return "";	// not possible - UTF-8 is always supported
		}
	}
	
	/**
	 * Helper routine for compareVersions()
	 * @exclude from published api.
	 */
	private void logDomChange(
			Node oCurrent, Attribute currentAttr, 
			Node oRollback, Attribute rollbackAttr, 
			Node.ChangeLogger oChangeLogger, Object oUserData) {
		assert oChangeLogger != null;
		
		Node oModelledNode;
		boolean bCurrentModelled = false;
		boolean bRollbackModelled = false;
	
		Node oCurrentParent = null;
		Node oRollbackParent = null;
	
		if (oCurrent != null) {
			oModelledNode = getXfaPeer(oCurrent, currentAttr);
			
			if (oModelledNode == null && oCurrent instanceof Chars)
				oModelledNode = getXfaPeer(oCurrent.getXMLParent(), null);
			
			if (oModelledNode != null) {
				bCurrentModelled = true;
				oCurrentParent = oModelledNode;
			}
		}
		if (oRollback != null) {
			oModelledNode = getXfaPeer(oRollback, rollbackAttr);
			
			if (oModelledNode == null && oRollback instanceof Chars) oModelledNode = getXfaPeer(oRollback.getXMLParent(), null);
			
			if (oModelledNode != null) {
				bRollbackModelled = true;
				oRollbackParent = oModelledNode;
			}
		}
	
		while (oCurrentParent == null && oCurrent != null) {
			oCurrentParent = getXfaPeer(oCurrent, currentAttr);
			if (currentAttr != null) {
				currentAttr = null;
				oCurrent = null;
			}
			else
				oCurrent = oCurrent.getXMLParent();
		}
	
		while (oRollbackParent == null && oRollback != null) {
			oRollbackParent = getXfaPeer(oRollback, rollbackAttr);
			if (rollbackAttr != null) {
				rollbackAttr = null;
				oRollback = null;
			}
			else
				oRollback = oRollback.getXMLParent();
		}
		
//		boolean bBound, bModelled, bInXHTML;
//		if (oCurrentParent == null) {
//			bBound = isBound(oRollbackParent);
//			bModelled = bRollbackModelled;
//			bInXHTML = inXHTML(oRollbackParent);
//		}
//		else if (oRollbackParent == null) {
//			bBound = isBound(oCurrentParent);
//			bModelled = bCurrentModelled;
//			bInXHTML = inXHTML(oCurrentParent);
//		}
//		else {
//			bBound = isBound(oCurrentParent) && isBound(oRollbackParent);
//			bModelled = bCurrentModelled && bRollbackModelled;
//			bInXHTML = inXHTML(oCurrentParent) && inXHTML(oRollbackParent);
//		
//			// Hack for test suite: because we're testing two data DOMs in a single template rather than "current" 
//			// and "rollback" instances, current is never bound.  To test the other path, we just assume it is.
//			// JavaPort: Since this is in DataNode and not the unit test, it can't be
//			// checked in with this line in place, so the unit test baseline will diverge.
//			//bBound = isBound(oRollbackParent);
//		}
//		oChangeLogger.logDataChange(oCurrentParent, oRollbackParent, (bBound && (bModelled || bInXHTML)), getDataNodeAsXML((DataNode)oCurrentParent), oUserData);
		oChangeLogger.logDataChange(oCurrentParent, oRollbackParent, bCurrentModelled, bRollbackModelled, getDataNodeAsXML((DataNode)oCurrentParent), oUserData);	
	}
	
	/**
	 * Retrieves the XFA peer for a given XML DOM node.
	 * Unlike the C++ implementation, this implementation does not implement
	 * a back-reference from the jfNodeImpl back to the XFA object (i.e., via
	 * getUserObject). This is a much slower implementation that does a simple
	 * brute force search, but we don't really care about the speed since it would
	 * only be used in cases where a difference is being logged by DOM compare.
	 * @param xmlPeer the XML DOM node to find the corresponding XFA peer for.
	 * @return the XFA peer for the node, or null if no peer was found.
	 */
	@FindBugsSuppress(code="RCN") // node
	private static Node getXfaPeer(Node xmlPeer, Attribute attribute) {
		
		// JavaPort TODO: We do have back references now (but not from Attributes),
		// so this can probably be trimmed a bit to limit the part of the DOM that
		// is searched.
		
		Node node = xmlPeer;
		while (!(node instanceof ModelPeer) && !(node instanceof Model))
			node = node.getXMLParent();
		
		if (node == null) {
			assert false;
			return null;
		}
		
		// If the containing model isn't dual-DOM, xmlPeer it is its own xfa peer.
		if (node instanceof Model)
			return xmlPeer;
		
		Model model = (Model)((ModelPeer)node).getXfaPeer();
		if (model instanceof Model.DualDomModel) {
			Node xfaPeer = findMatchingXfaPeer(model, xmlPeer);
			
			if (attribute == null)
				return xfaPeer;
			
			if (xfaPeer != null) {
    			for (Node child = xfaPeer.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
    				Node childXmlPeer = ((DualDomNode)child).getXmlPeer();
    				if (childXmlPeer instanceof AttributeWrapper) {
    					AttributeWrapper wrapper = (AttributeWrapper)childXmlPeer;
    					
    					if (wrapper.mAttribute == attribute)
    						return child;
    				}
    			}
			}
			
			// Some attributes don't have a corresponding DataNode, so just return the
			// XFA parent that contains it.
			return null;
		}
		else {
			return xmlPeer;
		}
	}
	
	private static Node findMatchingXfaPeer(Node xfaPeer, Node xmlPeer) {
		if (((DualDomNode)xfaPeer).getXmlPeer() == xmlPeer)
			return xfaPeer;
		
		if (xfaPeer instanceof Element) {
			for (Node child = ((Element)xfaPeer).getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
				Node node = findMatchingXfaPeer(child, xmlPeer);
				if (node != null)
					return node;
			}
		}
		
		return null;
	}

	/**
	 * Helper routine for compareVersions()
	 */
	private boolean isEmptyTextNode(Node oNode) {
		if (oNode == null)
			return false;
		
		if (oNode instanceof Chars)
			return ((Chars)oNode).isXMLSpace();
	
		return false;
	}
	
	/**
	 * Helper routine for compareVersions()
	 */
	@FindBugsSuppress(code="ES,NP")
	private boolean compareDomNodes(Node oCurrent, Node oRollback, Node.ChangeLogger oChangeLogger, Object oUserData) {
		if (oCurrent == null && oRollback == null)
			return true;
		if (oCurrent == null || oRollback == null) {
			if (oChangeLogger != null)
				logDomChange(oCurrent, null, oRollback, null, oChangeLogger, oUserData);
			return false;
		}
		
		// There could be non-DataNode nodes in the DataNode such as a PI.
		String oCurrentName, oCurrentNS;
		if (oCurrent instanceof Element) {
			Element currentElement = (Element)oCurrent;
			oCurrentName = currentElement.getLocalName();
			oCurrentNS = currentElement.getNS();
		}
		else {
			oCurrentName = oCurrent.getName();
			oCurrentNS = "";
		}
		
		String oRollbackName, oRollbackNS;
		if (oRollback instanceof Element) {
			Element rollbackElement = (Element)oRollback;
			oRollbackName = rollbackElement.getLocalName();
			oRollbackNS = rollbackElement.getNS();
		}
		else {
			oRollbackName = oCurrent.getName();
			oRollbackNS = "";
		}
		
		if (oCurrentName != oRollbackName || oCurrentNS != oRollbackNS) {
			if (oChangeLogger != null)
				logDomChange(oCurrent, null, oRollback, null, oChangeLogger, oUserData);
			return false;
		}
		
		boolean	bMatches = true;
		
		//
		// Go through attributes
		//
		Element	oCurrentDataNode = null;
		int nSizeCurrent = 0;
		if (oCurrent instanceof Element) {
			oCurrentDataNode = (Element) oCurrent;
			nSizeCurrent = oCurrentDataNode.getNumAttrs();
		}
		Element	oRollbackDataNode = null;
		int nSizeRollback = 0;
		if (oRollback instanceof Element) {
			oRollbackDataNode = (Element) oRollback;
			nSizeRollback = oRollbackDataNode.getNumAttrs();
		}
		for (int i = 0; i < nSizeCurrent; i++) {
			Attribute oCurrentAttr = oCurrentDataNode.getAttr(i);
			if (oCurrentAttr.isNameSpaceAttr())
				continue;
			
			int nAt = oRollbackDataNode.findAttr(oCurrentAttr.getNS(), oCurrentAttr.getLocalName());
			Attribute oRollbackAttr = (nAt == -1) ? null : oRollbackDataNode.getAttr(nAt);
			
			if (oRollbackAttr == null || oCurrentAttr.getAttrValue().equals(oRollbackAttr.getAttrValue()) == false) {
				bMatches = false;
				if (oChangeLogger != null)
					logDomChange(oCurrent, oCurrentAttr, oRollback, oRollbackAttr, oChangeLogger, oUserData);
			}
		}
		for (int i = 0; i < nSizeRollback; i++) {
			Attribute oRollbackAttr = oRollbackDataNode.getAttr(i);
			if (oRollbackAttr.isNameSpaceAttr())
				continue;
			int nAt = oCurrentDataNode.findAttr(oRollbackAttr.getNS(), oRollbackAttr.getLocalName());
			Attribute oCurrentAttr = (nAt == -1) ? null : oCurrentDataNode.getAttr(nAt);

			//
			// Only check for missing attribute -- we've already compared values for those found in both above:
			if (oCurrentAttr == null) {
				bMatches = false;
				if (oChangeLogger != null)
					logDomChange(oCurrent, oCurrentAttr, oRollback, oRollbackAttr, oChangeLogger, oUserData);
			}
		}
		
		if (bMatches == false && oChangeLogger == null)
			return false;  // if we're not logging then there's no need to wait for the fat lady to sing...
		
		//
		// Recurse through the children, ignoring empty text & CDATA nodes
		//
		Node oCurrentChild = null;
		if (oCurrent instanceof Element) {
			oCurrentChild = oCurrent.getFirstXMLChild();
			while (isEmptyTextNode(oCurrentChild))
				oCurrentChild = oCurrentChild.getNextXMLSibling();
		}
		
		Node oRollbackChild = null;
		if (oRollback instanceof Element) {
			oRollbackChild = oRollback.getFirstXMLChild();
			while (isEmptyTextNode(oRollbackChild))
				oRollbackChild = oRollbackChild.getNextXMLSibling();
		}
		
		while (oCurrentChild != null || oRollbackChild != null) {
			
			if (!compareDomNodes(oCurrentChild, oRollbackChild, oChangeLogger, oUserData))
				bMatches = false;
			
			if (oCurrentChild != null) {
				oCurrentChild = oCurrentChild.getNextXMLSibling();
				while (isEmptyTextNode(oCurrentChild))
					oCurrentChild = oCurrentChild.getNextXMLSibling();
			}
			if (oRollbackChild != null) {
				oRollbackChild = oRollbackChild.getNextXMLSibling();
				while (isEmptyTextNode(oRollbackChild))
					oRollbackChild = oRollbackChild.getNextXMLSibling();
			}
		}
		
		//
		// Lastly, check any immediate value
		//
	
		String	sCurrentValue = oCurrent.getData();		
		String	sRollbackValue = oRollback.getData();
		
		//Bug#3047226: XTG does case insensitive comparison here. Need to do it explicitly in XFA4J.
		if (!sCurrentValue.equalsIgnoreCase(sRollbackValue)) {
			bMatches = false;
			if (oChangeLogger != null)
				logDomChange(oCurrent, null, oRollback, null, oChangeLogger, oUserData);
		}
	
		return bMatches;
	}
	
	/**
	 * Override of Element.compareVersions.
	 * 
	 * @exclude from published api.
	 */
	protected boolean compareVersions(Node oRollbackNode, Node oContainer, Node.ChangeLogger oChangeLogger, Object oUserData) {
		//
		// Move to the jfDOM tree for data compares.
		//
		if (oRollbackNode instanceof DualDomNode)
			oRollbackNode = ((DualDomNode)oRollbackNode).getXmlPeer();
		
		return compareDomNodes((Element)getXmlPeer(), oRollbackNode, oChangeLogger, oUserData);
	}
	
	/** @exclude */
	public void connectPeerToDocument() {
		super.connectPeerToDocument();
		
		if (getClassTag() == XFA.DATAGROUPTAG)
			((DataModel)getModel()).getDataWindow().resetRecordDepth();
	}

	/**
	 * @exclude from public api.
	 */
	public void setXmlPeer(Node peer) {
		mXmlPeer = peer;
		mbContainsData = !(peer instanceof AttributeWrapper);
	}
	/**
	 * @exclude from public api.
	 */
	public Node getXmlPeer() {
		return mXmlPeer;
	}
	private Node mXmlPeer;			// Our XML tree peer.

	
	/**
	 * @exclude from published api.
	 */
	protected ScriptDynamicPropObj getDynamicScriptProp(String sPropertyName,
										boolean bPropertyOverride, boolean bPeek) {
		
		//
		// Search the data description for a dd:association with the relevant name:
		//

		if (getClassTag() == XFA.DATAGROUPTAG) {
		
			DataNode dataDescription = getDataDescription();
			if (dataDescription != null) {
				for (DataNode child = (DataNode)dataDescription.getFirstXFAChild(); child != null; child = (DataNode)child.getNextXFASibling())	{
					Element assoc = (Element)child.getXmlPeer();
					if	(assoc.getNS() == STRS.DATADESCRIPTIONURI && assoc.getLocalName() == XFA.ASSOCIATION) {
					
						int index = assoc.findAttr(STRS.DATADESCRIPTIONURI, XFA.NAME);
						if (index != -1) {
							// Check the value against the property name
							if (assoc.getAttr(index).getAttrValue().equals(sPropertyName)) {
								return resolveAssociationPropObj;
							}
						}
					}
				}
			}
		}
		
		//
		// Fall through to superclass:
		//
			
		return super.getDynamicScriptProp(sPropertyName, bPropertyOverride, bPeek);
	}
	
	private final static ScriptDynamicPropObj resolveAssociationPropObj = 
		new ScriptDynamicPropObj(Schema.XFAVERSION_31, Schema.XFAAVAILABILITY_ALL) {
		
		public boolean invokeGetProp(Obj scriptThis, Arg retValue, String sPropertyName) {
			return DataNodeScript.scriptPropResolveAssociation(scriptThis, retValue, sPropertyName);
		}
	};
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy