Recursively expands the variables in text and attribute values and return the new DOM.

The expansion uses {@link Util#replaceMacro(String, VariableResolver)}, so any unresolved
references will be left as-is.
181731
public XStreamDOM expandMacro(VariableResolver<String> vars) {
        String[] newAttributes = new String[attributes.length];
        for (int i=0; i<attributes.length; i+=2) {
            //noinspection PointlessArithmeticExpression
            newAttributes[i+0] = attributes[i]; // name
            newAttributes[i+1] = Util.replaceMacro(attributes[i+1],vars);
        }

        List<XStreamDOM> newChildren = null;
        if (children!=null) {
            newChildren = new ArrayList<>(children.size());
            for (XStreamDOM d : children)
                newChildren.add(d.expandMacro(vars));
        }

        return new XStreamDOM(tagName,newAttributes,newChildren,Util.replaceMacro(value,vars));
    }

    public String getAttribute(String name) {
        for (int i=0; i<attributes.length; i+=2)
            if (attributes[i].equals(name))
                return attributes[i+1];
        return null;
    }

    public int getAttributeCount() {
        return attributes.length / 2;
    }

    String getAttributeName(int index) {
        return attributes[index*2];
    }

    public String getAttribute(int index) {
        return attributes[index*2+1];
    }

    public String getValue() {
        return value;
    }

    public List<XStreamDOM> getChildren() {
        return children;
    }

    /**
     * Returns a new {@link HierarchicalStreamReader} that reads a sub-tree rooted at this node.
     */
    public HierarchicalStreamReader newReader() {
        return new ReaderImpl(this);
    }

    /**
     * Returns a new {@link HierarchicalStreamWriter} for marshalling objects into {@link XStreamDOM}.
     * After the writer receives the calls, call {@link WriterImpl#getOutput()} to obtain the populated tree.
     */
    public static WriterImpl newWriter() {
        return new WriterImpl();
    }

    /**
     * Writes this {@link XStreamDOM} into {@link OutputStream}.
     */
    public void writeTo(OutputStream os) {
        writeTo(XStream2.getDefaultDriver().createWriter(os));
    }

    public void writeTo(Writer w) {
        writeTo(XStream2.getDefaultDriver().createWriter(w));
    }

    public void writeTo(HierarchicalStreamWriter w) {
        new ConverterImpl().marshal(this,w,null);
    }

    /**
     * Marshals the given object with the given XStream into {@link XStreamDOM} and return it.
     */
    public static XStreamDOM from(XStream xs, Object obj) {
        WriterImpl w = newWriter();
        xs.marshal(obj, w);
        return w.getOutput();
    }

    public static XStreamDOM from(InputStream in) {
        return from(XStream2.getDefaultDriver().createReader(in));
    }

    public static XStreamDOM from(Reader in) {
        return from(XStream2.getDefaultDriver().createReader(in));
    }

    public static XStreamDOM from(HierarchicalStreamReader in) {
        return new ConverterImpl().unmarshalElement(in, null);
    }

    public Map<String, String> getAttributeMap() {
        Map<String,String> r = new HashMap<>();
        for (int i=0; i<attributes.length; i+=2)
            r.put(attributes[i],attributes[i+1]);
        return r;
    }

    private static class ReaderImpl extends AbstractXmlReader implements DocumentReader {
        private static class Pointer {
            final XStreamDOM node;
            int pos;

            private Pointer(XStreamDOM node) {
                this.node = node;
            }

            public String peekNextChild() {
                if (hasMoreChildren())
                    return node.children.get(pos).tagName;
                return null;
            }

            public boolean hasMoreChildren() {
                return node.children!=null && pos<node.children.size();
            }

            public String xpath() {
                XStreamDOM child = node.children.get(pos - 1);
                int count =0;
                for (int i=0; i<pos-1; i++)
                    if (node.children.get(i).tagName.equals(child.tagName))
                        count++;
                boolean more = false;
                for (int i=pos; !more && i<node.children.size(); i++)
                    if (node.children.get(i).tagName.equals(child.tagName))
                        more = true;

                if (count==0 && !more)  return child.tagName;   // sole child
                return child.tagName+'['+count+']';
            }
        }

        private final Stack<Pointer> pointers = new Stack<>();


        public ReaderImpl(XStreamDOM current) {
            super(new XmlFriendlyReplacer());
            pointers.push(new Pointer(current));
        }

        private Pointer current() {
            return pointers.peek();
        }

        public Object getCurrent() {
            return current().node;
        }

        public boolean hasMoreChildren() {
            return current().hasMoreChildren();
        }

        public HierarchicalStreamReader underlyingReader() {
            return this;
        }

        public void moveDown() {
            Pointer p = current();
            pointers.push(new Pointer(p.node.children.get(p.pos++)));
        }

        public void moveUp() {
            pointers.pop();
        }

        public Iterator getAttributeNames() {
            return new AttributeNameIterator(this);
        }

        public void appendErrors(ErrorWriter errorWriter) {
            StringBuilder buf = new StringBuilder();
            Pointer parent = null;
            for (Pointer cur : pointers) {
                if (parent!=null) {
                    buf.append('/').append(parent.xpath());
                } else {
                    buf.append(cur.node.tagName);
                }
                parent = cur;
            }
            errorWriter.add("xpath", buf.toString());
        }

        public void close() {
        }

        public String peekNextChild() {
            return current().peekNextChild();
        }

        public String getNodeName() {
            return unescapeXmlName(current().node.tagName);
        }

        public String getValue() {
            return Util.fixNull(current().node.value);
        }

        public String getAttribute(String name) {
            return current().node.getAttribute(name);
        }

        public String getAttribute(int index) {
            return current().node.getAttribute(index);
        }

        public int getAttributeCount() {
            return current().node.getAttributeCount();
        }

        public String getAttributeName(int index) {
            return unescapeXmlName(current().node.getAttributeName(index));
        }
    }

    public static class WriterImpl extends AbstractXmlWriter {
        private static class Pending {
            final String tagName;
            List<XStreamDOM> children;
            List<String> attributes = new ArrayList<String>();
            String value;

            private Pending(String tagName) {
                this.tagName = tagName;
            }

            void addChild(XStreamDOM dom) {
                if (children==null)
                    children = new ArrayList<XStreamDOM>();
                children.add(dom);
            }

            XStreamDOM toDOM() {
                return new XStreamDOM(tagName,attributes.toArray(new String[attributes.size()]),children,value);
            }
        }

        private final Stack<Pending> pendings = new Stack<Pending>();

        public WriterImpl() {
            pendings.push(new Pending(null));   // to get the final result
        }

        public void startNode(String name) {
            pendings.push(new Pending(escapeXmlName(name)));
        }



        public void endNode() {
            XStreamDOM dom = pendings.pop().toDOM();
            pendings.peek().addChild(dom);
        }

        public void addAttribute(String name, String value) {
            List<String> atts = pendings.peek().attributes;
            atts.add(escapeXmlName(name));
            atts.add(value);
        }

        public void setValue(String text) {
            pendings.peek().value = text;
        }

        public void flush() {
        }

        public void close() {
        }

        public HierarchicalStreamWriter underlyingWriter() {
            return this;
        }

        public XStreamDOM getOutput() {
            if (pendings.size()!=1)     throw new IllegalStateException();
            return pendings.peek().children.get(0);
        }
    }

    public static class ConverterImpl implements Converter {
        public boolean canConvert(Class type) {
            return type==XStreamDOM.class;
        }

        /**
         * {@link XStreamDOM} holds infoset (which is 'escaped' from XStream's PoV),
         * whereas {@link HierarchicalStreamWriter} expects unescaped names,
         * so we need to unescape it first before calling into {@link HierarchicalStreamWriter}.
         */
        // TODO: ideally we'd like to use the contextual HierarchicalStreamWriter to unescape,
        // but this object isn't exposed to us
        private String unescape(String s) {
            return REPLACER.unescapeName(s);
        }

        private String escape(String s) {
            return REPLACER.escapeName(s);
        }

        public void marshal(Object source, HierarchicalStreamWriter w, MarshallingContext context) {
            XStreamDOM dom = (XStreamDOM)source;
            w.startNode(unescape(dom.tagName));
            for (int i=0; i<dom.attributes.length; i+=2)
                w.addAttribute(unescape(dom.attributes[i]),dom.attributes[i+1]);
            if (dom.value!=null)
                w.setValue(dom.value);
            else {
                for (XStreamDOM c : Util.fixNull(dom.children)) {
                    marshal(c, w, context);
                }
            }
            w.endNode();
        }

        /**
         * Unmarshals a single child element.
         */
        public XStreamDOM unmarshal(HierarchicalStreamReader r, UnmarshallingContext context) {
            r.moveDown();
            XStreamDOM dom = unmarshalElement(r,context);
            r.moveUp();
            return dom;
        }

        public XStreamDOM unmarshalElement(HierarchicalStreamReader r, UnmarshallingContext context) {
            String name = escape(r.getNodeName());

            int c = r.getAttributeCount();
            String[] attributes = new String[c*2];
            for (int i=0; i<c; i++) {
                attributes[i*2]   = escape(r.getAttributeName(i));
                attributes[i*2+1] = r.getAttribute(i);
            }

            List<XStreamDOM> children = null;
            String value = null;
            if (r.hasMoreChildren()) {
                children = new ArrayList<XStreamDOM>();
                while (r.hasMoreChildren()) {
                    children.add(unmarshal(r, context));
                }
            } else {
                value = r.getValue();
            }

            return new XStreamDOM(name,attributes,children,value);
        }
    }

    public static XmlFriendlyReplacer REPLACER = new XmlFriendlyReplacer();
}
169157
public static VariableResolver getReadVariableResolver(JspContext jspContext) {
        assert jspContext != null;

        /* todo: ugly ugly ugly...getting the appropriate variable resolver should be easier than this */
        Object actionForm = getActionForm(((PageContext)jspContext).getRequest());

        NetUIReadVariableResolver netuiVariableResolver =
            new NetUIReadVariableResolver(jspContext.getVariableResolver());
        netuiVariableResolver.setActionForm(unwrapForm(actionForm));

        return netuiVariableResolver;
    }
