com.adobe.xfa.SOMParser 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;
import java.util.ArrayList;
import java.util.List;
import com.adobe.xfa.ut.BooleanHolder;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.MsgFormat;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.StringUtils;
/**
* SOMParser provides support for resolving a SOM (Scripting Object Model)
* expression into a list of nodes.
*/
public final class SOMParser {
/**
* Describes one returned value from the SOM parser.
*/
public final static class SomResultInfo {
/**
* The object member represents the Obj that the SOM expression refers
* to. This is often, but not always, actually a Node.
*/
public final Obj object;
/**
* The occurrence member indicates the occurrence number of the
* referenced property (if propertyName is not empty).
*/
public final int occurrence;
/**
* If the SOM expression refers to a property then the propertyName
* field contains the name of the property. For example, for the SOM
* expression "datavalue.value"
, the property name would be
* "value"
in this example). This is useful for the left
* hand side of expressions involving SOM statements. Note that if a
* method is invoked, then this contains the text representation of the
* method call (not that this is terribly useful in this case).
*/
public final String propertyName;
/**
* The value field indicates the "answer" to the SOM expression. For
* example, for datavalue.value, value would be set to be a string
* containing the value of the datavalue. In the case of a method call,
* value contains the return value of the method. For convenience, if
* the SOM expression returns an Obj without any properties, then value
* is set to contain the Obj. Note that value does not contain the
* "default value" of the Obj in this case.
*/
public final Arg value;
/**
* @exclude from published api.
*/
SomResultInfo(Obj initObject) {
object = initObject;
occurrence = 0;
value = new Arg();
propertyName = null;
value.setObject(initObject);
}
/**
* @exclude from published api.
*/
SomResultInfo(Obj initObject, String initPropertyName,
int initOccurrence, Arg initValue) {
object = initObject;
propertyName = initPropertyName;
occurrence = initOccurrence;
value = initValue;
}
}
private static final int BRT_ABSOLUTE = 2;
private static final int BRT_ALL = 0;
private static final int BRT_LAST = 3;
private static final int BRT_RELATIVE = 1;
private static final Arg[] emptyParameters = new Arg[0];
/**
* We used to simply use strchr, but now that parameters can be part of a
* SOM expression, the search for characters needs to be smarter. For
* example, a.function(3.5) or a.function("text[]") would cause problems.
*/
private static int findChar(String src, int offset, char charToFind) {
final int length = src.length();
for (; offset < length; offset++) {
final char c = src.charAt(offset);
if (c == charToFind)
return offset;
if (c == '\\') {
// Skip over the escape characters (\)
offset++; // skip past the '\'
}
else if (c == '"' || c == '\'') {
final char cQuote = c;
for (offset++; offset < length; offset++) {
// check for doubled-up quotes -- which are considered to be an
// inline quote as opposed to the end of the literal string.
if (src.charAt(offset) == cQuote) {
if (offset < length - 1 && src.charAt(offset + 1) != cQuote)
break; // found a terminating quote (normal case)
offset++; // doubled-up quote -- skip past this quote
}
}
}
else if (c == '(' || c == '[') {
// look for matching parenthesis (called recursively to handle quotes)
int rightParenOrBracket = findChar(src, offset + 1, c == '(' ? ')' : ']');
if (rightParenOrBracket == -1)
return -1;
offset = rightParenOrBracket;
}
}
return -1;
}
// SomResultInfo describes one returned value from the SOM parser.
// The object member represents the XFAObject that the SOM expression
// refers to. This is often, but not always, actually an Node.
//
// If the SOM expression refers to a property (eg. datavalue.value)
// then the propertyName member contains the name of the property
// ("value" in this example). This is useful for the left hand side
// of expressions involving SOM statements. Note that if a method
// is invoked, then this contains the text representation of the method
// call (not that this is terribly useful in this case).
//
// The occurrence member indicates the occurrence number of the referenced
// property (if propertyName is not empty).
//
// The value member indicates the "answer" to the SOM expression. For
// example, for datavalue.value, value would be set to be a string
// containing the value of the datavalue. In the case of a method call,
// value contains the return value of the method. For convenience, if
// the SOM expression returns an XFAObject without any properties,
// then value is set to contain the XFAObject. Note that value does
// not contain the "default value" of the XFAObject in this case.
/**
* findDot is equivalent to findChar(p_string, '.'), with the exception that
* it will not return a pointer to ".(" or ".[". This is because, for
* example, a.[c>0] is considered to be one section of SOM, not two.
* @exclude from published api.
*/
static int findDot(String src, int offset) {
int p_dot = findChar(src, offset, '.');
while (p_dot != -1 && p_dot < src.length() - 1 &&
(src.charAt(p_dot + 1) == '(' || src.charAt(p_dot + 1) == '['))
p_dot = findChar(src, p_dot + 1, '.');
return p_dot;
}
/**
* @exclude from published api.
*/
static String escapeSomName(String sSomExpr) {
if (sSomExpr.indexOf('.') != -1) {
// Contains a '.' (which must be escaped with a '\').
// Turn all the "."'s into "\.", to represent the node's SOM expression properly.
StringBuilder somExpr = new StringBuilder(sSomExpr);
escapeSomName(somExpr);
return somExpr.toString();
}
else {
return sSomExpr;
}
}
/**
* @exclude from published api.
*/
static void escapeSomName(StringBuilder somExpr) {
// Contains a '.' (which must be escaped with a '\').
// Turn all the "."'s into "\.", to represent the node's SOM expression properly.
int nOffset = somExpr.length() - 1;
while ((nOffset = somExpr.lastIndexOf(".", nOffset)) != -1) {
somExpr.replace(nOffset, nOffset + 1, "\\.");
}
}
/**
* @exclude from published api.
*/
static String unescapeSomName(String sSomExpr) {
if (sSomExpr.contains("\\.")) {
StringBuilder somExpr = new StringBuilder(sSomExpr);
unescapeSomName(somExpr);
return somExpr.toString();
}
else {
return sSomExpr;
}
}
/**
* @exclude from published api.
*/
static void unescapeSomName(StringBuilder somExpr) {
int nOffset = 0;
while ((nOffset = somExpr.indexOf("\\.", nOffset)) != -1) {
somExpr.replace(nOffset, nOffset + 2, ".");
}
}
private static int getTreeIndex(Node poTree) {
if (poTree == null)
return 1;
boolean bUseName = poTree.useNameInSOM();
return poTree.getIndex(bUseName);
}
private final List m_RefStack = new ArrayList();
private boolean mbLastInstance;
private boolean mbNoProperties;
private boolean mbPeek;
/**
* This member variable exists so that we can return an extra parameter from
* parseBrackets()
*/
private int mBracketType;
private DependencyTracker mDependencyTracker;
private Object[] mObjectParameters;
private Arg[] mParameters; // used for calling functions (InvokeFunction)
/**
* @exclude from published api.
*/
public SOMParser(DependencyTracker dependencyTracker /* = null */) {
mDependencyTracker = dependencyTracker;
}
/**
* Constructs a new SOMParser.
*/
public SOMParser() {
this(null);
}
// Portions of this code implement AdobePatentID="1082"
final static String patentRef = "AdobePatentID=\"B1082\"";
private boolean locateNodes(
Obj startObject,
String name,
int brt,
int index,
boolean searchParent,
List somResult,
String somExpression,
BooleanHolder isAssociation) {
if (name.equals("*")) {
if (!(startObject instanceof Node)) {
MsgFormatPos oErr = new MsgFormatPos(
ResId.SearchNonNodeSOMException);
oErr.format(startObject.getClassName());
oErr.format(somExpression);
throw new ExFull(oErr);
}
Node startNode = (Node) startObject;
NodeList allChildren = startNode.getNodes();
int nLen = allChildren.length();
// TBD -- could handle BRT_RELATIVE too, i.e. A.*[+2]
if ((brt == BRT_RELATIVE) && (index != 0))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
if (brt == BRT_ABSOLUTE) {
if (index > nLen)
return false;
somResult.add(new SomResultInfo(allChildren.item(index)));
return true;
}
if (brt == BRT_LAST && nLen > 0) {
somResult.add(new SomResultInfo(allChildren.item(nLen - 1)));
return true;
}
// last
for (int i = 0; i < nLen; i++)
somResult.add(new SomResultInfo(allChildren.item(i)));
return true;
}
Arg arg = new Arg();
StringBuilder sFunctionName = new StringBuilder();
mParameters = null;
boolean bUseName = !name.startsWith("#");
boolean bPassed = false;
if (parseParams(name, sFunctionName, somExpression)) {
bPassed = startObject.invokeFunction(arg, sFunctionName.toString(),
mParameters, mDependencyTracker, false);
if (arg.getArgType() == Arg.EXCEPTION)
throw arg.getException();
} else {
bPassed = startObject.getScriptProperty(arg, name,
mDependencyTracker, mbPeek, true);
}
Node childNode = null;
if (bPassed) {
if (arg.getArgType() == Arg.OBJECT) {
Obj obj = arg.getObject();
AppModel appModel = null;
if (startObject instanceof Element)
appModel = ((Element)startObject).getAppModel();
if (obj == null)
arg.empty();
else {
if (obj instanceof NodeList) {
NodeList nodeList = (NodeList)obj;
if ( appModel == null && (nodeList.length() > 0) && (nodeList.item(0) != null) && (nodeList.item(0) instanceof Element))
appModel = ((Element)nodeList.item(0)).getAppModel();
//fix for watson bug#2426694 -- check if the SOM resolved object is a property or not
if (appModel != null && !appModel.getLegacySetting(AppModel.XFA_LEGACY_V32_SCRIPTING))
{
if(mbNoProperties & arg.isXFAProperty())
return false;
}
if (appModel != null && !appModel.getLegacySetting(AppModel.XFA_LEGACY_V30_SCRIPTING)) {
if (isAssociation != null)
isAssociation.value = true;
if (brt == BRT_ALL) {
for (int i = 0; i < nodeList.length(); i++)
somResult.add(new SomResultInfo(nodeList.item(i), null, 0, arg));
}
else if (brt == BRT_LAST)
somResult.add(new SomResultInfo(nodeList.item(nodeList.length() - 1), null, 0, arg));
else if (brt == BRT_ABSOLUTE)
somResult.add(new SomResultInfo(nodeList.item(index), null, 0, arg));
else if (brt == BRT_RELATIVE) {
if (index == 0) // return the nodeList itself
somResult.add(new SomResultInfo(nodeList, null, 0, arg));
else // can't do nodeList[+3]
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
}
return true;
}
}
if (!(obj instanceof Node)) {
// It's a non-XFATree XFAObject. Just return it.
somResult.add(new SomResultInfo(arg.getObject()));
return true;
}
// It's an XFATree-derived class
childNode = (Node) obj;
//fix for watson bug#2426694 -- check if the SOM resolved object is a property or not
if (appModel != null && !appModel.getLegacySetting(AppModel.XFA_LEGACY_V32_SCRIPTING))
{
if(mbNoProperties & arg.isXFAProperty())
return false;
}
// Careful with relational models: if "holding.owner" resolves to a , then the normal
// behavior of the SOM parser is to expand "holding.owner[*]" to all like-named siblings of
// said , rather than those returned from the association "holding.owner".
if (arg.isRefObject()) {
if (isAssociation != null)
isAssociation.value = true;
// only allow
if (brt == BRT_ALL || brt == BRT_LAST || (brt == BRT_ABSOLUTE && index == 0) || (brt == BRT_RELATIVE && index == 0))
somResult.add(new SomResultInfo(childNode, null, 0, arg));
else
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
return true;
}
}
}
if ((childNode == null) && (arg.getArgType() != Arg.EXCEPTION)) {
// This is a property which does not evaluate to an XFATree.
// If the SOM expression is something like "name[*]", this is valid only
// if looking for (data) elements named "name" (i.e. mbNoProperties is true).
// The "[*]" is invalid if we're looking for the "name" property.
// If we're here while looking for name elements, we didn't find any. So
// return now before checking for invalid [*] on a property ref.
// Watson 1131766 --jak
if (mbNoProperties)
return false; // Caller doesn't want properties.
// Ensure only a simple bracket reference is made.
int nPropIndex = 0;
if ((brt == BRT_RELATIVE) && (index == 0))
nPropIndex = 0;
else if (brt == (BRT_ABSOLUTE))
nPropIndex = index;
else
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // can't do property[+3] or property[*]
somResult.add(new SomResultInfo(startObject, name, nPropIndex, arg));
return true;
}
}
if (childNode != null) {
// ensure we can use the name
if (!childNode.useNameInSOM())
bUseName = false;
// It's an XFATree-derived class. Handle any bracket notation.
if (brt == BRT_ALL || brt == BRT_LAST) {
NodeList all = null;
int nLen = 0;
if (childNode instanceof Element) {
all = ((Element) childNode).getAll(bUseName);
nLen = all.length();
}
// add a dependency on the parent of childNode (so that child add/remove
// notifications get noticed).
if (mDependencyTracker != null)
mDependencyTracker.addDependency(startObject);
// last
if (brt == BRT_LAST && nLen > 0) {
somResult.add(new SomResultInfo(all.item(nLen - 1)));
return true;
}
for (int i = 0; i < nLen; i++)
somResult.add(new SomResultInfo(all.item(i)));
return true;
}
int nSearchIndex = index;
if (brt == BRT_RELATIVE) {
if (m_RefStack.size() == 0)
nSearchIndex = index; // eg. node[0] finds first occurrence
else
nSearchIndex = getTreeIndex(m_RefStack.get(m_RefStack.size() - 1)) + index;
// using int, not size_t, so we can compare < 0
// (this is just for efficiency... getSibling will handle an invalid index,
// but a 0 or a negative number would be interpreted as a huge number and it
// would scan the siblings needlessly).
if (((int) nSearchIndex) < 0)
return false; // invalid index
}
// childNode is the first occurrence of nodes by the desired name. If
// nSearchIndex is 0, then childNode is the node that should be returned.
// So in this case we don't call getSibling, for efficiency.
if (nSearchIndex == 0 && !childNode.isTransparent())
somResult.add(new SomResultInfo(childNode));
else {
Node node = childNode.getSibling(nSearchIndex, bUseName, false);
if (node == null) {
// Process this special rule from the spec:
// "If a multiply-occurring field makes reference to a singly-occurring field
// with no explicit occurrence indication, that single occurrence is always found."
//
// (brt == BRT_RELATIVE && index == 0) means that no occurrence number was specified.
//
// Given that no occurrence number was specified, (nSearchIndex > 0) means that
// we're looking for an index matching our own.
if (brt == BRT_RELATIVE && index == 0 && nSearchIndex > 0) {
// Determine if this is a multiply-occurring field by hunting
// for the second instance (index 1).
// For efficiency, don't search again if nSearchIndex == 1,
// because we just searched for that and we know it's not there.
Node oSecondInstance = null;
if (nSearchIndex > 1)
oSecondInstance = childNode.getSibling(1, bUseName,
false);
if (oSecondInstance == null)
node = childNode.getSibling(0, bUseName, false);
if (node == null)
return false;
} else {
return false;
}
}
somResult.add(new SomResultInfo(node));
}
return true;
}
if (searchParent) {
if (!(startObject instanceof Node)) {
MsgFormatPos oErr = new MsgFormatPos(ResId.SearchNonNodeSOMException);
oErr.format(startObject.getClassName());
oErr.format(somExpression);
throw new ExFull(oErr);
}
Node startNode = (Node) startObject;
// Not found in children. Check parent & parent's parent all the way up to the root.
Element oParent = startNode.getXFAParent();
if (oParent != null) {
// Remember this node in case there are any relative occurrences later on;
// at that point we'll need to know its occurrence number.
m_RefStack.add(startNode);
if (locateNodes(oParent, name, brt, index, true, somResult, somExpression, isAssociation))
return true;
} else {
// Special case -- node has no parent. Match if 'name' matches startNode's name.
boolean bValid = false;
if ((brt == BRT_RELATIVE) && (index == 0)) // allow for example xfa
bValid = true;
else if ((brt == BRT_ABSOLUTE) && (index == 0)) // allow for example xfa[0]
bValid = true;
if (!bValid)
return false; // only allow a simple reference
m_RefStack.add(null); // Put NULL on the stack for its implicit index of 1
if (name.equals(startNode.getName())) {
somResult.add(new SomResultInfo(startNode));
return true;
}
}
}
return false;
}
private int parseBrackets(String bracketSection, int offset, String somExpression) {
int digit_ptr;
boolean bNegative = false; // true if index should be negative
int index = 0;
while (Character.isWhitespace(bracketSection.charAt(offset)))
offset++;
if (bracketSection.charAt(offset) == '*') {
offset++;
while (Character.isWhitespace(bracketSection.charAt(offset)))
offset++;
if (bracketSection.charAt(offset) != ']')
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// all elements
if (mbLastInstance)
mBracketType = BRT_LAST;
else
mBracketType = BRT_ALL;
return index;
} else if ((bracketSection.charAt(offset) == '+')
|| (bracketSection.charAt(offset) == '-')) {
mBracketType = BRT_RELATIVE;
bNegative = (bracketSection.charAt(offset) == '-');
// a relative index. A number should follow.
digit_ptr = offset + 1;
} else {
mBracketType = BRT_ABSOLUTE;
digit_ptr = offset;
}
// parse out the index
while (Character.isWhitespace(bracketSection.charAt(digit_ptr)))
digit_ptr++;
if (!Character.isDigit(bracketSection.charAt(digit_ptr)))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// swscanf seems to be buggy, so do simple integer parsing
while (Character.isDigit(bracketSection.charAt(digit_ptr))) {
index *= 10;
index += bracketSection.charAt(digit_ptr) - 48; // 48 == '0'
digit_ptr++;
}
if (bNegative)
index = -index;
// verify that there's nothing (except possibly whitespace) after the index
while (Character.isWhitespace(bracketSection.charAt(digit_ptr)))
digit_ptr++;
if (bracketSection.charAt(digit_ptr) != ']')
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
return index;
}
private boolean parseParams(String name, StringBuilder sFunctionName, String somExpression) {
int p_name = 0;
int p_left_paren = findChar(name, p_name, '(');
if (p_left_paren == -1)
return false; // no '(' found; not a function invocation
// find the right parenthesis
int p_right_paren = findChar(name, p_left_paren+1, ')');
if (p_right_paren == -1)
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // no right parenthesis
// ensure the right parenthesis is exactly at the end of the string we're looking at
if (((p_right_paren - p_name) != name.length() - 1))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
sFunctionName.setLength(0);
sFunctionName.append(name, 0, p_left_paren - p_name);
// Trim trailing whitespace
while (sFunctionName.length() > 0 && Character.isWhitespace(sFunctionName.charAt(sFunctionName.length() - 1)))
sFunctionName.setLength(sFunctionName.length() - 1);
mParameters = null;
int p_scan = p_left_paren+1;
while (Character.isWhitespace(name.charAt(p_scan)))
p_scan++;
while (name.charAt(p_scan) != ')') {
Arg arg = new Arg();
int p_param = p_scan;
if (name.charAt(p_scan) == '-' || Character.isDigit(name.charAt(p_scan))) {
p_scan++;
while (Character.isDigit(name.charAt(p_scan)) || name.charAt(p_scan) == '.')
p_scan++;
String sNum = name.substring(p_param, p_scan);
double dNum= 0d;
try {
dNum = Double.parseDouble(sNum);
} catch (NumberFormatException n) {
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // malformed number
}
if (Double.isInfinite(dNum))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // malformed number
arg.setDouble(new Double(dNum));
}
else if (name.charAt(p_scan) == '"' || name.charAt(p_scan) == '\'') {
char cQuote = name.charAt(p_scan);
p_param++;
p_scan++;
boolean bInlineQuote = false; // TRUE if we found a doubled-up quote
for(;;) {
if (p_scan == name.length())
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // no closing quote
// check for doubled-up quotes -- which are considered to be an
// inline quote as opposed to the end of the literal string.
if (name.charAt(p_scan) == cQuote) {
if (p_scan == name.length() || name.charAt(p_scan + 1) != cQuote)
break; // found a terminating quote (normal case)
p_scan++; // doubled-up quote -- skip past this quote
bInlineQuote = true;
}
p_scan++;
}
StringBuilder sStringArg = new StringBuilder(name.substring(p_param, p_scan));
if (bInlineQuote) {
String sDoubledQuote = null; // construct either "" or '' depending on quote
if (cQuote == '\'')
sDoubledQuote = "''";
else
sDoubledQuote = "\"\"";
int nFoundAt = sStringArg.indexOf(sDoubledQuote);
while (nFoundAt != -1) {
sStringArg.delete(nFoundAt, nFoundAt + 1);
nFoundAt = sStringArg.indexOf(sDoubledQuote, nFoundAt + 1);
}
}
arg.setString(sStringArg.toString());
p_scan++; // skip past closing quote
}
else if (name.charAt(p_scan) == '%') // handle keyword %null%
{
if (name.regionMatches(p_scan, "%null%", 0, 6)) {
arg.empty();
p_scan += 6; // skip over keyword
}
else
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // malformed keyword
}
else if (name.charAt(p_scan) == '#') { // object reference (index is into mpoObjectParameters)
// We can't verify that the index is valid, but at least check
// that object parameters have been provided.
if (mObjectParameters == null)
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
p_scan++; // skip over '#'
// parse out the index
if (! Character.isDigit(name.charAt(p_scan)))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
int index = 0;
// swscanf seems to be buggy, so do simple integer parsing
while (Character.isDigit(name.charAt(p_scan))) {
index *= 10;
index += name.charAt(p_scan) - 48; // 48 is ascii '0'
p_scan++;
}
arg.setObject((Obj)mObjectParameters[index]);
}
else {
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
}
Arg[] newParameters = new Arg[mParameters == null ? 1 : mParameters.length + 1];
if (mParameters != null)
System.arraycopy(mParameters, 0, newParameters, 0, mParameters.length);
mParameters = newParameters;
mParameters[mParameters.length - 1] = arg;
while (Character.isWhitespace(name.charAt(p_scan)))
p_scan++;
if (name.charAt(p_scan) == ',') {
p_scan++; // skip over ',' to get ready for next parameter
if (name.charAt(p_scan) == ')')
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
while (Character.isWhitespace(name.charAt(p_scan)))
p_scan++;
}
else if (name.charAt(p_scan) != ')') {
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
}
}
if (mParameters == null)
mParameters = emptyParameters;
return true;
}
/**
* Resolves a SOM expression to the list of Obj that the SOM expression represents.
* @param startNode the Node to start at (the current node)
* @param somExpression the SOM expression to be resolved.
* @param result a List of SomResultInfo that will be populated with the result
* of evaluating the SOM expression.
* @return true
if any nodes were added to the result.
*/
public boolean resolve(Node startNode, String somExpression, List result) {
return resolve(startNode, somExpression, null, result, null);
}
/**
* Resolves a SOM expression to the list of Obj that the SOM expression represents.
* @param startNode the Node to start at (the current node)
* @param somExpression the SOM expression to be resolved.
* @param result a List of SomResultInfo that will be populated with the result
* of evaluating the SOM expression.
* @param isAssociation (out) will be set to true
if the nodes returned include an association
* @return true
if any nodes were added to the result.
* @exclude from published api.
*/
public boolean resolve(Node startNode, String somExpression, List result, BooleanHolder isAssociation) {
return resolve(startNode, somExpression, null, result, isAssociation);
}
/**
* @exclude from published api.
*/
boolean resolve(
Node startNode,
String somExpression,
ArrayNodeList result,
BooleanHolder isAssociation /* = null */) {
List nodeInfo = new ArrayList();
// Clear the contents of result
while (result.length() > 0)
result.remove(result.item(0));
if (!resolve(startNode, somExpression, nodeInfo, isAssociation))
return false;
for (int i = 0; i < nodeInfo.size(); i++) {
// check for pseudo models;
if (nodeInfo.get(i).propertyName == null) {
Obj obj = nodeInfo.get(i).object;
if (obj instanceof Node)
result.append(obj);
// watson bug 1517238, support xfa.record in resolveNode
else if (obj instanceof PseudoModel) {
// get the alias object
PseudoModel pseudoModel = (PseudoModel) obj;
obj = pseudoModel.getAliasObject();
// if alias object is a tree
if (obj instanceof Node)
result.append(obj);
}
}
}
return true;
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="EI2")
public boolean resolve(
Node startNode,
String somExpression,
Object[] objectParameters,
List result,
BooleanHolder isAssociation /* = null */) {
mObjectParameters = objectParameters;
m_RefStack.clear(); // reset reference stack
int p_dot = findDot(somExpression, 0);
int prev = 0;
boolean beforeFirstDot = true;
// trim whitespace off the front
while (prev < somExpression.length() && Character.isWhitespace(somExpression.charAt(prev)))
prev++;
// Look for a '!' on the front (this is the only place that it's valid).
// If it's there, it's equivalent to "xfa.datasets."
if (prev < somExpression.length() && somExpression.charAt(prev) == '!') {
Node parentNode = startNode;
// find the top-most model
if (startNode instanceof Node) {
parentNode = startNode.getModel();
}
while (parentNode.getXFAParent() != null)
parentNode = parentNode.getXFAParent(); // climb all the way to the top
result.add(new SomResultInfo(parentNode)); // prime list with the model
if (!resolveDotSection("datasets", 0, 8, true, result, false,
somExpression, isAssociation))
return false;
prev++; // skip past the '!'
}
else {
result.add(new SomResultInfo(startNode)); // prime list with current node
}
if (p_dot == prev) // don't allow SOM expression to start with '.'
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
boolean bSearchChildren = false;
while (p_dot != -1) {
if (p_dot == prev) { // means we encountered ".."
bSearchChildren = true;
prev++; // skip past the '.'
if (prev < somExpression.length() && somExpression.charAt(prev) == '.') // ensure there's only two '.' characters
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
p_dot = findDot(somExpression, prev);
if (p_dot == -1)
break;
}
if (!resolveDotSection(somExpression, prev, p_dot - prev, beforeFirstDot, result, bSearchChildren,
somExpression, isAssociation))
return false;
beforeFirstDot = false;
prev = p_dot + 1;
p_dot = findDot(somExpression, prev);
bSearchChildren = false;
}
if (!resolveDotSection(somExpression, prev, somExpression.length() - prev, beforeFirstDot,
result, bSearchChildren, somExpression, isAssociation))
return false;
return true;
}
private boolean resolveDotSection(
String src,
int dotSection, // offset into src
int length,
boolean beforeFirstDot,
List somResult,
boolean bSearchChildren,
String somExpression,
BooleanHolder isAssociation /* = null */) {
// trim whitespace off both ends
while (dotSection < src.length() && Character.isWhitespace(src.charAt(dotSection))) {
dotSection++;
length--;
}
while (length > 0 && (dotSection + length - 1) < src.length() && Character.isWhitespace(src.charAt(dotSection + length - 1)))
length--;
if (length == 0)
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
boolean bAllChildren = (length == 1) && (src.charAt(dotSection) == '*');
if ((bAllChildren) && (beforeFirstDot))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// the default bracket setting represents current occurrence
int brt = BRT_RELATIVE;
int bracket_index = 0;
int p_script_check = findChar(src, dotSection, '.');
String p_script_lang = null;
String sScriptContents = ""; // Empty if not using predicates
if ((p_script_check != -1) && (p_script_check - dotSection) < length) {
int p_scriptcontents_end = -1;
p_script_check++; // skip over '.'
if (src.charAt(p_script_check) == '[') {
p_script_lang = "formcalc";
p_scriptcontents_end = findChar(src, p_script_check + 1, ']');
}
else if (src.charAt(p_script_check) == '(') {
p_script_lang = "javascript";
p_scriptcontents_end = findChar(src, p_script_check + 1, ')');
}
else
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
if ((p_scriptcontents_end != -1)
&& ((p_scriptcontents_end - dotSection) < length)) {
sScriptContents = src.substring(p_script_check + 1,
p_scriptcontents_end);
}
else
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// Adjust length (trim off .[ or .( and beyond)
length = p_script_check - dotSection - 1;
// Check for any garbage characters preceding the .[ or .(.
int p_garbage = findChar(src, dotSection, '[');
if ((p_garbage != -1) && ((p_garbage - dotSection) < length))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
p_garbage = findChar(src, dotSection, ']');
if ((p_garbage != -1) && ((p_garbage - dotSection) < length))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// Look for '*' in the dot section, but not if the dot section is "*" itself (meaning length is 1).
// We want xyz.*.[ condition ] to be accepted.
if (length > 1) {
p_garbage = findChar(src, dotSection, '*');
if ((p_garbage != -1) && ((p_garbage - dotSection) < length))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
}
// Change bracket reference type to ALL (same as *) -- result will be post-filtered.
brt = BRT_ALL;
}
else {
// Look for any array references.
// Is there a left bracket?
int p_left_bracket = findChar(src, dotSection, '[');
if ((p_left_bracket != -1)
&& ((p_left_bracket - dotSection) < length)) {
// find the right bracket
int p_right_bracket = findChar(src, p_left_bracket + 1, ']');
if (p_right_bracket == -1)
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression)); // no right bracket
// ensure the right bracket is exactly at the end of the string we're looking at
if (((p_right_bracket - dotSection) != length - 1))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
bracket_index = parseBrackets(src, p_left_bracket + 1,
somExpression);
brt = mBracketType;
length = p_left_bracket - dotSection;
// trim whitespace off the end again
while ((length > 0)
&& (Character.isWhitespace(src.charAt(dotSection
+ length - 1))))
length--;
}
else if (!bAllChildren) {
// no left bracket; check for a right bracket or "*" (neither
// should exist)
int p_garbage = findChar(src, dotSection, ']');
if ((p_garbage != -1) && ((p_garbage - dotSection) < length))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
p_garbage = findChar(src, dotSection, '*');
if ((p_garbage != -1) && ((p_garbage - dotSection) < length))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
}
}
// name of node that we're looking for, without any array referencing notation
StringBuilder s = new StringBuilder(src.substring(dotSection, dotSection + length));
// See if the name contains a . which has been escaped by a '\'. Take care not to adjust
// a function of the form: resolveNodes("xfa[0].datasets[0].data[0].Exa\.mple[0]").
if (s.indexOf(".") != -1 && s.indexOf("(") == -1) {
// Contains a '.' (which must have been escaped with a '\').
// Turn all the "\." pairs into ".", to represent the node's real name.
unescapeSomName(s);
}
if (s.length() == 0)
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// look for special keywords $, $data etc., which are only valid before the first dot
// If present, these may replace the contents of somResult. Since these are only valid
// before the first dot, somResult would have originally consisted of only the current
// node anyway.
if (src.charAt(dotSection) == '$') {
// first ensure that only a simple reference was made
if ((brt != BRT_RELATIVE) || (bracket_index != 0)) {
// don't allow $data[+1] or $data[1] or $data[*], for example
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
}
if (length == 1) // i.e. s == "$", or current node
return true; // leave current node alone in somResult
if (!beforeFirstDot) // $ only allowed in first dot section
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
SomResultInfo sri = somResult.get(0);
if (!(sri.object instanceof Node)) {
// It shouldn't be possible to get to this point, but better
// safe than sorry.
return false;
}
if (sri.object instanceof Node) {
// find the top-most node
Model parentModel = ((Node) sri.object).getModel();
if (parentModel == null)
return false;
AppModel appModel = parentModel.getAppModel();
// the (only) element in somResult is replaced by the matching pseudo-root node(s)
somResult.clear();
// lookup special "root", eg. $data, $form etc.
// first verify that it's syntactically valid
if (!Character.isLetter(s.charAt(1)))
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
String sPseudoRootAtom = s.toString();
Node pseudoRoot = Model.lookupShortCut(appModel, sPseudoRootAtom);
if (pseudoRoot != null) {
somResult.add(new SomResultInfo(pseudoRoot));
return true;
}
// second chance -- look up a pseudo-model (which is not necessarily
// derived from XFATree)
Obj obj = appModel.lookupPseudoModel(sPseudoRootAtom);
// If it's a pseudomodel-derived class, use its alias node. $dataWindow, for example,
// isn't actually derived from a pseudo-model.
if (obj instanceof PseudoModel) {
// get the alias object
PseudoModel pPseudoModelIMpl = (PseudoModel) obj;
obj = pPseudoModelIMpl.getAliasObject();
}
if (obj != null) {
somResult.add(new SomResultInfo(obj));
return true;
}
}
return false; // some unknown name beginning with $
}
if ((src.charAt(dotSection) == '#') && (s.toString().equals("#0"))) {
// SOM expressions can start with #0, which means that
// mpoObjectParameters[0] is
// the object to use. This is for object support in FormCalc.
if ((!beforeFirstDot) || // #0 only allowed in first dot section
(mObjectParameters == null)) // parameters must be supplied
throw new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
// the (only) element in somResult is replaced by the #0 object
somResult.clear();
somResult.add(new SomResultInfo((Obj) mObjectParameters[0]));
return true;
}
// First, resolve ".." syntax, if specified, by hunting for a child node by name.
// Nodes are deleted if the child cannot be found, or are replaced by the child's
// parent node.
int nOldResultsSize = somResult.size();
int i;
if (bSearchChildren) { // means we've encountered ".." notation
Node childNode = null;
for (i = 0; i < nOldResultsSize; i++) {
Obj obj = somResult.get(i).object;
if (!(obj instanceof Node)) {
MsgFormatPos oErr = new MsgFormatPos(
ResId.SearchNonNodeSOMException);
oErr.format(obj.getClassName());
oErr.format(somExpression);
throw new ExFull(oErr);
}
StringBuilder sChildName = new StringBuilder(s);
boolean bSearchByClass = (sChildName.charAt(0) == '#');
if (bSearchByClass)
sChildName.delete(0, 1); // remove the '#'
Node test = searchForChild((Node) obj, sChildName.toString(), bSearchByClass);
if (test == null) {
somResult.remove(i);
i--;
nOldResultsSize--;
}
else {
childNode = test;
somResult.set(i, new SomResultInfo(childNode.getXFAParent()));
}
}
}
// This interaction involves logically either deleting an element of somResult
// (if it doesn't match anything), or replacing it with any number of
// other nodes/objects.
// Profiling has revealed that the original implementation ended up doing
// a substantial number of copies of SomResultInfo objects in very large
// documents (e.g., SAP performance benchmark). Now we build a separate
// array and copy it, rather than incurring the much higher cost of
// appends and removals on a large array.
List oNewResult = new ArrayList();
for (i = 0; i < nOldResultsSize; i++) {
// Since we are about to look at children or properties, make sure we've got
// a plain object, not a property.
if (!StringUtils.isEmpty(somResult.get(i).propertyName)) {
MsgFormatPos oErr = new MsgFormatPos(ResId.SOMRefPropertyException);
oErr.format(s.toString());
oErr.format(somExpression);
throw new ExFull(oErr);
}
int nNewSize = oNewResult.size();
if (!locateNodes(somResult.get(i).object, s.toString(), brt, bracket_index, beforeFirstDot,
oNewResult, somExpression, isAssociation)) {
// JavaPort: In C++ we did a oNewResult.SetSize(nNewSize)
while (oNewResult.size() > nNewSize)
oNewResult.remove(oNewResult.size() - 1);
}
}
// now, replace the original contents
somResult.clear();
somResult.addAll(oNewResult);
if (!StringUtils.isEmpty(sScriptContents)) {
// Predicates (eg. Foo.[a > 0]) used. Filter the results.
String sScriptLanguage = p_script_lang;
for (i = 0; i < somResult.size(); i++) {
Boolean bAccept = Boolean.FALSE;
SomResultInfo sri = somResult.get(i);
if (StringUtils.isEmpty(sri.propertyName) && sri.object instanceof Element) {
Element oNode = (Element)sri.object;
try {
// Watson 2269463: ensure we lock the entire app model while resolving predicate expressions, in case the
// script therein actually modifies the current state.
oNode.getAppModel().setPermsLock(true);
Arg oScriptResult = oNode.evaluate(sScriptContents,
sScriptLanguage, ScriptHandler.PREDICATE, true);
if (oScriptResult.getArgType() == Arg.EXCEPTION) {
ExFull oErr = new ExFull(new MsgFormat(ResId.InvalidSOMException, somExpression));
oErr.insert(oScriptResult.getException(), true);
throw oErr;
}
bAccept = oScriptResult.getAsBool(false);
}
finally {
oNode.getAppModel().setPermsLock(false);
}
}
// If not accepted, rip it out of results
if (!bAccept.booleanValue()) {
somResult.remove(i);
i--;
}
}
}
if (m_RefStack.size() > 0)
m_RefStack.remove(m_RefStack.size() - 1);
return somResult.size() > 0;
}
/**
* Do a recursive depth-first search to find the first child node
* with a given class or Element name.
*
* @param startNode The starting node to search from.
* @param childName The class or Element name to search for.
* If "*", the first leaf-level child is returned.
* This String must be interned.
* @param bSearchByClass if true, search by class name; if false, search by name.
* @return The Node with the specified class or element name.
*/
private Node searchForChild(Node startNode, String childName, boolean bSearchByClass) {
boolean bAllChildren = childName.equals("*");
Node child = startNode.getFirstXFAChild();
while (child != null) {
if (childName == null && !bSearchByClass)
return null;
if (childName.equals(bSearchByClass ? child.getClassName() : child.getName())) {
return child;
}
Node childNode = searchForChild(child, childName, bSearchByClass);
if (childNode != null)
return childNode;
if (bAllChildren) {
return child;
}
child = child.getNextXFASibling();
}
return null;
}
/**
* Set the options for the parser
*
* @param bPeek -
* if true do not create any default children. Defaults to false
* @param bLastInstance -
* if true "*" is used return only the last instance. Defaults to
* false
* @param bNoProperties -
* if true no properties (such as x.name) are returned. Note that
* if name were a child node, it would be returned. Defaults to
* false
* @exclude from published api.
*/
public void setOptions(boolean bPeek /* =false */,
boolean bLastInstance /* =false */,
boolean bNoProperties /* =false */) {
// if true, don't create default children.
mbPeek = bPeek;
// get only the last instance if * is used
mbLastInstance = bLastInstance;
// Don't return properties such as "x.name"
// (this would still return a node if there were a node called "name")
mbNoProperties = bNoProperties;
}
/**
* Sets the DependencyTracker for the parser
* @param dependencyTracker the dependency tracker for the next call to resolve
* @exclude from published api.
*/
public void setDependencyTracker(DependencyTracker dependencyTracker) {
mDependencyTracker = dependencyTracker;
}
}