blob: b8c6b2affe383c45f5e22bfac037b743d3df38fd [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.
*/
package com.intellij.psi.impl.source.parsing.xml;
import com.intellij.codeInsight.daemon.XmlErrorMessages;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiBuilderFactory;
import com.intellij.lang.dtd.DTDLanguage;
import com.intellij.lexer.DtdLexer;
import com.intellij.lexer._DtdLexer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.DummyHolderFactory;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.xml.XmlElementType;
import com.intellij.psi.xml.XmlEntityDecl;
import org.jetbrains.annotations.NotNull;
/**
* @author Mike
*/
public class DtdParsing extends XmlParsing implements XmlElementType {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.parsing.xml.XmlParser");
private final IElementType myRootType;
public static final XmlEntityDecl.EntityContextType TYPE_FOR_MARKUP_DECL = XmlEntityDecl.EntityContextType.ELEMENT_CONTENT_SPEC;
private final XmlEntityDecl.EntityContextType myContextType;
public DtdParsing(IElementType root, XmlEntityDecl.EntityContextType contextType, PsiBuilder builder) {
super(builder);
myRootType = root;
myContextType = contextType;
myBuilder.enforceCommentTokens(TokenSet.EMPTY);
}
public DtdParsing(CharSequence chars,
final IElementType type,
final XmlEntityDecl.EntityContextType contextType,
PsiFile contextFile
) {
this(
type,
contextType,
PsiBuilderFactory.getInstance().createBuilder(
LanguageParserDefinitions.INSTANCE.forLanguage(DTDLanguage.INSTANCE),
new DtdLexer(false) {
final int myInitialState = getLexerInitialState(type, contextType);
@Override
public void start(@NotNull CharSequence buffer, int startOffset, int endOffset, int initialState) {
super.start(buffer, startOffset, endOffset, myInitialState);
}
}, chars
)
);
if (contextFile != null) myBuilder.putUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY, contextFile);
}
public ASTNode parse() {
final PsiBuilder.Marker root = myBuilder.mark();
if (myRootType == XML_MARKUP_DECL) {
parseTopLevelMarkupDecl();
root.done(myRootType);
return myBuilder.getTreeBuilt();
}
PsiBuilder.Marker document = null;
if (myRootType == DTD_FILE) {
document = myBuilder.mark();
parseProlog();
}
switch (myContextType) {
case GENERIC_XML:
parseGenericXml();
break;
case ELEMENT_CONTENT_SPEC:
doParseContentSpec(true);
break;
case ATTLIST_SPEC:
parseAttlistContent();
break;
case ATTR_VALUE:
parseAttrValue();
case ATTRIBUTE_SPEC:
parseAttributeContentSpec();
break;
case ENTITY_DECL_CONTENT:
parseEntityDeclContent();
break;
case ENUMERATED_TYPE:
parseEnumeratedTypeContent();
break;
}
while(!myBuilder.eof()) myBuilder.advanceLexer();
if (document != null) document.done(XML_DOCUMENT);
root.done(myRootType);
ASTNode astNode = myBuilder.getTreeBuilt();
if (myRootType != DTD_FILE) {
PsiFile file = myBuilder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY);
if (file != null) {
final DummyHolder result = DummyHolderFactory.createHolder(file.getManager(), DTDLanguage.INSTANCE, file);
final FileElement holder = result.getTreeElement();
holder.rawAddChildren((TreeElement)astNode);
}
}
return astNode;
}
private static int getLexerInitialState(IElementType rootNodeType, XmlEntityDecl.EntityContextType context) {
short state = 0;
switch (context) {
case ELEMENT_CONTENT_SPEC:
case ATTRIBUTE_SPEC:
case ATTLIST_SPEC:
case ENUMERATED_TYPE:
case ENTITY_DECL_CONTENT:
{
state = _DtdLexer.DOCTYPE_MARKUP;
break;
}
case ATTR_VALUE:
case GENERIC_XML: {
break;
}
default: LOG.error("context: " + context);
}
if (rootNodeType == XML_MARKUP_DECL && context == TYPE_FOR_MARKUP_DECL) {
state = _DtdLexer.DOCTYPE;
}
return state;
}
private void parseGenericXml() {
IElementType tokenType;
while ((tokenType = myBuilder.getTokenType()) != null) {
if (tokenType == XML_ATTLIST_DECL_START) {
parseAttlistDecl();
}
else if (tokenType == XML_ELEMENT_DECL_START) {
parseElementDecl();
}
else if (tokenType == XML_ENTITY_DECL_START) {
parseEntityDecl();
}
else if (tokenType == XML_NOTATION_DECL_START) {
parseNotationDecl();
}
else if (tokenType == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
}
else if (parseProcessingInstruction()) {
}
else if (tokenType == XML_START_TAG_START) {
parseTag(false);
} else if (isCommentToken(tokenType)) {
parseComment();
}
else if (parseConditionalSection()) {
}
else if (tokenType != null) {
addToken();
}
}
}
private void parseNotationDecl() {
if (myBuilder.getTokenType() != XML_NOTATION_DECL_START) {
return;
}
PsiBuilder.Marker decl = myBuilder.mark();
addToken();
if (!parseName()) {
decl.done(XML_NOTATION_DECL);
return;
}
parseEntityDeclContent();
if (myBuilder.getTokenType() != null) {
addToken();
}
decl.done(XML_NOTATION_DECL);
}
private void parseEntityDecl() {
if (myBuilder.getTokenType() != XML_ENTITY_DECL_START) {
return;
}
PsiBuilder.Marker decl = myBuilder.mark();
addToken();
if (myBuilder.getTokenType() == XML_PERCENT) {
addToken();
}
if (parseCompositeName()) {
decl.done(XML_ENTITY_DECL);
return;
}
parseEntityDeclContent();
if (myBuilder.getTokenType() != null) {
addToken();
}
decl.done(XML_ENTITY_DECL);
}
private boolean parseCompositeName() {
if (!parseName()) {
if (myBuilder.getTokenType() == XML_LEFT_PAREN) {
parseGroup();
} else {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.name.expected"));
return true;
}
}
return false;
}
private void parseEntityDeclContent() {
IElementType tokenType = myBuilder.getTokenType();
if (tokenType != XML_ATTRIBUTE_VALUE_START_DELIMITER &&
tokenType != XML_DOCTYPE_PUBLIC &&
tokenType != XML_DOCTYPE_SYSTEM) {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.literal.public.system.expected"));
return;
}
while (tokenType != XML_TAG_END && tokenType != null) {
if (tokenType == XML_ATTRIBUTE_VALUE_START_DELIMITER) {
parseAttributeValue();
}
else {
addToken();
}
tokenType = myBuilder.getTokenType();
}
}
private boolean parseConditionalSection() {
if (myBuilder.getTokenType() != XML_CONDITIONAL_SECTION_START) {
return false;
}
PsiBuilder.Marker conditionalSection = myBuilder.mark();
addToken();
IElementType tokenType = myBuilder.getTokenType();
if (tokenType != XML_CONDITIONAL_IGNORE &&
tokenType != XML_CONDITIONAL_INCLUDE &&
tokenType != XML_ENTITY_REF_TOKEN) {
conditionalSection.done(XML_CONDITIONAL_SECTION);
return true;
}
if (tokenType == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
} else {
addToken();
}
if (myBuilder.getTokenType() != XML_MARKUP_START) {
conditionalSection.done(XML_CONDITIONAL_SECTION);
return true;
}
parseMarkupContent();
if (myBuilder.getTokenType() != XML_CONDITIONAL_SECTION_END) {
conditionalSection.done(XML_CONDITIONAL_SECTION);
return true;
}
addToken();
conditionalSection.done(XML_CONDITIONAL_SECTION);
return true;
}
private boolean parseProcessingInstruction() {
if (myBuilder.getTokenType() != XML_PI_START) {
return false;
}
PsiBuilder.Marker tag = myBuilder.mark();
addToken();
if (myBuilder.getTokenType() != XML_PI_TARGET) {
tag.done(XML_PROCESSING_INSTRUCTION);
return true;
}
addToken();
if (myBuilder.getTokenType() != XML_PI_END) {
tag.done(XML_PROCESSING_INSTRUCTION);
return true;
}
addToken();
tag.done(XML_PROCESSING_INSTRUCTION);
return true;
}
private void parseEntityRef() {
PsiBuilder.Marker ref = myBuilder.mark();
if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
addToken();
}
ref.done(XML_ENTITY_REF);
}
private void parseProlog() {
PsiBuilder.Marker prolog = myBuilder.mark();
while (parseProcessingInstruction()) {}
if (myBuilder.getTokenType() == XML_DECL_START) {
parseDecl();
}
while (parseProcessingInstruction()) {}
if (myBuilder.getTokenType() == XML_DOCTYPE_START) {
parseDocType();
}
while (parseProcessingInstruction()) {}
prolog.done(XML_PROLOG);
}
private void parseDocType() {
if (myBuilder.getTokenType() != XML_DOCTYPE_START) {
return;
}
PsiBuilder.Marker docType = myBuilder.mark();
addToken();
if (myBuilder.getTokenType() != XML_NAME) {
docType.done(XML_DOCTYPE);
return;
}
addToken();
if (myBuilder.getTokenType() == XML_DOCTYPE_SYSTEM) {
addToken();
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_TOKEN) {
addToken();
}
}
else if (myBuilder.getTokenType() == XML_DOCTYPE_PUBLIC) {
addToken();
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_TOKEN) {
addToken();
}
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_TOKEN) {
addToken();
}
}
if (myBuilder.getTokenType() == XML_MARKUP_START) {
parseMarkupDecl();
}
if (myBuilder.getTokenType() != XML_DOCTYPE_END) {
docType.done(XML_DOCTYPE);
return;
}
addToken();
docType.done(XML_DOCTYPE);
}
private void parseMarkupDecl() {
PsiBuilder.Marker decl = myBuilder.mark();
parseMarkupContent();
decl.done(XML_MARKUP_DECL);
}
private void parseMarkupContent() {
IElementType tokenType = myBuilder.getTokenType();
if (tokenType == XML_MARKUP_START) {
addToken();
}
while (true) {
tokenType = myBuilder.getTokenType();
if (tokenType == XML_ELEMENT_DECL_START) {
parseElementDecl();
}
else if (tokenType == XML_ATTLIST_DECL_START) {
parseAttlistDecl();
}
else if (tokenType == XML_ENTITY_DECL_START) {
parseEntityDecl();
}
else if (tokenType == XML_NOTATION_DECL_START) {
parseNotationDecl();
} else if (tokenType == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
} else if (tokenType == XML_COMMENT_START) {
parseComment();
}
else if (parseConditionalSection()) {
}
else {
break;
}
}
if (tokenType == XML_MARKUP_END) {
addToken();
}
}
private void parseElementDecl() {
if (myBuilder.getTokenType() != XML_ELEMENT_DECL_START) {
return;
}
PsiBuilder.Marker decl = myBuilder.mark();
addToken();
if (parseCompositeName()) {
decl.done(XML_ELEMENT_DECL);
return;
}
doParseContentSpec(false);
skipTillEndOfBlock();
decl.done(XML_ELEMENT_DECL);
}
private void skipTillEndOfBlock() {
while (!myBuilder.eof() &&
myBuilder.getTokenType() != XML_TAG_END &&
!isAnotherDeclStart(myBuilder.getTokenType())
) {
if (myBuilder.getTokenType() == XML_COMMENT_START) parseComment();
else addToken();
}
if(myBuilder.getTokenType() == XML_TAG_END) addToken();
}
private boolean isAnotherDeclStart(IElementType type) {
return type == XML_ATTLIST_DECL_START || type == XML_ELEMENT_DECL_START;
}
private boolean parseName() {
IElementType type = myBuilder.getTokenType();
if (type == XML_NAME) {
addToken();
return true;
}
if (type == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
return true;
}
return consumeKeywordAsName(type);
}
private boolean consumeKeywordAsName(IElementType type) {
if (type == XML_DOCTYPE_PUBLIC || type == XML_DOCTYPE_SYSTEM || type == XML_CONTENT_EMPTY || type == XML_CONTENT_ANY) {
myBuilder.remapCurrentToken(XML_NAME);
addToken();
return true;
}
return false;
}
private void doParseContentSpec(boolean topLevel) {
if (!topLevel && myBuilder.rawLookup(0) != XML_WHITE_SPACE) {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.whitespace.expected"));
} else if (!topLevel) {
final IElementType tokenType = myBuilder.getTokenType();
String tokenText;
if (tokenType != XML_LEFT_PAREN &&
tokenType != XML_ENTITY_REF_TOKEN &&
tokenType != XML_CONTENT_ANY &&
tokenType != XML_CONTENT_EMPTY &&
(tokenType != XML_NAME || ( !("-".equals(tokenText = myBuilder.getTokenText())) && !"O".equals(tokenText))) // sgml compatibility
) {
PsiBuilder.Marker spec = myBuilder.mark();
spec.done(XML_ELEMENT_CONTENT_SPEC);
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.left.paren.or.entityref.or.empty.or.any.expected"));
return;
}
}
PsiBuilder.Marker spec = myBuilder.mark();
parseElementContentSpecInner(topLevel);
spec.done(XML_ELEMENT_CONTENT_SPEC);
}
private boolean parseElementContentSpecInner(boolean topLevel) {
IElementType tokenType = myBuilder.getTokenType();
boolean endedWithDelimiter = false;
while (
tokenType != null &&
tokenType != XML_TAG_END &&
tokenType != XML_START_TAG_START &&
tokenType != XML_ELEMENT_DECL_START &&
tokenType != XML_RIGHT_PAREN &&
tokenType != XML_COMMENT_START
) {
if (tokenType == XML_BAR && topLevel) {
addToken();
tokenType = myBuilder.getTokenType();
continue;
} else
if (tokenType == XML_LEFT_PAREN) {
if (!parseGroup()) return false;
endedWithDelimiter = false;
} else
if (tokenType == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
endedWithDelimiter = false;
} else if (tokenType == XML_NAME ||
tokenType == XML_CONTENT_EMPTY ||
tokenType == XML_CONTENT_ANY ||
tokenType == XML_PCDATA
) {
addToken();
endedWithDelimiter = false;
}
else if (consumeKeywordAsName(tokenType)) {
endedWithDelimiter = false;
}
else {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.name.or.entity.ref.expected"));
return false;
}
tokenType = myBuilder.getTokenType();
if (tokenType == XML_STAR ||
tokenType == XML_PLUS ||
tokenType == XML_QUESTION
) {
addToken();
tokenType = myBuilder.getTokenType();
if (tokenType == XML_PLUS) {
addToken();
tokenType = myBuilder.getTokenType();
}
}
if (tokenType == XML_BAR || tokenType == XML_COMMA) {
addToken();
tokenType = myBuilder.getTokenType();
endedWithDelimiter = true;
}
}
if (endedWithDelimiter && tokenType == XML_RIGHT_PAREN) {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.name.or.entity.ref.expected"));
}
return true;
}
private boolean parseGroup() {
PsiBuilder.Marker group = myBuilder.mark();
addToken();
boolean b = parseElementContentSpecInner(false);
if (b && myBuilder.getTokenType() == XML_RIGHT_PAREN) {
addToken();
group.done(XML_ELEMENT_CONTENT_GROUP);
return true;
} else if (b) {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.rbrace.expected"));
group.done(XML_ELEMENT_CONTENT_GROUP);
return false;
}
group.done(XML_ELEMENT_CONTENT_GROUP);
return b;
}
private void parseAttlistDecl() {
if (myBuilder.getTokenType() != XML_ATTLIST_DECL_START) {
return;
}
PsiBuilder.Marker decl = myBuilder.mark();
addToken();
if (!parseName()) {
final IElementType tokenType = myBuilder.getTokenType();
if (tokenType == XML_LEFT_PAREN) {
parseGroup();
} else {
myBuilder.error(XmlErrorMessages.message("dtd.parser.message.name.expected"));
decl.done(XML_ATTLIST_DECL);
return;
}
}
parseAttlistContent();
skipTillEndOfBlock();
decl.done(XML_ATTLIST_DECL);
}
private void parseAttlistContent() {
while (true) {
if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
}
else if (myBuilder.getTokenType() == XML_COMMENT_START) {
parseComment();
} else if (parseAttributeDecl()) {
}
else {
break;
}
}
}
private boolean parseAttributeDecl() {
if (myBuilder.getTokenType() != XML_NAME) {
return false;
}
PsiBuilder.Marker decl = myBuilder.mark();
addToken();
final boolean b = parseAttributeContentSpec();
//if (myBuilder.getTokenType() == XML_COMMENT_START) parseComment();
decl.done(XML_ATTRIBUTE_DECL);
return b;
}
private boolean parseAttributeContentSpec() {
if (parseName()) {
}
else if (myBuilder.getTokenType() == XML_LEFT_PAREN) {
parseEnumeratedType();
}
else {
return true;
}
if (myBuilder.getTokenType() == XML_ATT_IMPLIED) {
addToken();
}
else if (myBuilder.getTokenType() == XML_ATT_REQUIRED) {
addToken();
}
else if (myBuilder.getTokenType() == XML_ATT_FIXED) {
addToken();
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_START_DELIMITER) {
parseAttributeValue();
}
}
else if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_START_DELIMITER) {
parseAttributeValue();
}
return true;
}
private void parseEnumeratedType() {
PsiBuilder.Marker enumeratedType = myBuilder.mark();
addToken();
parseEnumeratedTypeContent();
if (myBuilder.getTokenType() == XML_RIGHT_PAREN) {
addToken();
}
enumeratedType.done(XML_ENUMERATED_TYPE);
}
private void parseEnumeratedTypeContent() {
while (true) {
if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
continue;
}
if (myBuilder.getTokenType() != XML_NAME && myBuilder.getTokenType() != XML_BAR) break;
addToken();
}
}
private void parseDecl() {
if (myBuilder.getTokenType() != XML_DECL_START) {
return;
}
PsiBuilder.Marker decl = myBuilder.mark();
addToken();
parseAttributeList();
if (myBuilder.getTokenType() == XML_DECL_END) {
addToken();
}
else {
myBuilder.error(XmlErrorMessages.message("expected.prologue.tag.termination.expected"));
}
decl.done(XML_DECL);
}
private void parseAttributeList() {
int lastPosition = -1;
while (true) {
if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
continue;
}
if (myBuilder.getTokenType() != XML_NAME) {
return;
}
if (lastPosition != -1) {
if (lastPosition == myBuilder.getCurrentOffset()) {
myBuilder.error(XmlErrorMessages.message("expected.whitespace"));
lastPosition = -1;
}
}
addToken();
if (myBuilder.getTokenType() != XML_EQ) {
myBuilder.error(XmlErrorMessages.message("expected.attribute.eq.sign"));
continue;
}
addToken();
if (myBuilder.getTokenType() != XML_ATTRIBUTE_VALUE_START_DELIMITER) {
return;
}
addToken();
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_TOKEN) {
addToken();
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_END_DELIMITER) {
lastPosition = myBuilder.getCurrentOffset();
addToken();
}
else {
lastPosition = -1;
}
}
else if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_END_DELIMITER) {
lastPosition = myBuilder.getCurrentOffset();
addToken();
}
else {
lastPosition = -1;
}
}
}
private int parseAttributeValue() {
if (myBuilder.getTokenType() != XML_ATTRIBUTE_VALUE_START_DELIMITER) {
return -1;
}
PsiBuilder.Marker value = myBuilder.mark();
addToken();
while (true) {
if (myBuilder.getTokenType() == XML_ATTRIBUTE_VALUE_TOKEN) {
addToken();
}
else if (myBuilder.getTokenType() == XML_CHAR_ENTITY_REF) {
addToken();
}
else if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
}
else {
break;
}
}
if (myBuilder.getTokenType() != XML_ATTRIBUTE_VALUE_END_DELIMITER) {
value.done(XML_ATTRIBUTE_VALUE);
return -1;
}
int tokenEnd = myBuilder.getCurrentOffset();
addToken();
value.done(XML_ATTRIBUTE_VALUE);
return tokenEnd;
}
private void addToken() {
myBuilder.advanceLexer();
}
private void parseTopLevelMarkupDecl() {
parseMarkupContent();
while (myBuilder.getTokenType() != null) {
if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
}
else if (myBuilder.getTokenType() == XML_ENTITY_DECL_START) {
parseEntityDecl();
}
else {
myBuilder.advanceLexer();
}
}
}
private void parseAttrValue() {
while(myBuilder.getTokenType() != null) {
if (myBuilder.getTokenType() == XML_ENTITY_REF_TOKEN) {
parseEntityRef();
} else {
addToken();
}
}
}
}