diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DTDDeclNode.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DTDDeclNode.java index 23eb395cf..21b121ad5 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DTDDeclNode.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DTDDeclNode.java @@ -13,6 +13,7 @@ package org.eclipse.lsp4xml.dom; import java.util.ArrayList; +import java.util.List; /** @@ -33,7 +34,7 @@ public class DTDDeclNode extends DOMNode{ public DTDDeclParameter unrecognized; // holds all content after parsing goes wrong in a DTD declaration (ENTITY, ATTLIST, ...). public DTDDeclParameter declType; // represents the actual name of the decl eg: ENTITY, ATTLIST, ... - ArrayList parameters; + private List parameters; public DTDDeclNode(int start, int end, DOMDocumentType parentDocumentType) { super(start, end); @@ -84,7 +85,7 @@ public void updateLastParameterEnd(int end) { } } - public ArrayList getParameters() { + public List getParameters() { if(parameters == null) { parameters = new ArrayList(); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java index 39a4a1ac3..7631207d8 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java @@ -126,7 +126,7 @@ TokenType internalScan() { case PrologOrPI: if (stream.advanceIfChars(_QMA, _RAN)) { // ?> - state = ScannerState.WithinContent; + state = getWithinContentState(); return finishToken(offset, TokenType.PIEnd); } if (stream.advanceUntilAnyOfChars(_NWL, _CAR, _WSP, _QMA, _RAN) || stream.eos()) { // \n or \r or ' ' or '?' @@ -156,12 +156,12 @@ TokenType internalScan() { } if (stream.advanceIfChars(_QMA, _RAN)) { - state = ScannerState.WithinContent; + state = getWithinContentState(); return finishToken(offset, TokenType.PIEnd); } if (stream.advanceUntilCharsOrNewTag(_QMA, _RAN)) { // ?> if (stream.peekChar() == _LAN) { - state = ScannerState.WithinContent; + state = getWithinContentState(); } if (getTokenTextFromOffset(offset).length() == 0) { return finishToken(offset, TokenType.PIEnd); @@ -268,7 +268,7 @@ TokenType internalScan() { return finishToken(offset, TokenType.Whitespace); } if (stream.advanceIfChars(_QMA, _RAN)) { // ?> - state = ScannerState.WithinContent; + state = getWithinContentState(); return finishToken(offset, TokenType.PrologEnd); } @@ -467,6 +467,10 @@ TokenType internalScan() { } if(isDTDFile) { if(startsWithLessThanBracket) { + if (stream.advanceIfChar(_QMA)) { // ? + state = ScannerState.PrologOrPI; + return finishToken(offset, TokenType.StartPrologOrPI); + } if(stream.advanceUntilCharOrNewTag(_RAN)){ // > if(stream.peekChar() == _RAN) { stream.advance(1); //consume '>' @@ -875,6 +879,13 @@ private String localize(String string, String string2) { return string; } + private ScannerState getWithinContentState() { + if (isDTDFile) { + return ScannerState.DTDWithinContent; + } + return ScannerState.WithinContent; + } + public int getLastNonWhitespaceOffset() { return stream.getLastNonWhitespaceOffset(); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLFormatter.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLFormatter.java index 624f41f22..83719d304 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLFormatter.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLFormatter.java @@ -181,26 +181,12 @@ private void format(DOMNode node, int level, int end, XMLBuilder xml) { xml.linefeed(); } } else if (node.isProcessingInstruction()) { - DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; - xml.startPrologOrPI(processingInstruction.getTarget()); - if(processingInstruction.hasAttributes()) { - addAttributes(processingInstruction, xml); - } - else { - xml.addContentPI(processingInstruction.getData()); - } - - xml.endPrologOrPI(); + addPIToXMLBuilder(node, xml); if (level == 0) { xml.linefeed(); } } else if (node.isProlog()) { - DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; - xml.startPrologOrPI(processingInstruction.getTarget()); - if (node.hasAttributes()) { - addAttributes(node, xml); - } - xml.endPrologOrPI(); + addPrologToXMLBuilder(node, xml); xml.linefeed(); } else if (node.isText()) { DOMText textNode = (DOMText) node; @@ -214,7 +200,7 @@ private void format(DOMNode node, int level, int end, XMLBuilder xml) { DOMDocumentType documentType = (DOMDocumentType) node; if(!isDTD) { xml.startDoctype(); - ArrayList params = documentType.getParameters(); + List params = documentType.getParameters(); for (DTDDeclParameter param : params) { if(!documentType.isInternalSubset(param)) { xml.addParameter(param.getParameter()); @@ -253,82 +239,79 @@ private static boolean formatDTD(DOMDocumentType doctype, int level, int end, XM xml.indent(level); - if(node.isText()) { + if (node.isText()) { xml.addContent(((DOMText)node).getData().trim()); - } - else { - if(node.isComment()) { - DOMComment comment = (DOMComment) node; - xml.startComment(comment); - xml.addContentComment(comment.getData()); - xml.endComment(); - } - else { - boolean setEndBracketOnNewLine = false; - DTDDeclNode decl = (DTDDeclNode) node; - xml.addDeclTagStart(decl); + } else if (node.isComment()) { + DOMComment comment = (DOMComment) node; + xml.startComment(comment); + xml.addContentComment(comment.getData()); + xml.endComment(); + } else if (node.isProcessingInstruction()) { + addPIToXMLBuilder(node, xml); + } else if (node.isProlog()) { + addPrologToXMLBuilder(node, xml); + } else { + boolean setEndBracketOnNewLine = false; + DTDDeclNode decl = (DTDDeclNode) node; + xml.addDeclTagStart(decl); - if(decl.isDTDAttListDecl()) { - DTDAttlistDecl attlist = (DTDAttlistDecl) decl; - ArrayList internalDecls = attlist.getInternalChildren(); + if (decl.isDTDAttListDecl()) { + DTDAttlistDecl attlist = (DTDAttlistDecl) decl; + List internalDecls = attlist.getInternalChildren(); - if(internalDecls == null) { - for (DTDDeclParameter param : decl.getParameters()) { - xml.addParameter(param.getParameter()); + if (internalDecls == null) { + for (DTDDeclParameter param : decl.getParameters()) { + xml.addParameter(param.getParameter()); + } + } else { + boolean multipleInternalAttlistDecls = false; + List params = attlist.getParameters(); + DTDDeclParameter param; + for(int i = 0; i < params.size(); i++) { + param = params.get(i); + if(attlist.elementName.equals(param)) { + xml.addParameter(param.getParameter()); + if(attlist.getParameters().size() > 1) { //has parameters after elementName + xml.linefeed(); + xml.indent(level + 1); + setEndBracketOnNewLine = true; + multipleInternalAttlistDecls = true; + } + } else { + if(multipleInternalAttlistDecls && i == 1) { + xml.addUnindentedParameter(param.getParameter()); + } else { + xml.addParameter(param.getParameter()); + } } } - else { - boolean multipleInternalAttlistDecls = false; - ArrayList params = attlist.getParameters(); - DTDDeclParameter param; + for (DTDAttlistDecl attlistDecl : internalDecls) { + xml.linefeed(); + xml.indent(level + 1); + params = attlistDecl.getParameters(); for(int i = 0; i < params.size(); i++) { param = params.get(i); - if(attlist.elementName.equals(param)) { - xml.addParameter(param.getParameter()); - if(attlist.getParameters().size() > 1) { //has parameters after elementName - xml.linefeed(); - xml.indent(level + 1); - setEndBracketOnNewLine = true; - multipleInternalAttlistDecls = true; - } + if(i == 0) { + xml.addUnindentedParameter(param.getParameter()); } else { - if(multipleInternalAttlistDecls && i == 1) { - xml.addUnindentedParameter(param.getParameter()); - } else { - xml.addParameter(param.getParameter()); - } - + xml.addParameter(param.getParameter()); } } - - for (DTDAttlistDecl attlistDecl : internalDecls) { - xml.linefeed(); - xml.indent(level + 1); - params = attlistDecl.getParameters(); - for(int i = 0; i < params.size(); i++) { - param = params.get(i); - if(i == 0) { - xml.addUnindentedParameter(param.getParameter()); - } else { - xml.addParameter(param.getParameter()); - } - } - } - } - } else { - for (DTDDeclParameter param : decl.getParameters()) { - xml.addParameter(param.getParameter()); } } - if(setEndBracketOnNewLine) { - xml.linefeed(); - xml.indent(level); - } - if(decl.isClosed()) { - xml.closeStartElement(); + } else { + for (DTDDeclParameter param : decl.getParameters()) { + xml.addParameter(param.getParameter()); } } + if(setEndBracketOnNewLine) { + xml.linefeed(); + xml.indent(level); + } + if(decl.isClosed()) { + xml.closeStartElement(); + } } previous = node; } @@ -344,6 +327,29 @@ private static boolean isPreviousSiblingNodeType(DOMNode node, short nodeType) { return previousNode != null && previousNode.getNodeType() == nodeType; } + private static void addPIToXMLBuilder(DOMNode node, XMLBuilder xml) { + DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; + xml.startPrologOrPI(processingInstruction.getTarget()); + + String content = processingInstruction.getData(); + if (content.length() > 0) { + xml.addContentPI(content); + } else { + xml.addContent(" "); + } + + xml.endPrologOrPI(); + } + + private static void addPrologToXMLBuilder(DOMNode node, XMLBuilder xml) { + DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; + xml.startPrologOrPI(processingInstruction.getTarget()); + if (node.hasAttributes()) { + addAttributes(node, xml); + } + xml.endPrologOrPI(); + } + /** * Will add all attributes, to the given builder, on a single line */ diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/XMLPositionUtility.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/XMLPositionUtility.java index 1a2ae8bdb..4383a915c 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/XMLPositionUtility.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/utils/XMLPositionUtility.java @@ -432,7 +432,7 @@ public static Range getLastValidDTDDeclParameter(int offset, DOMDocument documen DOMNode node = document.findNodeAt(offset); if (node instanceof DTDDeclNode) { DTDDeclNode decl = (DTDDeclNode) node; - ArrayList params = decl.getParameters(); + List params = decl.getParameters(); DTDDeclParameter finalParam; if(params == null || params.isEmpty()) { return createRange(decl.declType.getStart(), decl.declType.getEnd(), document); @@ -477,7 +477,7 @@ public static Range getLastValidDTDDeclParameterOrUnrecognized(int offset, DOMDo decl = internal.get(internal.size() - 1); //get last internal decl } } - ArrayList params = decl.getParameters(); + List params = decl.getParameters(); if(params == null || params.isEmpty()) { return createRange(decl.declType.getStart(), decl.declType.getEnd(), document); } @@ -501,7 +501,7 @@ public static Range getLastDTDDeclParameter(int offset, DOMDocument document) { DOMNode node = document.findNodeAt(offset); if (node instanceof DTDDeclNode) { DTDDeclNode decl = (DTDDeclNode) node; - ArrayList params = decl.getParameters(); + List params = decl.getParameters(); DTDDeclParameter lastParam; if(params != null && !params.isEmpty()) { lastParam = params.get(params.size() - 1); @@ -532,7 +532,7 @@ public static Range getElementDeclMissingContentOrCategory(int offset, DOMDocume DOMNode node = document.findNodeAt(offset); if (node instanceof DTDElementDecl) { DTDElementDecl declNode = (DTDElementDecl) node; - ArrayList params = declNode.getParameters(); + List params = declNode.getParameters(); if(params.isEmpty()) { return null; } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/parser/XMLScannerForExternalDTDTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/parser/XMLScannerForExternalDTDTest.java index e197b644e..df578cf51 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/parser/XMLScannerForExternalDTDTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/dom/parser/XMLScannerForExternalDTDTest.java @@ -263,6 +263,37 @@ public void elementDeclContent() { assertOffsetAndToken(38, TokenType.EOS); } + @Test + public void elementDeclContentWithProlog() { + String dtd = + "\n" + + ""; + + scanner = XMLScanner.createScanner(dtd, true); + + assertOffsetAndToken(0, TokenType.StartPrologOrPI); + assertOffsetAndToken(2, TokenType.PrologName, "xml"); + assertOffsetAndToken(5, TokenType.Whitespace); + assertOffsetAndToken(6, TokenType.AttributeName); + assertOffsetAndToken(13, TokenType.DelimiterAssign); + assertOffsetAndToken(14, TokenType.AttributeValue); + assertOffsetAndToken(19, TokenType.Whitespace); + assertOffsetAndToken(20, TokenType.AttributeName); + assertOffsetAndToken(28, TokenType.DelimiterAssign); + assertOffsetAndToken(29, TokenType.AttributeValue); + assertOffsetAndToken(36, TokenType.PrologEnd); + assertOffsetAndToken(38, TokenType.Content); + assertOffsetAndToken(39, TokenType.DTDStartElement); + assertOffsetAndToken(48, TokenType.Whitespace); + assertOffsetAndToken(49, TokenType.DTDElementDeclName); + assertOffsetAndToken(53, TokenType.Whitespace); + assertOffsetAndToken(54, TokenType.DTDStartElementContent); + assertOffsetAndToken(55, TokenType.DTDElementContent); + assertOffsetAndToken(75, TokenType.DTDEndElementContent); + assertOffsetAndToken(76, TokenType.DTDEndTag); + assertOffsetAndToken(77, TokenType.EOS); + } + @Test public void elementOnlyName() { String dtd = " "; @@ -366,6 +397,37 @@ public void attlistDecl() { assertOffsetAndToken(44, TokenType.EOS); } + @Test + public void attlistDeclWithProlog() { + String dtd = + "\n" + + ""; + scanner = XMLScanner.createScanner(dtd, true); + assertOffsetAndToken(0, TokenType.StartPrologOrPI); + assertOffsetAndToken(2, TokenType.PrologName, "xml"); + assertOffsetAndToken(5, TokenType.Whitespace); + assertOffsetAndToken(6, TokenType.AttributeName); + assertOffsetAndToken(13, TokenType.DelimiterAssign); + assertOffsetAndToken(14, TokenType.AttributeValue); + assertOffsetAndToken(19, TokenType.Whitespace); + assertOffsetAndToken(20, TokenType.AttributeName); + assertOffsetAndToken(28, TokenType.DelimiterAssign); + assertOffsetAndToken(29, TokenType.AttributeValue); + assertOffsetAndToken(36, TokenType.PrologEnd); + assertOffsetAndToken(38, TokenType.Content); + assertOffsetAndToken(39, TokenType.DTDStartAttlist); + assertOffsetAndToken(48, TokenType.Whitespace); + assertOffsetAndToken(49, TokenType.DTDAttlistElementName); + assertOffsetAndToken(55, TokenType.Whitespace); + assertOffsetAndToken(56, TokenType.DTDAttlistAttributeName); + assertOffsetAndToken(63, TokenType.Whitespace); + assertOffsetAndToken(64, TokenType.DTDAttlistAttributeType); + assertOffsetAndToken(69, TokenType.Whitespace); + assertOffsetAndToken(70, TokenType.DTDAttlistAttributeValue); + assertOffsetAndToken(82, TokenType.DTDEndTag); + assertOffsetAndToken(83, TokenType.EOS); + } + @Test public void attlistMultipleDecls() { String dtd = diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java index 044388a13..08675fd28 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java @@ -181,10 +181,59 @@ public void testAutoCompletionPrologWithPartialXML() throws BadLocationException "xml version=\"1.0\" encoding=\"UTF-8\"?>")); } + @Test + public void testAutoCompletionPrologDTDFileWithXML() throws BadLocationException { + // With 'xml' label + String dtdFileURI = "test://test/test.dtd"; + testCompletionFor("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 5), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", dtdFileURI, true, true, c("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 6), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", dtdFileURI, true, true, c("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 7), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + } + + @Test + public void testAutoCompletionPrologDTDFileWithoutXML() throws BadLocationException { + //No 'xml' label + String dtdFileURI = "test://test/test.dtd"; + testCompletionFor("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 2), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", "xml version=\"1.0\" encoding=\"UTF-8\"?>", r(0, 2, 0, 2), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", dtdFileURI, true, true, c("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 3), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", dtdFileURI, true, true, c("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 4), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + } + + @Test + public void testAutoCompletionPrologDTFFileWithPartialXML() throws BadLocationException { + String dtdFileURI = "test://test/test.dtd"; + testCompletionFor("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 3), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 4), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 3), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", dtdFileURI, true, true, c("", "xml version=\"1.0\" encoding=\"UTF-8\"?>$0", r(0, 2, 0, 6), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + testCompletionFor("", dtdFileURI, true, false, c("", "xml version=\"1.0\" encoding=\"UTF-8\"?>", r(0, 2, 0, 6), + "xml version=\"1.0\" encoding=\"UTF-8\"?>")); + } + private void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException { XMLAssert.testCompletionFor(xml, null, expectedItems); } + private void testCompletionFor(String xml, String fileURI, boolean autoCloseTags, boolean isSnippetsSupported, CompletionItem... expectedItems) throws BadLocationException { + testCompletionFor(xml, fileURI, formattingSettings, createCompletionSettings(autoCloseTags, isSnippetsSupported), expectedItems); + } + + private void testCompletionFor(String xml, String fileURI, XMLFormattingOptions formattingSettings, CompletionSettings completionSettings, CompletionItem... expectedItems) throws BadLocationException { + XMLAssert.testCompletionFor(new XMLLanguageService(), xml, null, null, fileURI, null, completionSettings, formattingSettings, expectedItems); + } + private void testCompletionFor(String xml, boolean autoCloseTags, boolean isSnippetsSupported, CompletionItem... expectedItems) throws BadLocationException { testCompletionFor(xml, formattingSettings, createCompletionSettings(autoCloseTags, isSnippetsSupported), expectedItems); diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLFormatterTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLFormatterTest.java index 61ae70601..59ac1e2b0 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLFormatterTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLFormatterTest.java @@ -162,6 +162,15 @@ public void testPI() throws BadLocationException { format(content, expected); } + @Test + public void testPINoContent() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + format(content, expected); + } + @Ignore @Test public void testDefinedPIWithVariables() throws BadLocationException { @@ -1500,7 +1509,7 @@ public void testXMLInDTDFile() throws BadLocationException { " \r\n" + ""; String expected = - "\r\n" + + "\r\n" + "\r\n" + "\r\n" + "\r\n" +