707008
private function expandExplodeMod($op, $varname, array $varvalue)
    {
        $result = '';
        $first = true;
        $isList = $this->isNumericArray($varvalue);
        if (self::$behavior[$op]['named']) {
            foreach ($varvalue as $name => $value) {
                if ($first) {
                    $first = false;
                } else {
                    $result .= self::$behavior[$op]['sep'];
                }
                if ($isList) {
                    $result .= $varname;
                } else {
                    $result .= $name;
                }
                if (!$value) {
                    $result .= self::$behavior[$op]['ifemp'];
                } else {
                    $result .= '=' . rawurlencode($value);
                }
            }
        } else {
            if ($isList) {
                if (self::$behavior[$op]['allow'] === 'U') {
                    $varvalue = array_map('rawurlencode', $varvalue);
                } else {
                    $varvalue = array_map([$this, 'encodeNonReservedCharacters'], $varvalue);
                }
                $result .= implode(self::$behavior[$op]['sep'], $varvalue);
            } else {
                $newvals = [];
                foreach ($varvalue as $name => $value) {
                    if (self::$behavior[$op]['allow'] === 'U') {
                        $newvals[] .= rawurlencode($name) . '=' . rawurlencode($value);
                    } else {
                        $newvals[] .= $this->encodeNonReservedCharacters($name) . '=' . $this->encodeNonReservedCharacters($value);
                    }
                }
                $result .= implode(self::$behavior[$op]['sep'], $newvals);
            }
        }

        return $result;
    }
