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

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

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

package com.adobe.xfa;

import java.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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy