| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.royale.compiler.internal.tree.mxml; |
| |
| import org.apache.royale.abc.ABCConstants; |
| import org.apache.royale.abc.instructionlist.InstructionList; |
| import org.apache.royale.abc.semantics.Name; |
| import org.apache.royale.abc.semantics.Namespace; |
| import org.apache.royale.abc.semantics.Nsset; |
| import org.apache.royale.compiler.common.PrefixMap; |
| import org.apache.royale.compiler.internal.as.codegen.InstructionListNode; |
| import org.apache.royale.compiler.mxml.IMXMLTagAttributeData; |
| import org.apache.royale.compiler.mxml.IMXMLTagData; |
| import org.apache.royale.compiler.mxml.IMXMLTextData; |
| import org.apache.royale.compiler.mxml.IMXMLTextData.TextType; |
| import org.apache.royale.compiler.mxml.IMXMLUnitData; |
| import org.apache.royale.compiler.tree.mxml.IMXMLBindingNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLSingleDataBindingNode; |
| |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| /** |
| * Helper class for MXMLXMLNode and MXMLXMLListNode to process their subtrees. |
| * Will turn the children sub-tree into a String, and a list of DataBinding |
| * expressions. |
| */ |
| class XMLBuilder |
| { |
| public XMLBuilder(MXMLInstanceNode parent, IMXMLTagData rootTag, PrefixMap externalPrefixes, MXMLTreeBuilder builder) |
| { |
| this.parent = parent; |
| this.rootTag = rootTag; |
| this.externalPrefixes = externalPrefixes; |
| |
| this.builder = builder; |
| } |
| |
| private MXMLInstanceNode parent; |
| private MXMLTreeBuilder builder; |
| |
| private IMXMLTagData rootTag; |
| |
| /** |
| * PrefixMap of the prefix'es defined outside of the contents of the XML |
| * tag. |
| */ |
| PrefixMap externalPrefixes; |
| |
| /** |
| * External prefixes which are referenced from inside the XML tag, each |
| * these must be added to the root tag as xmlns. |
| */ |
| Set<String> referencedPrefixes = new HashSet<String>(); |
| |
| private List<IMXMLBindingNode> databindings = new ArrayList<IMXMLBindingNode>(); |
| |
| /** |
| * Process an MXMLTagData - this will write a String representation of the |
| * tag into the StringWriter passed in. This will strip out any databinding |
| * expressions from the String, TODO: databinding - add the databinding |
| * expressions as children of the MXMLXMLNode, and also record what the |
| * TODO: target expressions for those are (these are the expressions to set |
| * the value in the XML object when the TODO: PropertyChange event fires). |
| */ |
| void processNode(IMXMLTagData tag, |
| StringWriter sw) |
| { |
| sw.write('<'); |
| if (tag.isCloseTag()) |
| sw.write('/'); |
| sw.write(tag.getName()); |
| String tagPrefix = tag.getPrefix(); |
| |
| // lookup the prefix in case it's defined elsewhere in the document outside the XML tag |
| if (tagPrefix != null) |
| lookupPrefix(tagPrefix, tag); |
| |
| List<IMXMLTagAttributeData> attrs = getAttributes(tag); |
| for (IMXMLTagAttributeData attr : attrs) |
| { |
| sw.write(' '); |
| sw.write(attr.getName()); |
| sw.write('='); |
| sw.write('"'); |
| sw.write(attr.getRawValue()); |
| sw.write('"'); |
| |
| // lookup the prefix in case it's defined outside the XML tag |
| String prefix = attr.getPrefix(); |
| if (prefix != null) |
| lookupPrefix(prefix, tag); |
| |
| } |
| |
| StringWriter childrenSW = new StringWriter(); |
| for (IMXMLUnitData unit = tag.getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit()) |
| { |
| processNode(unit, childrenSW); |
| } |
| |
| if (tag == rootTag) |
| { |
| // If we're the root tag, then add an xmlns for each prefix that was referenced by one of our |
| // children, but was defined elsewhere in the document (like in the Application tag). |
| for (String prefix : referencedPrefixes) |
| { |
| String uri = externalPrefixes.getNamespaceForPrefix(prefix); |
| if (uri != null) |
| { |
| sw.write(" xmlns"); |
| if (!prefix.isEmpty()) |
| sw.write(":"); |
| sw.write(prefix); |
| sw.write("=\""); |
| sw.write(uri); |
| sw.write('\"'); |
| } |
| } |
| } |
| if (tag.isEmptyTag()) |
| { |
| sw.write("/>"); |
| } |
| else |
| { |
| sw.write('>'); |
| } |
| sw.write(childrenSW.toString()); |
| |
| IMXMLTagData endTag = tag.findMatchingEndTag(); |
| if (endTag != null) |
| { |
| processNode(endTag, sw); |
| } |
| } |
| |
| /** |
| * Do a lookup of a prefix, starting with the specified tag. This method |
| * will record if the prefix is defined outside of the <fx:XML> tag. If it |
| * is defined outside of the tag, then the prefix will need to be added to |
| * the root XML tag as an 'xmlns' so that it will still be accessible. |
| * |
| * @param prefix the prefix to look for |
| * @param tag the tag to start looking in |
| */ |
| void lookupPrefix(String prefix, IMXMLTagData tag) |
| { |
| while (tag != null) |
| { |
| PrefixMap pm = tag.getPrefixMap(); |
| if (pm != null && pm.containsPrefix(prefix)) |
| // we found the prefix, and haven't gone past the root tag, so don't need to do anything special |
| return; |
| else if (tag == rootTag) |
| // don't look past the root tag |
| tag = null; |
| else |
| tag = tag.getParentTag(); |
| } |
| // We will only get here if we have traversed past the root XML tag, and haven't found the prefix yet |
| // in that case, record the prefix if its defined somewhere else in the MXML document, so we can |
| // make sure it ends up in the resulting XML literal. |
| if (externalPrefixes.containsPrefix(prefix)) |
| referencedPrefixes.add(prefix); |
| |
| } |
| |
| /** |
| * Process an IMXMLTextData - this will write a String representation of the |
| * tag into the StringWriter passed in. This will strip out any databinding |
| * expressions from the String. This will write out only CDATA and TEXT |
| * TextDatas Other kinds of text data are not output into the resulting XML |
| * object. TODO: databinding - add the databinding expressions as children |
| * of the MXMLXMLNode, and also record what the TODO: target expressions for |
| * those are (these are the expressions to set the value in the XML object |
| * when the TODO: PropertyChange event fires). |
| */ |
| void processNode(IMXMLTextData tag, |
| StringWriter sw) |
| { |
| switch (tag.getTextType()) |
| { |
| |
| case CDATA: |
| // For CDATA, just write out the text |
| sw.write(tag.getContent()); |
| break; |
| case TEXT: |
| { |
| IMXMLSingleDataBindingNode db = null; |
| if ((db = parseBindingExpression(tag)) != null) |
| { |
| // do databinding stuff: |
| // 1. Walk up parent chain to compute target expression |
| // 2. Parse databinding expression |
| // 3. Save off both those pieces of data for use during codegen |
| |
| databindings.add(generateBindingNode(tag, db)); |
| if (!isOnlyTextChild(tag)) |
| { |
| // Write out an empty CDATA section, so the tag will have the right |
| // number of text children for the binding to target. |
| sw.write("<![CDATA[]]>"); |
| } |
| } |
| else |
| { |
| sw.write(replaceBindingEscapes(tag.getContent())); |
| } |
| } |
| break; |
| // Everything else gets stripped out |
| } |
| } |
| |
| /** |
| * Generate an MXMLBindingNode to represent the binding expression in the |
| * IMXMLTextData passed in. |
| * |
| * @param tag the TextData that is the destination of the binding expression |
| * @param dbnode the DataBinding Node that contains the source expression |
| * @return An MXMLBindingNode with expressions for the source and |
| * destination |
| */ |
| private MXMLBindingNode generateBindingNode(IMXMLTextData tag, IMXMLSingleDataBindingNode dbnode) |
| { |
| return generateBindingNode(tag, null, dbnode); |
| } |
| |
| /** |
| * Generate an MXMLBindingNode to represent the binding expression in the |
| * MXMLTagAttributeData passed in. |
| * |
| * @param attr the Attribute that is the destination of the binding |
| * expression |
| * @param dbnode the DataBinding Node that contains the source expression |
| * @return An MXMLBindingNode with expressions for the source and |
| * destination |
| */ |
| private MXMLBindingNode generateBindingNode(IMXMLTagAttributeData attr, IMXMLSingleDataBindingNode dbnode) |
| { |
| return generateBindingNode(attr.getParent(), attr, dbnode); |
| } |
| |
| /** |
| * Generate the destination expression, and build the MXMLBindingNode |
| * |
| * @param tag The unit data that is the target of the databinding |
| * expression, if an attribute is passed in this should be the TagData |
| * containing the attribute |
| * @param attr The attribute that is the target of the databinding |
| * expression, or null if we are not targeting an attribute |
| * @param dbnode The DataBindingNode that contains the source expression |
| * @return An MXMLBindingNode with the source and destination expressions |
| */ |
| private MXMLBindingNode generateBindingNode(IMXMLUnitData tag, IMXMLTagAttributeData attr, IMXMLSingleDataBindingNode dbnode) |
| { |
| // Build the destination expression |
| InstructionListNode destExpr = getTargetExprNode(tag, attr); |
| |
| MXMLBindingNode bindingNode = new MXMLBindingNode(parent); |
| |
| MXMLBindingAttributeNode target = new MXMLBindingAttributeNode(bindingNode, destExpr); |
| destExpr.setParent(target); |
| MXMLBindingAttributeNode source = new MXMLBindingAttributeNode(bindingNode, dbnode.getExpressionNode()); |
| |
| bindingNode.setDestinationAttributeNode(target); |
| bindingNode.setSourceAttributeNode(source); |
| |
| return bindingNode; |
| } |
| |
| /** |
| * Attempt to parse a binding expression from the passed in text data |
| * |
| * @param text The Text Data to parse |
| * @return An IMXMLDataBindingNode that was parsed from text, or null if no |
| * databinding expression was found |
| */ |
| private IMXMLSingleDataBindingNode parseBindingExpression(IMXMLTextData text) |
| { |
| Object o = MXMLDataBindingParser.parse(parent, text, text.getFragments(builder.getProblems()), builder.getProblems(), |
| builder.getWorkspace(), builder.getMXMLDialect(), |
| builder.getProject()); |
| if (o instanceof IMXMLSingleDataBindingNode) |
| { |
| return ((IMXMLSingleDataBindingNode)o); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Attempt to parse a binding expression from the passed in attribute |
| * |
| * @param attr The Tag Attribute Data to parse |
| * @return An IMXMLDataBindingNode that was parsed from attr, or null if no |
| * databinding expression was found |
| */ |
| private IMXMLSingleDataBindingNode parseBindingExpression(IMXMLTagAttributeData attr) |
| { |
| Object o = MXMLDataBindingParser.parse(parent, attr, attr.getValueFragments(builder.getProblems()), builder.getProblems(), |
| builder.getWorkspace(), builder.getMXMLDialect(), |
| builder.getProject()); |
| if (o instanceof IMXMLSingleDataBindingNode) |
| { |
| return ((IMXMLSingleDataBindingNode)o); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Helper to build a InstructionListNode |
| * |
| * @param data The unit data to target |
| * @param attr The attr to target, or null if we aren't targeting an attr |
| * @return An InstructionListNode that can be used as the destination |
| * expression for an MXMLBindingNode |
| */ |
| private InstructionListNode getTargetExprNode(IMXMLUnitData data, IMXMLTagAttributeData attr) |
| { |
| InstructionListNode expr = null; |
| InstructionList il = getTargetInstructions(data, attr); |
| if (il != null) |
| { |
| expr = new InstructionListNode(il); |
| } |
| |
| return expr; |
| } |
| |
| /** |
| * Generate the instructions to set the target expression based on the Data |
| * and Attribute passed in. This will walk up the parent chain to the root |
| * tag, and then proceed to emit get instructions for all parents from the |
| * root tag down. For the actual target, instructions to set the value will |
| * be generated. If an attribute is passed in then the set instructions will |
| * target the attribute instead |
| * |
| * @param data The UnitData to target |
| * @param attr The attribute to target, or null if there is no attribute |
| * @return An InstructionList that contains the instructions to set the |
| * target expression |
| */ |
| private InstructionList getTargetInstructions(IMXMLUnitData data, IMXMLTagAttributeData attr) |
| { |
| IMXMLUnitData d = data; |
| Stack<IMXMLUnitData> parentStack = new Stack<IMXMLUnitData>(); |
| |
| if (isOnlyTextChild(d)) |
| { |
| // If we are the only text child of a TagData, then start with the TagData, |
| // as that is what we'll be setting |
| d = d.getParentUnitData(); |
| } |
| IMXMLUnitData target = d; |
| |
| // push parents onto a stack, so we can walk down from the parent later |
| while (d != null) |
| { |
| parentStack.add(d); |
| d = d == rootTag ? null : d.getParentUnitData(); |
| } |
| |
| InstructionList il = new InstructionList(); |
| // we're always going to start with "this" |
| il.addInstruction(ABCConstants.OP_getlocal0); |
| |
| // Walk down the parent stack, and emit get instructions for each tag |
| // except for the last one, which is the one we're targeting |
| while (parentStack.size() > 1) |
| { |
| IMXMLUnitData unitData = parentStack.pop(); |
| if (unitData instanceof IMXMLTagData) |
| { |
| generateGetInstructions(il, (IMXMLTagData)unitData); |
| } |
| } |
| |
| if (target instanceof IMXMLTagData) |
| { |
| // Targeting a Tag |
| if (attr == null) |
| { |
| // Just setting the tag value |
| generateSetInstructions(il, (IMXMLTagData)target); |
| } |
| else |
| { |
| // We have an attr, do a get for the tag, and a set |
| // for the attr |
| generateGetInstructions(il, (IMXMLTagData)target); |
| generateSetInstructions(il, attr); |
| } |
| } |
| else if (target instanceof IMXMLTextData) |
| { |
| // We're targeting a TextData |
| generateSetInstructions(il, (IMXMLTextData)target); |
| } |
| |
| return il; |
| } |
| |
| /** |
| * Generate the instructions to place the tag on the stack. This assumes |
| * that the base expr is already in the instruction list |
| * <a><b><c></c></b></a> so if called on the C node above, it would assume |
| * the instructions to place b on the stack were already generated - this |
| * method will only compute the instructions to get c from b |
| */ |
| private void generateGetInstructions(InstructionList il, IMXMLTagData tag) |
| { |
| if (tag == rootTag) |
| { |
| il.addInstruction(ABCConstants.OP_getproperty, getNameForTag(tag)); |
| } |
| else |
| { |
| il.addInstruction(ABCConstants.OP_getproperty, getNameForTag(tag)); |
| int index = getIndexOfTag(tag); |
| il.addInstruction(ABCConstants.OP_getproperty, new Name(String.valueOf(index))); |
| } |
| } |
| |
| /** |
| * Generate the instructions to set the value of a tag. This assumes that |
| * the base expr is already in the instruction list <a><b><c></c></b></a> so |
| * if called on the C node above, it would assume the instructions to place |
| * b on the stack were already generated - this method will only compute the |
| * instructions to set c in b Assumes that the new value is stored in local |
| * 1 (will use OP_getlocal1 to get it). This code will be in a function that |
| * has 1 argument, which is the new value, so we know it's passed in as the |
| * first local. |
| */ |
| private void generateSetInstructions(InstructionList il, IMXMLTagData tag) |
| { |
| if (tag == rootTag) |
| { |
| il.addInstruction(ABCConstants.OP_getlocal1); |
| il.addInstruction(ABCConstants.OP_setproperty, getNameForTag(tag)); |
| } |
| else |
| { |
| il.addInstruction(ABCConstants.OP_getproperty, getNameForTag(tag)); |
| il.addInstruction(ABCConstants.OP_getlocal1); |
| int index = getIndexOfTag(tag); |
| il.addInstruction(ABCConstants.OP_setproperty, new Name(String.valueOf(index))); |
| } |
| } |
| |
| /** |
| * Generate the instructions to set the value of a tag. This assumes that |
| * the base expr is already in the instruction list <a><b><c |
| * f="{blah}"></c></b></a> so if called on the f node above, it would assume |
| * the instructions to place c on the stack were already generated - this |
| * method will only compute the instructions to set f in c Assumes that the |
| * new value is stored in local 1 (will use OP_getlocal1 to get it). This |
| * code will be in a function that has 1 argument, which is the new value, |
| * so we know it's passed in as the first local. |
| */ |
| private void generateSetInstructions(InstructionList il, IMXMLTagAttributeData attr) |
| { |
| il.addInstruction(ABCConstants.OP_getlocal1); |
| il.addInstruction(ABCConstants.OP_setproperty, getNameForAttr(attr)); |
| } |
| |
| /** |
| * Generate the instructions to set the value of a text data. This assumes |
| * that the base expr is already in the instruction list |
| * <a><b><c>...<![CDATA[]]></![CDATA[]]></c></b></a> so if called on the |
| * CDATA node above, it would assume the instructions to place c on the |
| * stack were already generated - this method will only compute the |
| * instructions to set the CDATA in c This will compute the index of the |
| * cdata, and then emit code like: .text()[index] = value Assumes that the |
| * new value is stored in local 1 (will use OP_getlocal1 to get it). This |
| * code will be in a function that has 1 argument, which is the new value, |
| * so we know it's passed in as the first local. |
| */ |
| private void generateSetInstructions(InstructionList il, IMXMLTextData text) |
| { |
| il.addInstruction(ABCConstants.OP_callproperty, new Object[] {new Name("text"), 0}); |
| il.addInstruction(ABCConstants.OP_getlocal1); |
| int index = getIndexOfText(text); |
| il.addInstruction(ABCConstants.OP_setproperty, new Name(String.valueOf(index))); |
| } |
| |
| /** |
| * Get the index of a tag. Grabs the parent tag, and iterates it's children |
| * to find out what the index of the tag passed in should be |
| */ |
| private int getIndexOfTag(IMXMLTagData tag) |
| { |
| IMXMLTagData parent = tag.getParentTag(); |
| |
| int index = 0; |
| for (IMXMLTagData d = parent.getFirstChild(true); d != null; d = d.getNextSibling(true)) |
| { |
| if (d == tag) |
| break; |
| else if (d.getName().equals(tag.getName())) |
| ++index; |
| |
| } |
| return index; |
| } |
| |
| /** |
| * Get the index of a text data. Grabs the parent, and iterates it's |
| * children to find out what the index of the text data passed in should be |
| */ |
| private int getIndexOfText(IMXMLTextData text) |
| { |
| IMXMLUnitData parent = text.getParentUnitData(); |
| |
| IMXMLTagData parentTag = parent instanceof IMXMLTagData ? (IMXMLTagData)parent : null; |
| int index = 0; |
| |
| if (parentTag != null) |
| { |
| for (IMXMLUnitData d = parentTag.getFirstChildUnit(); d != null; d = d.getNextSiblingUnit()) |
| { |
| if (d == text) |
| break; |
| else if (d instanceof IMXMLTextData && ((IMXMLTextData)d).getTextType() == TextType.CDATA) |
| ++index; |
| } |
| } |
| return index; |
| } |
| |
| /** |
| * Helper to determine if a give MXMLUnitData is the only Text child of an |
| * MXMLTagData This implies special, different processing from normal Text |
| * Datas. |
| */ |
| private boolean isOnlyTextChild(IMXMLUnitData child) |
| { |
| if (child instanceof IMXMLTextData && ((IMXMLTextData)child).getTextType() == TextType.TEXT) |
| { |
| IMXMLUnitData p = child.getParentUnitData(); |
| IMXMLTagData parent = p instanceof IMXMLTagData ? (IMXMLTagData)p : null; |
| if (parent != null) |
| { |
| return parent.getFirstChildUnit() == child && child.getNextSiblingUnit() == null; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Generate an AET Name that corresponds to the tag passed in |
| */ |
| private Name getNameForTag(IMXMLTagData tag) |
| { |
| if (tag == rootTag) |
| { |
| return new Name(parent.getEffectiveID()); |
| } |
| else |
| { |
| String uri = tag.getURI(); |
| if (uri != null) |
| { |
| return new Name(new Namespace(ABCConstants.CONSTANT_Namespace, uri), tag.getShortName()); |
| } |
| else |
| { |
| return new Name(tag.getShortName()); |
| } |
| } |
| } |
| |
| /** |
| * Generate an AET Name for the attr passed in |
| */ |
| private Name getNameForAttr(IMXMLTagAttributeData attr) |
| { |
| String uri = attr.getURI(); |
| if (uri != null) |
| { |
| return new Name(ABCConstants.CONSTANT_QnameA, new Nsset(new Namespace(ABCConstants.CONSTANT_Namespace, uri)), attr.getShortName()); |
| } |
| else |
| { |
| return new Name(ABCConstants.CONSTANT_QnameA, new Nsset(new Namespace(ABCConstants.CONSTANT_Namespace, "")), attr.getShortName()); |
| } |
| } |
| |
| /** |
| * replace backslashes for curly braces and @ with d; b; |
| * |
| * @param toClean the string to clean |
| * @return the cleaned string |
| */ |
| public static String replaceBindingEscapes(String toClean) |
| { |
| toClean = cleanupEscapedCharForXML('{', toClean); |
| toClean = cleanupEscapedCharForXML('}', toClean); |
| toClean = cleanupEscapedCharForXML('@', toClean); |
| return toClean; |
| } |
| |
| /** |
| * Get rid of backslashes that were escaping the specified character |
| * @param toClean |
| * @return the cleaned string |
| */ |
| private static String cleanupEscapedCharForXML(char escapedChar, String toClean) |
| { |
| //if there's no char to begin with or no escape character we can just return the orig string |
| if (toClean == null || toClean.indexOf(escapedChar) == -1 || toClean.indexOf('\\') == -1) |
| { |
| return toClean; |
| } |
| StringBuilder buf = new StringBuilder(toClean.length()); |
| char[] chars = toClean.toCharArray(); |
| int i; |
| for (i = 0; i < chars.length - 1; ++i) |
| { |
| if (chars[i] != '\\' || chars[i+1] != escapedChar) |
| { |
| buf.append(chars[i]); |
| } else { |
| buf.append("&#x" + Integer.toString((chars[i+1]), 16) + ";"); |
| i++; |
| } |
| } |
| if (i == chars.length - 1) { |
| buf.append(chars[chars.length - 1]); |
| } |
| |
| return buf.toString(); |
| } |
| |
| /** |
| * Get a list of attributes, filtering out the attributes that have data |
| * binding values. |
| * |
| * @param tag The |
| * @return |
| */ |
| List<IMXMLTagAttributeData> getAttributes(IMXMLTagData tag) |
| { |
| IMXMLTagAttributeData[] rawAttrs = tag.getAttributeDatas(); |
| if (rawAttrs != null) |
| { |
| ArrayList<IMXMLTagAttributeData> attrs = new ArrayList<IMXMLTagAttributeData>(rawAttrs.length); |
| |
| for (IMXMLTagAttributeData attr : rawAttrs) |
| { |
| IMXMLSingleDataBindingNode db = null; |
| if ((db = parseBindingExpression(attr)) != null) |
| { |
| // do databinding stuff: |
| // 1. Walk up parent chain to compute target expression |
| // 2. Parse databinding expression |
| // 3. Save off both those pieces of data for use during codegen |
| |
| databindings.add(generateBindingNode(attr, db)); |
| } |
| else |
| { |
| attrs.add(attr); |
| } |
| } |
| return attrs; |
| } |
| return Collections.emptyList(); |
| } |
| |
| void processNode(IMXMLUnitData node, StringWriter sw) |
| { |
| if (node instanceof IMXMLTagData) |
| processNode((IMXMLTagData)node, sw); |
| else if (node instanceof IMXMLTextData) |
| processNode((IMXMLTextData)node, sw); |
| } |
| |
| public List<IMXMLBindingNode> getDatabindings() |
| { |
| return databindings; |
| } |
| } |