1238919
@Override
  public <O> O replaceTerms(Map substitutions) throws KbTypeException, CreateException {
    return wrapped().replaceTerms(substitutions);
  }
1093866
public TextImpl replaceWithText() {
        TextImpl replacement = new TextImpl(document, getData());
        parent.insertBefore(replacement, this);
        parent.removeChild(this);
        return replacement;
    }
932324
public static String replaceFunctionsInString(final String stringValue, TestContext context, boolean enableQuoting) {
        // make sure given string expression meets requirements for having a function
        if (!StringUtils.hasText(stringValue) ||
                (stringValue.indexOf(':') < 0) || (stringValue.indexOf('(') < 0) || (stringValue.indexOf(')') < 0) ) {

            // it is not a function, as it is defined as 'prefix:methodName(arguments)'
            return stringValue;
        }
        
        String newString = stringValue;
        StringBuffer strBuffer = new StringBuffer();

        boolean isVarComplete = false;
        StringBuffer variableNameBuf = new StringBuffer();

        int startIndex = 0;
        int curIndex;
        int searchIndex;

        for (FunctionLibrary library: context.getFunctionRegistry().getFunctionLibraries()) {
            startIndex = 0;

            while ((searchIndex = newString.indexOf(library.getPrefix(), startIndex)) != -1) {
                int control = -1;
                isVarComplete = false;

                curIndex = searchIndex;

                while (curIndex < newString.length() && !isVarComplete) {
                    if (newString.indexOf('(', curIndex) == curIndex) {
                        control++;
                    }

                    if (newString.charAt(curIndex) == ')' || curIndex == newString.length() - 1) {
                        if (control == 0) {
                            isVarComplete = true;
                        } else {
                            control--;
                        }
                    }

                    variableNameBuf.append(newString.charAt(curIndex));
                    curIndex++;
                }

                final String value = resolveFunction(variableNameBuf.toString(), context);

                strBuffer.append(newString.substring(startIndex, searchIndex));

                if (enableQuoting) {
                    strBuffer.append("'" + value + "'");
                } else {
                    strBuffer.append(value);
                }

                startIndex = curIndex;

                variableNameBuf = new StringBuffer();
                isVarComplete = false;
            }

            strBuffer.append(newString.substring(startIndex));
            newString = strBuffer.toString();

            strBuffer = new StringBuffer();
        }

        return newString;
    }
