/* * 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(); } } } }