/* * * 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.flex.compiler.internal.mxml; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.ListIterator; import java.util.Map; import org.apache.flex.compiler.common.ISourceLocation; import org.apache.flex.compiler.common.MutablePrefixMap; import org.apache.flex.compiler.common.PrefixMap; import org.apache.flex.compiler.common.PrefixedXMLName; import org.apache.flex.compiler.common.SourceLocation; import org.apache.flex.compiler.common.XMLName; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.parsing.mxml.MXMLToken; import org.apache.flex.compiler.mxml.IMXMLData; import org.apache.flex.compiler.mxml.IMXMLNamespaceAttributeData; import org.apache.flex.compiler.mxml.IMXMLTagAttributeData; import org.apache.flex.compiler.mxml.IMXMLTagData; import org.apache.flex.compiler.mxml.IMXMLTextData; import org.apache.flex.compiler.mxml.IMXMLUnitData; import org.apache.flex.compiler.mxml.IMXMLTextData.TextType; import org.apache.flex.compiler.parsing.IMXMLToken; import org.apache.flex.compiler.parsing.MXMLTokenTypes; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.MXMLDuplicateAttributeProblem; import org.apache.flex.compiler.problems.MXMLUnclosedTagProblem; import org.apache.flex.compiler.problems.SyntaxProblem; import org.apache.flex.utils.FastStack; /** * Encapsulation of an open tag, a close tag, or an empty tag in MXML. */ public class MXMLTagData extends MXMLUnitData implements IMXMLTagData { private static final IMXMLTagAttributeData[] NO_ATTRIBUTES = new IMXMLTagAttributeData[0]; /** * Constructor. */ public MXMLTagData() { // Although we don't normally rely on the default ctor to make a completely valid object, // in some cases we don't end up calling init, and we end up with a partially construced object. // So let's init enough stuff so that the object is well behaved. this.attributeMap = Collections.emptyMap(); this.attributes = NO_ATTRIBUTES; } /** * Copy constructor. */ protected MXMLTagData(MXMLTagData other) { super(other); this.nameStart = other.nameStart; this.contentEnd = other.contentEnd; this.tagName = other.tagName; this.nameType = other.nameType; this.commentToken = other.commentToken; this.stateStart = other.stateStart; this.stateName = other.stateName; this.attributesStart = other.attributesStart; this.emptyTag = other.emptyTag; this.explicitCloseToken = other.explicitCloseToken; this.attributeMap = other.attributeMap; this.attributes = other.attributes; this.uri = other.uri; this.setOffsets(other.getStart(), other.getEnd()); this.setLine(other.getLine()); this.setColumn(other.getColumn()); this.setEndLine(other.getEndLine()); this.setEndColumn(other.getEndColumn()); } protected String tagName; /** * The URI specified by this tag's prefix. */ protected String uri; protected String stateName; /** * In-order list of MXML attributes */ protected IMXMLTagAttributeData[] attributes; /** * Map of attribute name to MXML attribute */ protected Map<String, IMXMLTagAttributeData> attributeMap; /* * offset where the tag name starts Note that we also use the for * offsetContains(), as it is the first "real" character in the tag */ protected int nameStart; /* * this is the offset of the end of the "stuff inside the punctuation */ protected int contentEnd; protected int nameType; /** * MXML Comment */ protected IMXMLToken commentToken; protected int stateStart; /** * Start of the attribute list (after the tag name + whitespace) */ protected int attributesStart; /** * Is this an empty tag (ending with "/>")? */ protected boolean emptyTag; /** * Indicates if we have an explicit close tag */ protected boolean explicitCloseToken; /** * The problems list in case we find a problem later */ Collection<ICompilerProblem> problems; /* * unlike most MXML units, our "content" bounds are not the same as the * {@link SourceLocation#getStart()} For tags, we don't count the outer * punctuation as "content", although we do count spaces. Example: * 0123456789 <foo /> start ==0 contentStart==1 contentEnd== 6 end == 8 */ @Override public int getContentStart() { return nameStart; } @Override public int getContentEnd() { return contentEnd; } @SuppressWarnings("fallthrough") MutablePrefixMap init(IMXMLData mxmlData, MXMLToken nameToken, ListIterator<MXMLToken> tokenIterator, MXMLDialect dialect, IFileSpecification spec, Collection<ICompilerProblem> problems) { this.problems = problems; setSourcePath(mxmlData.getPath()); MutablePrefixMap map = null; emptyTag = false; explicitCloseToken = false; // the start offset will by where '<' is. We strip that text off, but need to remember correct offset first int startOffset = nameToken.getStart(); if (nameToken.getType() == MXMLTokenTypes.TOKEN_OPEN_TAG_START) nameToken.truncate(1, 0); else if (nameToken.getType() == MXMLTokenTypes.TOKEN_CLOSE_TAG_START) nameToken.truncate(2, 0); else { problems.add(new SyntaxProblem(nameToken)); return map; } // Deal with name if it is of the form name.state int nameStart = nameToken.getStart(); MXMLStateSplitter splitState = new MXMLStateSplitter(nameToken, dialect, problems, spec); tagName = splitState.getBaseName(); if (splitState.getStateName() != null) { stateName = splitState.getStateName(); stateStart = nameToken.getStart() + splitState.getStateNameOffset(); } nameType = nameToken.getType(); int nameEnd = nameStart + tagName.length(); int contentEnd = nameEnd; setTagOffsets(startOffset, nameEnd, nameStart, contentEnd); setColumn(nameToken.getColumn()); setLine(nameToken.getLine()); setEndColumn(nameToken.getEndColumn()); setEndLine(nameToken.getEndLine()); attributesStart = getNameEnd(); ArrayList<IMXMLTagAttributeData> attrs = new ArrayList<IMXMLTagAttributeData>(); attributeMap = new LinkedHashMap<String, IMXMLTagAttributeData>(); //preserve order of attrs boolean foundTagEnd = false; boolean putTokenBack = false; // This is a pre-falcon algorithm that helped recover from tag nesting errors // I am bringing it back to life while (tokenIterator.hasNext() && !foundTagEnd) { MXMLToken token = tokenIterator.next(); MXMLTagAttributeData attribute = null; switch (token.getType()) { case MXMLTokenTypes.TOKEN_NAME: if (nameType == MXMLTokenTypes.TOKEN_CLOSE_TAG_START) { problems.add(new SyntaxProblem(token)); //burn forward until the end tag //TODO do we want to mark each token as an error, or just the first? while (tokenIterator.hasNext() && !foundTagEnd) { token = tokenIterator.next(); switch (token.getType()) { case MXMLTokenTypes.TOKEN_TAG_END: case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: foundTagEnd = true; break; } } break; } if (token.getText().startsWith("xmlns")) { attribute = new MXMLNamespaceAttributeData(token, tokenIterator, dialect, spec, problems); if (map == null) map = new MutablePrefixMap(); map.add(((IMXMLNamespaceAttributeData)attribute).getNamespacePrefix(), ((IMXMLNamespaceAttributeData)attribute).getNamespace()); } else { attribute = new MXMLTagAttributeData(token, tokenIterator, dialect, spec, problems); } attribute.setParent(this); // add the attribute to the attributes list even if it is duplicate // otherwise code-hinting will not work properly if (attributeMap.containsKey(token.getText())) { MXMLDuplicateAttributeProblem problem = new MXMLDuplicateAttributeProblem(attribute); problems.add(problem); } attrs.add(attribute); attributeMap.put(token.getText(), attribute); // Now advance the offsets to include the newly parsed attributes contentEnd = attribute.getAbsoluteEnd(); setTagOffsets(startOffset, contentEnd, nameStart, contentEnd); break; case MXMLTokenTypes.TOKEN_TAG_END: foundTagEnd = true; explicitCloseToken = !token.isImplicit(); break; case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: emptyTag = true; explicitCloseToken = !token.isImplicit(); foundTagEnd = true; break; case MXMLTokenTypes.TOKEN_OPEN_TAG_START: case MXMLTokenTypes.TOKEN_CLOSE_TAG_START: problems.add(new SyntaxProblem(token)); foundTagEnd = true; // Don't keep going - bail from malformed tag putTokenBack = true; // if we added this.fEmptyTag = true; then we could repair the damage here, // but it's better to let the balancer repair it (in case there is a matching close lurking somewhere) break; default: problems.add(new SyntaxProblem(token)); break; } if (foundTagEnd) { if (token.isImplicit() && token.getStart() == -1) { explicitCloseToken = false; //let's try to end at the start of the next token if one exists if (tokenIterator.hasNext()) { MXMLToken next = tokenIterator.next(); if (next != null) { // extend the end, but not the content end setTagOffsets(getAbsoluteStart() == -1 ? next.getStart() : getAbsoluteStart(), getAbsoluteEnd() == -1 ? next.getStart() : getAbsoluteEnd(), nameStart == -1 ? next.getStart() : nameStart, contentEnd == -1 ? next.getStart() : contentEnd); tokenIterator.previous(); } } else { // TODO: if we hit this case do we need to call setTagOffset. // and is getNameEnd() correct in any case? setOffsets(startOffset, getNameEnd()); } } else { // A Tag's content extends all the way to the end token, // so use the token to set content end contentEnd = token.getStart(); if (!putTokenBack) { // if we are terminating on a "real" close tag, then the "end" // of our tag will be the end of the TOKEN_TAG_END setTagOffsets(startOffset, token.getEnd(), nameStart, contentEnd); } else { // ... conversely, if we are terminating on some other kind of token // and are going to push the token back, we definietly don't // want to adjust our bounds based on the end of THAT token. // // So.. use the token start to set the conent end (above) and the end setTagOffsets(startOffset, contentEnd, nameStart, contentEnd); tokenIterator.previous(); } } } else if (getAbsoluteEnd() < token.getEnd()) { contentEnd = token.getEnd(); setTagOffsets(startOffset, contentEnd, nameStart, contentEnd); } } attributes = attrs.toArray(new MXMLTagAttributeData[0]); return map; } /** * For tags, we "contain" an offset if our content contains the offset. This * means that we return false for the outside "<", ">", "</", "/>" */ @Override public boolean containsOffset(int offset) { boolean ret = offset >= nameStart && offset <= contentEnd; return ret; } private void setTagOffsets(int start, int end, int contentStart, int contentEnd) { assert (start <= contentStart) && ((contentStart <= contentEnd) || contentEnd == -1) && ((contentEnd <= end) || (end == -1)); setOffsets(start, end); nameStart = contentStart; if (contentEnd != -1) this.contentEnd = contentEnd; } public void setCommentToken(IMXMLToken commentToken) { this.commentToken = commentToken; } public IMXMLToken getCommentToken() { return commentToken; } @Override public void setParentUnitDataIndex(int parentIndex) { //when we fixup tokens, we don't have enough context to determine if we are a root tag. When we're a root tag, we cannot be //an emty if (emptyTag && !explicitCloseToken) { if (parentIndex == -1) { emptyTag = false; } } super.setParentUnitDataIndex(parentIndex); } /** * Adjust all associated offsets by the adjustment amount * * @param offsetAdjustment amount to add to offsets */ @Override public void adjustOffsets(int offsetAdjustment) { super.adjustOffsets(offsetAdjustment); nameStart += offsetAdjustment; contentEnd += offsetAdjustment; if (stateName != null) { stateStart += offsetAdjustment; } attributesStart += offsetAdjustment; for (int i = 0; i < attributes.length; i++) { IMXMLTagAttributeData attribute = attributes[i]; ((MXMLTagAttributeData)attribute).adjustOffsets(offsetAdjustment); } } @Override public boolean isTag() { return true; } @Override public boolean isEmptyTag() { return emptyTag; } /** * accessor for repair. This lets the balancer mark a tag as empty. */ public void setEmptyTag() { emptyTag = true; } /** * True if this MXMLTagData object has an actual close token, and was not * closed as a post-process step of MXML repair * * @return if we have an explicit close tag */ public boolean hasExplicitCloseTag() { return explicitCloseToken; } /** * Returns true if this tag is the root tag of the containing MXML document * * @return true if we are the root tag */ public boolean isDocumentRoot() { if (isOpenTag()) { if (getParentUnitDataIndex() == -1) { int index = getIndex(); if (index == 0) return true; //if we are not zero, scan backwards to see if there is a tag before us index--; while (index >= 0) { IMXMLUnitData unit = getParent().getUnit(index); if (unit == null || unit.isTag()) return false; index--; } return true; } } return false; } /** * Is this MXML unit an open tag? (i.e. <foo> OR <foo/>, note * that the latter is also an empty tag) * * @return true if the unit is an open tag */ @Override public boolean isOpenTag() { return nameType == MXMLTokenTypes.TOKEN_OPEN_TAG_START; } @Override public boolean isOpenAndNotEmptyTag() { return (isOpenTag() && !isEmptyTag()); } @Override public boolean isCloseTag() { return nameType == MXMLTokenTypes.TOKEN_CLOSE_TAG_START; } @Override public String getName() { return tagName; } /** * Get the tag name as an {@link PrefixedXMLName} * * @return the tag name as an {@link PrefixedXMLName} */ public PrefixedXMLName getPrefixedXMLName() { return new PrefixedXMLName(getName(), getURI()); } @Override public XMLName getXMLName() { return new XMLName(getURI(), getShortName()); } @Override public PrefixMap getPrefixMap() { return getParent().getPrefixMapForData(this); } @Override public PrefixMap getCompositePrefixMap() { MutablePrefixMap compMap = new MutablePrefixMap(); IMXMLTagData lookingAt = this; while (lookingAt != null) { PrefixMap depth = getParent().getPrefixMapForData(lookingAt); if (depth != null) { compMap.addAll(depth, true); } lookingAt = lookingAt.getParentTag(); } return compMap; } @Override public String getPrefix() { String name = getName(); int i = name.indexOf(':'); return i != -1 ? name.substring(0, i) : ""; } @Override public String getShortName() { String name = getName(); int i = name.indexOf(':'); return i != -1 ? name.substring(i + 1) : name; } @Override public String getURI() { if (uri == null) { //walk up our chain to find the correct uri for our namespace. first one wins String prefix = getPrefix(); IMXMLTagData lookingAt = this; while (lookingAt != null) { PrefixMap depth = getParent().getPrefixMapForData(lookingAt); if (depth != null && depth.containsPrefix(prefix)) { uri = depth.getNamespaceForPrefix(prefix); break; } lookingAt = lookingAt.getParentTag(); } } return uri; } public void invalidateURI() { uri = null; int length = attributes.length; for (int i = 0; i < length; i++) { ((MXMLTagAttributeData)attributes[i]).invalidateURI(); } } @Override public String getStateName() { return stateName != null ? stateName : ""; } /** * Find out if this tag contains the specified attribute. * * @param attributeName name of the attribute * @return true if the attribute is present */ public boolean hasAttribute(String attributeName) { return attributeMap.containsKey(attributeName); } public boolean hasState() { return stateName != null; } public int getStateStart() { return stateStart; } public int getStateEnd() { return stateName != null ? stateName.length() + stateStart : 0; } @Override public String getRawAttributeValue(String attributeName) { IMXMLTagAttributeData attributeData = attributeMap.get(attributeName); if (attributeData != null) return attributeData.getRawValue(); return null; } @Override public IMXMLTagAttributeData getTagAttributeData(String attributeName) { return attributeMap.get(attributeName); } /** * Get the start position of the tag name * * @return the start position of the tag name */ public int getNameStart() { return nameStart; } /** * Get the end position of the tag name * * @return the end position of the tag name */ public int getNameEnd() { return nameStart + tagName.length(); } /** * Get the start position of the state name * * @return the start position of the tag name */ public int getStateNameStart() { return stateName != null ? stateStart : -1; } /** * Get the end position of the state name * * @return the end position of the tag name */ public int getStateNameEnd() { return getStateEnd(); } /** * Get the start position of the attribute list (after the first whitespace * after the tag name). * * @return the start position of the attribute list */ public int getAttributesStart() { return attributesStart; } /** * Get the end position of the attribute list (before the ">" or "/>" or the * start of the next tag). * * @return the end position of the attribute list */ public int getAttributesEnd() { return getAbsoluteEnd(); //attr end is just the end of this tag unit } @Override public boolean isOffsetInAttributeList(int offset) { return MXMLData.contains(attributesStart, getAbsoluteEnd(), offset); } public boolean isInsideStateName(int offset) { if (stateName != null) return MXMLData.contains(getStateStart(), getStateEnd(), offset); return false; } /** * Does the offset fall inside this tag's contents? * * @param offset test offset * @return true iff the offset falls inside this tag's contents */ public boolean isOffsetInsideContents(int offset) { return MXMLData.contains(getContentStart(), getContentEnd(), // was getContentsEnd (plural) offset); } /** * Get all of the attribute names in this tag * * @return all of the attribute names (as a String []) */ public String[] getAttributeNames() { String[] attributeNames = new String[attributes.length]; for (int i = 0; i < attributeNames.length; i++) { attributeNames[i] = attributes[i].getName(); } return attributeNames; } @Override public IMXMLTagAttributeData[] getAttributeDatas() { return attributes; } /** * Find the attribute that contains the offset * * @param offset test offset * @return the attribute (or null, if no attribute contains the offset) */ public IMXMLTagAttributeData findAttributeContainingOffset(int offset) { // Find the last attribute that starts to the left of offset IMXMLTagAttributeData lastAttribute = null; IMXMLTagAttributeData attribute = null; for (int i = 0; i < attributes.length; i++) { attribute = attributes[i]; if (attribute.getAbsoluteStart() < offset) lastAttribute = attribute; else break; } // That last attribute is good if it's unfinished or if it contains the offset in question if (lastAttribute != null && (lastAttribute.getAbsoluteEnd() == -1 || lastAttribute.getAbsoluteEnd() >= offset)) return lastAttribute; return null; } /** * Collect the text contents of this tag (like <mx:Script> or <mx:Style>) * * @return the text contents of this tag */ public int[] getTextContentOffsets() { int startOffset = -1; int endOffset = -1; if (!isEmptyTag() && isOpenTag()) { IMXMLUnitData[] list = getParent().getUnits(); int index = getIndex() + 1; for (int i = index; i < list.length; i++) { IMXMLUnitData next = list[i]; if (next instanceof IMXMLTextData && ((IMXMLTextData)next).getTextType() == TextType.WHITESPACE) continue; if (next.isText()) { startOffset = startOffset == -1 ? next.getAbsoluteStart() : startOffset; endOffset = next.getAbsoluteEnd(); } else { if (startOffset == -1 && endOffset == -1) { startOffset = getAbsoluteEnd(); endOffset = next.getAbsoluteStart(); } break; } } } return new int[] {startOffset, endOffset}; } @Override public String getCompilableText() { StringBuilder sb = new StringBuilder(); for (IMXMLUnitData unit = getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit()) { if (unit.isText()) sb.append(((IMXMLTextData)unit).getCompilableText()); } return sb.toString(); } @Override public IMXMLTagData findMatchingEndTag() { return findMatchingEndTag(false); } /** * Finds the close tag that matches this tag. * <p> * Returns null if this tag is a close or empty tag. * <p> * Returns null if a surrounding tag is unbalanced ONLY if includeImplicit * is false; this is determined by backing up to the innermost parent tag * with a different tag. * <p> * {@code <a> <b> <b> <-- find matching for this one will return null * </b> </a> * } */ public IMXMLTagData findMatchingEndTag(boolean includeImplicit) { if (isCloseTag() || isEmptyTag()) return null; // Back up to the first surrounding tag that has a different name, and ensure // that *it* is balanced, saving our expected return value along the way. IMXMLTagData startTag = this; while (true) { IMXMLTagData parentTag = startTag.getContainingTag(startTag.getAbsoluteStart()); if (parentTag == null) break; startTag = parentTag; if (!parentTag.getName().equals(this.getName())) { break; } } // Now walk through the tags starting at startTag. Once we pop ourselves // off the tagStack, we've found our candidate result -- but keep going // until the stack is null, to ensure that we're balanced out to the // surrounding tag. IMXMLUnitData[] list = getParent().getUnits(); FastStack<IMXMLTagData> tagStack = new FastStack<IMXMLTagData>(); IMXMLTagData result = null; for (int i = startTag.getIndex(); i < list.length; i++) { IMXMLUnitData curUnit = list[i]; if (curUnit.isTag()) { IMXMLTagData curTag = (IMXMLTagData)curUnit; if (curTag.isEmptyTag()) { // do nothing for empty tags. } else if (curTag.isOpenTag()) { tagStack.push(curTag); } else if (curTag.isCloseTag()) { if (tagStack.isEmpty()) { // document is unbalanced. return null; } IMXMLTagData pop = tagStack.pop(); //check the short name in case the namespace is not spelled properly if (!pop.getName().equals(curTag.getName()) && !pop.getShortName().equals(curTag.getShortName())) { // document is unbalanced. return null; } if (pop == this) { // This is our result -- remember it. result = curTag; } if (tagStack.isEmpty()) { if (result.isImplicit() && !includeImplicit) return null; return result; } } } } if (!tagStack.isEmpty()) { IMXMLTagData pop = tagStack.pop(); problems.add(new MXMLUnclosedTagProblem(pop, pop.getName())); } return null; } @Override public boolean isImplicit() { return false; } /** * determines if the current tag has an end tag. Will return true if this * tag is a close tag or an end tag. If the document is not balanced, we * will return false. If the end tag was implicit, we will return false * * @return true if this tag has an end tag */ public boolean hasEndTag() { if (isCloseTag() || isEmptyTag()) return explicitCloseToken; IMXMLTagData tagData = findMatchingEndTag(); return tagData != null && !tagData.isImplicit(); } @Override public IMXMLUnitData getFirstChildUnit() { // If this tag is <foo/> then it has no child units. if (!isOpenAndNotEmptyTag()) return null; IMXMLUnitData next = getNext(); // If this tag is followed immediately by its end tag, // as in <foo></foo>, then it has no child units. if (next == findMatchingEndTag()) return null; // Otherwise, the first child unit is the unit after the tag. return next; } @Override public IMXMLTagData getFirstChild(boolean includeEmptyTags) { IMXMLTagData nextTag = null; if (isEmptyTag()) return null; if (isOpenTag()) { nextTag = getNextTag(); } else { // This is a close tag. Start at the corresponding open tag. IMXMLTagData openTag = getContainingTag(getAbsoluteStart()); nextTag = openTag.getNextTag(); } // Skip any text blocks to find the next actual tag. If it's an open tag, // that is our first child. Otherwise it's a close tag, return null. while (true) { if (nextTag == null || nextTag.isCloseTag()) return null; if (nextTag.isOpenAndNotEmptyTag() || (nextTag.isEmptyTag() && includeEmptyTags)) return nextTag; nextTag = nextTag.getNextTag(); } } @Override public IMXMLTagData getNextSibling(boolean includeEmptyTags) { IMXMLTagData nextTag = null; // Be sure we're starting at the close tag, then get the next tag. if (isCloseTag() || isEmptyTag()) { nextTag = getNextTag(); } else { IMXMLTagData endTag = findMatchingEndTag(); if (endTag == null) return null; nextTag = endTag.getNextTag(); } while (true) { if (nextTag == null || nextTag.isCloseTag()) return null; if (nextTag.isOpenAndNotEmptyTag() || (nextTag.isEmptyTag() && includeEmptyTags)) return nextTag; nextTag = nextTag.getNextTag(); } } /** * Get the start tags for all children of this tag. * * @param includeEmptyTags <code>true</code> if empty tags should be * included. * @return Array of children. */ public IMXMLTagData[] getChildren(boolean includeEmptyTags) { ArrayList<IMXMLTagData> children = new ArrayList<IMXMLTagData>(); IMXMLTagData child = getFirstChild(includeEmptyTags); while (child != null) { children.add(child); child = child.getNextSibling(includeEmptyTags); } return children.toArray(new IMXMLTagData[0]); } /** * Return the parent tag of this tag. If the document is not balanced before * this tag, returns null. */ public IMXMLTagData getParentTag() { IMXMLUnitData data = getParentUnitData(); if (data instanceof IMXMLTagData) return (IMXMLTagData)data; return null; } @Override public ISourceLocation getLocationOfChildUnits() { String sourcePath = getSourcePath(); int start = getStart(); int end = getEnd(); int line = getLine(); int column = getColumn(); boolean foundFirstChild = false; for (IMXMLUnitData unit = getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit()) { if (!foundFirstChild) { sourcePath = unit.getSourcePath(); start = unit.getStart(); line = unit.getLine(); column = unit.getColumn(); foundFirstChild = true; } end = unit.getEnd(); } return new SourceLocation(sourcePath, start, end, line, column); } /** * Verifies that this tag and its attributes have their source location * information set. * <p> * This is used only in asserts. */ @Override public boolean verify() { // Verify the source location. super.verify(); // Verify the attributes. for (IMXMLTagAttributeData attribute : getAttributeDatas()) { ((MXMLTagAttributeData)attribute).verify(); } return true; } /** * For debugging only. This format is nice in the Eclipse debugger. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('<'); if (isCloseTag()) sb.append('/'); sb.append(getName()); if (isEmptyTag()) sb.append('/'); sb.append('>'); sb.append(' '); // Display line, column, start, end as "17:5 160-188" sb.append(super.toString()); // add content range as "(161-187)" sb.append(' '); sb.append('('); sb.append(nameStart); sb.append('-'); sb.append(contentEnd); sb.append(')'); return sb.toString(); } /** * For debugging only. This format is nice in a text file. */ @Override public String toDumpString() { return buildDumpString(false); } @Override public String buildDumpString(boolean skipSrcPath) { StringBuilder sb = new StringBuilder(); sb.append(super.buildDumpString(skipSrcPath)); sb.append('\t'); sb.append('|'); sb.append(getName()); sb.append('|'); return sb.toString(); } public String stringify() { StringBuilder sb = new StringBuilder(); sb.append('<'); if (isCloseTag()) sb.append('/'); sb.append(getName()); // Verify the attributes. for (IMXMLTagAttributeData attribute : getAttributeDatas()) { sb.append(" "); sb.append(attribute.getName()); sb.append("=\""); sb.append(attribute.getRawValue()); sb.append("\""); } if (isEmptyTag()) sb.append('/'); sb.append('>'); for (IMXMLUnitData unit = getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit()) { if (unit.isText()) sb.append(((IMXMLTextData)unit).getCompilableText()); else if (unit instanceof MXMLTagData) sb.append(((MXMLTagData)unit).stringify()); } if (!isEmptyTag()) { sb.append('<'); sb.append('/'); sb.append(getName()); sb.append('>'); } return sb.toString(); } }