615860
function(nodePath, state) {
        var replacements = [];

        if ( nodePath.node.source.value.match(/^[\./]/) ) {
          nodePath.node.specifiers.forEach(function(specifier) {
            var expr = getGlobalExpression(
              state,
              removeExtensions(nodePath.node.source.value),
              specifier.imported ? specifier.imported.name : null,
              t.isImportNamespaceSpecifier(specifier)
            );
            replacements.push(t.variableDeclaration('var', [
              t.variableDeclarator(specifier.local, expr)
            ]));
          });
        }
        nodePath.replaceWithMultiple(replacements);
      }
689157
public String substitute(AbstractBuild<?,?> build, String text) {
        return Util.replaceMacro(text,createVariableResolver(build));
    }
1209553
String replace(String source) {
        if (source == null)
            return null;
        Matcher m = PATTERN.matcher(source);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            String var = m.group(1);
            String value = values.getProperty(var);
            String replacement = (value != null) ? replace(value) : "";
            m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
        }
        m.appendTail(sb);
        return sb.toString();
    }
549807
String expand(String expression, boolean horizontal) {
        int cursor = 0;
        int start = expression.indexOf(LayoutMap.VARIABLE_PREFIX_CHAR, cursor);
        if (start == -1) { // No variables
            return expression;
        }
        StringBuffer buffer = new StringBuffer();
        do {
            buffer.append(expression.substring(cursor, start));
            String variableName = nextVariableName(expression, start);
            buffer.append(expansion(variableName, horizontal));
            cursor = start + variableName.length() + 1;
            start = expression.indexOf(LayoutMap.VARIABLE_PREFIX_CHAR, cursor);
        } while (start != -1);
        buffer.append(expression.substring(cursor));
        return buffer.toString();
    }
