blob: e52d363717a0eb47cd31360f58199cb8626de578 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed 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.
*/
/*
* @author max
*/
package com.intellij.psi.impl.source.parsing.xml;
import com.intellij.lang.*;
import com.intellij.lang.impl.PsiBuilderImpl;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.xml.XmlElementType;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.util.containers.Stack;
import com.intellij.util.diff.FlyweightCapableTreeStructure;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class XmlBuilderDriver {
private final Stack<String> myNamespacesStack = new Stack<String>();
private final Stack<String> myPrefixesStack = new Stack<String>();
private final CharSequence myText;
@NonNls private static final String XMLNS = "xmlns";
@NonNls private static final String XMLNS_COLON = "xmlns:";
public XmlBuilderDriver(final CharSequence text) {
myText = text;
}
protected CharSequence getText() {
return myText;
}
public void addImplicitBinding(@NonNls @NotNull String prefix, @NonNls @NotNull String namespace) {
myNamespacesStack.push(namespace);
myPrefixesStack.push(prefix);
}
public void build(XmlBuilder builder) {
PsiBuilder b = createBuilderAndParse();
FlyweightCapableTreeStructure<LighterASTNode> structure = b.getLightTree();
LighterASTNode root = structure.getRoot();
root = structure.prepareForGetChildren(root);
final Ref<LighterASTNode[]> childrenRef = Ref.create(null);
final int count = structure.getChildren(root, childrenRef);
LighterASTNode[] children = childrenRef.get();
for (int i = 0; i < count; i++) {
LighterASTNode child = children[i];
final IElementType tt = child.getTokenType();
if (tt == XmlElementType.XML_TAG || tt == XmlElementType.HTML_TAG) {
processTagNode(b, structure, child, builder);
}
else if (tt == XmlElementType.XML_PROLOG) {
processPrologNode(b, builder, structure, child);
}
}
structure.disposeChildren(children, count);
}
private void processPrologNode(PsiBuilder psiBuilder,
XmlBuilder builder,
FlyweightCapableTreeStructure<LighterASTNode> structure,
LighterASTNode prolog) {
final Ref<LighterASTNode[]> prologChildren = new Ref<LighterASTNode[]>(null);
final int prologChildrenCount = structure.getChildren(structure.prepareForGetChildren(prolog), prologChildren);
for (int i = 0; i < prologChildrenCount; i++) {
LighterASTNode node = prologChildren.get()[i];
IElementType type = node.getTokenType();
if (type == XmlElementType.XML_DOCTYPE) {
processDoctypeNode(builder, structure, node);
break;
}
if (type == TokenType.ERROR_ELEMENT) {
processErrorNode(psiBuilder, node, builder);
}
}
}
private void processDoctypeNode(final XmlBuilder builder, final FlyweightCapableTreeStructure<LighterASTNode> structure,
final LighterASTNode doctype) {
final Ref<LighterASTNode[]> tokens = new Ref<LighterASTNode[]>(null);
final int tokenCount = structure.getChildren(structure.prepareForGetChildren(doctype), tokens);
if (tokenCount > 0) {
CharSequence publicId = null;
boolean afterPublic = false;
CharSequence systemId = null;
boolean afterSystem = false;
for (int i = 0; i < tokenCount; i++) {
LighterASTNode token = tokens.get()[i];
if (token.getTokenType() == XmlTokenType.XML_DOCTYPE_PUBLIC) {
afterPublic = true;
}
else if (token.getTokenType() == XmlTokenType.XML_DOCTYPE_SYSTEM) {
afterSystem = true;
}
else if (token.getTokenType() != TokenType.WHITE_SPACE && token.getTokenType() != XmlElementType.XML_COMMENT) {
if (token.getTokenType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
if (afterPublic) publicId = getTokenText(token);
else if (afterSystem) systemId = getTokenText(token);
}
afterPublic = afterSystem = false;
}
}
builder.doctype(publicId, systemId, doctype.getStartOffset(), doctype.getEndOffset());
}
}
private CharSequence getTokenText(final LighterASTNode token) {
return myText.subSequence(token.getStartOffset(), token.getEndOffset());
}
protected PsiBuilder createBuilderAndParse() {
final ParserDefinition xmlParserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(XMLLanguage.INSTANCE);
assert xmlParserDefinition != null;
PsiBuilder b = PsiBuilderFactory.getInstance().createBuilder(xmlParserDefinition, xmlParserDefinition.createLexer(null), myText);
new XmlParsing(b).parseDocument();
return b;
}
private void processErrorNode(PsiBuilder psiBuilder, LighterASTNode node, XmlBuilder builder) {
assert node.getTokenType() == TokenType.ERROR_ELEMENT;
String message = PsiBuilderImpl.getErrorMessage(node);
assert message != null;
builder.error(message, node.getStartOffset(), node.getEndOffset());
}
private void processTagNode(PsiBuilder psiBuilder,
FlyweightCapableTreeStructure<LighterASTNode> structure,
LighterASTNode node,
XmlBuilder builder) {
final IElementType nodeTT = node.getTokenType();
assert nodeTT == XmlElementType.XML_TAG || nodeTT == XmlElementType.HTML_TAG;
node = structure.prepareForGetChildren(node);
final Ref<LighterASTNode[]> childrenRef = Ref.create(null);
final int count = structure.getChildren(node, childrenRef);
LighterASTNode[] children = childrenRef.get();
int stackFrameSize = myNamespacesStack.size();
CharSequence tagName = "";
int headerEndOffset = node.getEndOffset();
for (int i = 0; i < count; i++) {
LighterASTNode child = children[i];
final IElementType tt = child.getTokenType();
if (tt == XmlElementType.XML_ATTRIBUTE) checkForXmlns(child, structure);
if (tt == XmlTokenType.XML_TAG_END || tt == XmlTokenType.XML_EMPTY_ELEMENT_END) {
headerEndOffset = child.getEndOffset();
break;
}
if (tt == XmlTokenType.XML_NAME || tt == XmlTokenType.XML_TAG_NAME) {
tagName = getTokenText(child);
}
}
CharSequence localName = XmlUtil.getLocalName(tagName);
String namespace = getNamespace(tagName);
XmlBuilder.ProcessingOrder order = builder.startTag(localName, namespace, node.getStartOffset(), node.getEndOffset(), headerEndOffset);
boolean processAttrs = order == XmlBuilder.ProcessingOrder.TAGS_AND_ATTRIBUTES ||
order == XmlBuilder.ProcessingOrder.TAGS_AND_ATTRIBUTES_AND_TEXTS;
boolean processTexts = order == XmlBuilder.ProcessingOrder.TAGS_AND_TEXTS ||
order == XmlBuilder.ProcessingOrder.TAGS_AND_ATTRIBUTES_AND_TEXTS;
for (int i = 0; i < count; i++) {
LighterASTNode child = children[i];
IElementType tt = child.getTokenType();
if (tt == TokenType.ERROR_ELEMENT) processErrorNode(psiBuilder, child, builder);
if (tt == XmlElementType.XML_TAG || tt == XmlElementType.HTML_TAG) processTagNode(psiBuilder, structure, child, builder);
if (processAttrs && tt == XmlElementType.XML_ATTRIBUTE) processAttributeNode(child, structure, builder);
if (processTexts && tt == XmlElementType.XML_TEXT) processTextNode(structure, child, builder);
if (tt == XmlElementType.XML_ENTITY_REF) builder.entityRef(getTokenText(child), child.getStartOffset(), child.getEndOffset());
}
builder.endTag(localName, namespace, node.getStartOffset(), node.getEndOffset());
int framesToDrop = myNamespacesStack.size() - stackFrameSize;
for (int i = 0; i < framesToDrop; i++) {
myNamespacesStack.pop();
myPrefixesStack.pop();
}
structure.disposeChildren(children, count);
}
private void processTextNode(FlyweightCapableTreeStructure<LighterASTNode> structure, LighterASTNode node, XmlBuilder builder) {
node = structure.prepareForGetChildren(node);
final Ref<LighterASTNode[]> childrenRef = Ref.create(null);
final int count = structure.getChildren(node, childrenRef);
LighterASTNode[] children = childrenRef.get();
for (int i = 0; i < count; i++) {
LighterASTNode child = children[i];
IElementType tt = child.getTokenType();
final int start = child.getStartOffset();
final int end = child.getEndOffset();
final CharSequence physical = getTokenText(child);
if (XmlTokenType.COMMENTS.contains(tt)) continue;
if (tt == XmlTokenType.XML_CDATA_START || tt == XmlTokenType.XML_CDATA_END) {
builder.textElement("", physical, start, end);
}
else if (tt == XmlElementType.XML_CDATA) {
processTextNode(structure, child, builder);
}
else if (tt == XmlTokenType.XML_CHAR_ENTITY_REF) {
builder.textElement(new String(new char[] {XmlUtil.getCharFromEntityRef(physical.toString())}), physical, start, end);
}
else {
builder.textElement(physical, physical, start, end);
}
}
structure.disposeChildren(children, count);
}
private void processAttributeNode(final LighterASTNode attrNode, FlyweightCapableTreeStructure<LighterASTNode> structure, XmlBuilder builder) {
builder.attribute(getAttributeName(attrNode, structure), getAttributeValue(attrNode, structure), attrNode.getStartOffset(), attrNode.getEndOffset());
}
private String getNamespace(final CharSequence tagName) {
final String namespacePrefix;
int pos = StringUtil.indexOf(tagName, ':');
if (pos == -1) {
namespacePrefix = "";
}
else {
namespacePrefix = tagName.subSequence(0, pos).toString();
}
for (int i = myPrefixesStack.size() - 1; i >= 0; i--) {
if (namespacePrefix.equals(myPrefixesStack.get(i))) return myNamespacesStack.get(i);
}
return "";
}
private void checkForXmlns(LighterASTNode attrNode, FlyweightCapableTreeStructure<LighterASTNode> structure) {
final CharSequence name = getAttributeName(attrNode, structure);
if (Comparing.equal(name, XMLNS)) {
myPrefixesStack.push("");
myNamespacesStack.push(getAttributeValue(attrNode, structure).toString());
}
else if (StringUtil.startsWith(name, XMLNS_COLON)) {
myPrefixesStack.push(name.subSequence(XMLNS_COLON.length(), name.length()).toString());
myNamespacesStack.push(getAttributeValue(attrNode, structure).toString());
}
}
private CharSequence getAttributeName(LighterASTNode attrNode, FlyweightCapableTreeStructure<LighterASTNode> structure) {
return findTextByTokenType(attrNode, structure, XmlTokenType.XML_NAME);
}
private CharSequence getAttributeValue(LighterASTNode attrNode, FlyweightCapableTreeStructure<LighterASTNode> structure) {
final CharSequence fullValue = findTextByTokenType(attrNode, structure, XmlElementType.XML_ATTRIBUTE_VALUE);
int start = 0;
if (fullValue.length() > 0 && fullValue.charAt(0) == '\"') start++;
int end = fullValue.length();
if (fullValue.length() > start && fullValue.charAt(fullValue.length() - 1) == '\"') end--;
return fullValue.subSequence(start, end);
}
private CharSequence findTextByTokenType(LighterASTNode attrNode,
FlyweightCapableTreeStructure<LighterASTNode> structure,
IElementType tt) {
attrNode = structure.prepareForGetChildren(attrNode);
final Ref<LighterASTNode[]> childrenRef = Ref.create(null);
final int count = structure.getChildren(attrNode, childrenRef);
LighterASTNode[] children = childrenRef.get();
CharSequence name = "";
for (int i = 0; i < count; i++) {
LighterASTNode child = children[i];
if (child.getTokenType() == tt) {
name = getTokenText(child);
break;
}
}
structure.disposeChildren(children, count);
return name;
}
}