com.adobe.xfa.data.DataNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
/*
* 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);
}
};
}