998577
public void renameNodesWithAttributeValue(String oldName, String newName, String attributeName, String attributeValue) {
		List<Node> nodes = getNodesFromTreeByName(oldName);
		Iterator i = nodes.iterator();
		while (i.hasNext()) {
			Node n = (Node) i.next();
			n.setName(newName);
		}
	}
1075320
public static String replaceVariablesInString(final String str, TestContext context, boolean enableQuoting) {
       StringBuffer newStr = new StringBuffer();

       boolean isVarComplete;
       StringBuffer variableNameBuf = new StringBuffer();

       int startIndex = 0;
       int curIndex;
       int searchIndex;

       while ((searchIndex = str.indexOf(Citrus.VARIABLE_PREFIX, startIndex)) != -1) {
           int control = 0;
           isVarComplete = false;

           curIndex = searchIndex + Citrus.VARIABLE_PREFIX.length();

           while (curIndex < str.length() && !isVarComplete) {
               if (str.indexOf(Citrus.VARIABLE_PREFIX, curIndex) == curIndex) {
                   control++;
               }

               if ((!Character.isJavaIdentifierPart(str.charAt(curIndex)) && (str.charAt(curIndex) == Citrus.VARIABLE_SUFFIX.charAt(0))) || (curIndex + 1 == str.length())) {
                   if (control == 0) {
                       isVarComplete = true;
                   } else {
                       control--;
                   }
               }

               if (!isVarComplete) {
                   variableNameBuf.append(str.charAt(curIndex));
               }
               ++curIndex;
           }

           final String value = context.getVariable(variableNameBuf.toString());
           if (value == null) {
               throw new NoSuchVariableException("Variable: " + variableNameBuf.toString() + " could not be found");
           }

           newStr.append(str.substring(startIndex, searchIndex));

           if (enableQuoting) {
               newStr.append("'" + value + "'");
           } else {
               newStr.append(value);
           }

           startIndex = curIndex;

           variableNameBuf = new StringBuffer();
           isVarComplete = false;
       }

       newStr.append(str.substring(startIndex));

       return newStr.toString();
   }
120296
private static String replaceProperties(final String value) {
        final StringBuilder builder = new StringBuilder();
        final int len = value.length();
        int state = 0;
        int start = -1;
        int nameStart = -1;
        for (int i = 0; i < len; i = value.offsetByCodePoints(i, 1)) {
            final char ch = value.charAt(i);
            switch (state) {
                case INITIAL: {
                    switch (ch) {
                        case '$': {
                            state = GOT_DOLLAR;
                            continue;
                        }
                        default: {
                            builder.append(ch);
                            continue;
                        }
                    }
                    // not reachable
                }
                case GOT_DOLLAR: {
                    switch (ch) {
                        case '$': {
                            builder.append(ch);
                            state = INITIAL;
                            continue;
                        }
                        case '{': {
                            start = i + 1;
                            nameStart = start;
                            state = GOT_OPEN_BRACE;
                            continue;
                        }
                        default: {
                            // invalid; emit and resume
                            builder.append('$').append(ch);
                            state = INITIAL;
                            continue;
                        }
                    }
                    // not reachable
                }
                case GOT_OPEN_BRACE: {
                    switch (ch) {
                        case ':':
                        case '}':
                        case ',': {
                            final String name = value.substring(nameStart, i).trim();
                            if ("/".equals(name)) {
                                builder.append('/');
                                state = ch == '}' ? INITIAL : RESOLVED;
                                continue;
                            } else if (":".equals(name)) {
                                builder.append('/');
                                state = ch == '}' ? INITIAL : RESOLVED;
                                continue;
                            }
                            final String val = null;//System.getProperty(name);
                            if (val != null) {
                                builder.append(val);
                                state = ch == '}' ? INITIAL : RESOLVED;
                                continue;
                            } else if (ch == ',') {
                                nameStart = i + 1;
                                continue;
                            } else if (ch == ':') {
                                start = i + 1;
                                state = DEFAULT;
                                continue;
                            } else {
                                builder.append(value.substring(start - 2, i + 1));
                                state = INITIAL;
                                continue;
                            }
                        }
                        default: {
                            continue;
                        }
                    }
                    // not reachable
                }
                case RESOLVED: {
                    if (ch == '}') {
                        state = INITIAL;
                    }
                    continue;
                }
                case DEFAULT: {
                    if (ch == '}') {
                        state = INITIAL;
                        builder.append(value.substring(start, i));
                    }
                    continue;
                }
                default: throw new IllegalStateException();
            }
        }
        switch (state) {
            case GOT_DOLLAR: {
                builder.append('$');
                break;
            }
            case DEFAULT:
            case GOT_OPEN_BRACE: {
                builder.append(value.substring(start - 2));
                break;
            }
        }
        return builder.toString();
    }
