| package com.intellij.xml.arrangement; |
| |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.XmlElementVisitor; |
| import com.intellij.psi.codeStyle.arrangement.DefaultArrangementEntry; |
| import com.intellij.psi.codeStyle.arrangement.std.ArrangementSettingsToken; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.containers.Stack; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collection; |
| |
| import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.EntryType.XML_ATTRIBUTE; |
| import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.EntryType.XML_TAG; |
| |
| /** |
| * @author Eugene.Kudelevsky |
| */ |
| public class XmlArrangementVisitor extends XmlElementVisitor { |
| |
| private final Stack<XmlElementArrangementEntry> myStack = new Stack<XmlElementArrangementEntry>(); |
| |
| private final XmlArrangementParseInfo myInfo; |
| private final Collection<TextRange> myRanges; |
| |
| public XmlArrangementVisitor(@NotNull XmlArrangementParseInfo info, @NotNull Collection<TextRange> ranges) { |
| myInfo = info; |
| myRanges = ranges; |
| } |
| |
| @Override |
| public void visitXmlFile(XmlFile file) { |
| final XmlTag tag = file.getRootTag(); |
| |
| if (tag != null) { |
| tag.accept(this); |
| } |
| } |
| |
| @Override |
| public void visitXmlTag(XmlTag tag) { |
| final XmlElementArrangementEntry entry = createNewEntry( |
| tag.getTextRange(), XML_TAG, null, null, true); |
| processEntry(entry, tag); |
| } |
| |
| @Override |
| public void visitXmlAttribute(XmlAttribute attribute) { |
| final XmlElementArrangementEntry entry = createNewEntry( |
| attribute.getTextRange(), XML_ATTRIBUTE, attribute.getName(), attribute.getNamespace(), true); |
| processEntry(entry, null); |
| } |
| |
| private void processEntry(@Nullable XmlElementArrangementEntry entry, @Nullable PsiElement nextElement) { |
| if (entry == null || nextElement == null) { |
| return; |
| } |
| myStack.push(entry); |
| try { |
| nextElement.acceptChildren(this); |
| } |
| finally { |
| myStack.pop(); |
| } |
| } |
| |
| @Nullable |
| private XmlElementArrangementEntry createNewEntry(@NotNull TextRange range, |
| @NotNull ArrangementSettingsToken type, |
| @Nullable String name, |
| @Nullable String namespace, |
| boolean canBeMatched) { |
| if (range.getStartOffset() == 0 && range.getEndOffset() == 0 || !isWithinBounds(range)) { |
| return null; |
| } |
| final DefaultArrangementEntry current = getCurrent(); |
| final XmlElementArrangementEntry entry = new XmlElementArrangementEntry( |
| current, range, type, name, namespace, canBeMatched); |
| |
| if (current == null) { |
| myInfo.addEntry(entry); |
| } |
| else { |
| current.addChild(entry); |
| } |
| return entry; |
| } |
| |
| @Nullable |
| private DefaultArrangementEntry getCurrent() { |
| return myStack.isEmpty() ? null : myStack.peek(); |
| } |
| |
| private boolean isWithinBounds(@NotNull TextRange range) { |
| for (TextRange textRange : myRanges) { |
| if (textRange.intersects(range)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| } |