317939
public void addVariable(String attribute, String varName, String value) {
	Bindings binds = rslTree.getBindings(attribute);
	if (binds == null) {
	    binds = new Bindings(attribute);
	    rslTree.put(binds);
	}
	binds.add(new Binding(varName, value));
    }
831794
public static String expand(final String templateString, Map<String, Object> values)
    throws MalformedUriTemplateException, VariableExpansionException
    {
        UriTemplate t = new UriTemplate(templateString);
        t.set(values);
        return t.expand();
    }
1080772
function(str, vars) {
			vars = vars || {};
			var resolver = typeof vars === 'function' ? vars : function(str, p1) {
				return p1 in vars ? vars[p1] : null;
			};
			
			return this.processText(str, {
				variable: function(data) {
					var newValue = resolver(data.token, data.name, data);
					if (newValue === null) {
						// try to find variable in resources
						newValue = resources.getVariable(data.name);
					}
					
					if (newValue === null || typeof newValue === 'undefined')
						// nothing found, return token itself
						newValue = data.token;
					return newValue;
				}
			});
		}
35768
public final JavaParser.variableModifier_return variableModifier() throws RecognitionException {
		JavaParser.variableModifier_return retval = new JavaParser.variableModifier_return();
		retval.start = input.LT(1);
		int variableModifier_StartIndex = input.index();

		try {
			if ( state.backtracking>0 && alreadyParsedRule(input, 52) ) { return retval; }

			// src/main/resources/org/drools/compiler/semantics/java/parser/Java.g:548:5: ( 'final' | annotation )
			int alt73=2;
			int LA73_0 = input.LA(1);
			if ( (LA73_0==83) ) {
				alt73=1;
			}
			else if ( (LA73_0==58) ) {
				alt73=2;
			}

			else {
				if (state.backtracking>0) {state.failed=true; return retval;}
				NoViableAltException nvae =
					new NoViableAltException("", 73, 0, input);
				throw nvae;
			}

			switch (alt73) {
				case 1 :
					// src/main/resources/org/drools/compiler/semantics/java/parser/Java.g:548:7: 'final'
					{
					match(input,83,FOLLOW_83_in_variableModifier1908); if (state.failed) return retval;
					}
					break;
				case 2 :
					// src/main/resources/org/drools/compiler/semantics/java/parser/Java.g:549:9: annotation
					{
					pushFollow(FOLLOW_annotation_in_variableModifier1918);
					annotation();
					state._fsp--;
					if (state.failed) return retval;
					}
					break;

			}
			retval.stop = input.LT(-1);

		}
		catch (RecognitionException re) {
			reportError(re);
			recover(input,re);
		}
		finally {
			// do for sure before leaving
			if ( state.backtracking>0 ) { memoize(input, 52, variableModifier_StartIndex); }

		}
		return retval;
	}