Skip to content

Commit

Permalink
Bind to a schema from an empty document
Browse files Browse the repository at this point in the history
Allows you to use the code action to bind to a schema when
a document doesn't have a root element.
Also, the bind schema CodeLens is shown when the document doesn't
have a root element.
It generates an element `placeholder-element-name` when the binding
strategy requires a root element to function (eg. `schemaLocation`,
`noNamespaceSchemaLocation`).
It also uses `placeholder-element-name` when binding to a `.dtd` using a
DOCTYPE declaration.

Closes redhat-developer/vscode-xml#819

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 committed Dec 13, 2022
1 parent 74689af commit a5c7ece
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
package org.eclipse.lemminx.extensions.contentmodel.commands;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.services.IXMLDocumentProvider;
import org.eclipse.lemminx.services.extensions.commands.AbstractDOMDocumentCommandHandler;
import org.eclipse.lemminx.settings.SharedSettings;
Expand Down Expand Up @@ -42,17 +41,13 @@ protected Object executeCommand(DOMDocument document, ExecuteCommandParams param
/**
* Returns true if the given DOM document can be bound with a given grammar and
* false otherwise.
*
*
* @param document the DOM document.
*
*
* @return true if the given DOM document can be bound with a given grammar and
* false otherwise.
*/
public static boolean canBindWithGrammar(DOMDocument document) {
DOMElement documentElement = document.getDocumentElement();
if (documentElement == null) {
return false;
}
return !document.hasGrammar();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,12 @@ private static void createBindToGrammarSchemaLenses(ICodeLensRequest request, Li
return;
}
String documentURI = document.getDocumentURI();
Range range = XMLPositionUtility.selectRootStartTag(document);
Range range;
if (document.getDocumentElement() != null) {
range = XMLPositionUtility.selectRootStartTag(document);
} else {
range = XMLPositionUtility.createRange(0, 0, document);
}

lenses.add(createAssociateLens(documentURI, "Bind to grammar/schema...", range));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -57,6 +58,8 @@
public class NoGrammarConstraintsCodeAction implements ICodeActionParticipant {

private static final Logger LOGGER = Logger.getLogger(NoGrammarConstraintsCodeAction.class.getName());
// FIXME: the element name should be derived from the content model
private static final String PLACEHOLDER_ELEMENT_NAME = "placeholder-element-name";
private final Map<String, ICodeActionResolvesParticipant> resolveCodeActionParticipants;

public NoGrammarConstraintsCodeAction() {
Expand Down Expand Up @@ -246,18 +249,32 @@ public static TextDocumentEdit createXSINoNamespaceSchemaLocationEdit(String sch
throws BadLocationException {
String delimiter = document.getTextDocument().lineDelimiter(0);
DOMElement documentElement = document.getDocumentElement();
int beforeTagOffset = documentElement.getStartTagOpenOffset();
int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length();
int beforeTagOffset = documentElement != null ? documentElement.getStartTagOpenOffset() : document.getLastChild() != null ? document.getLastChild().getEnd() : 0;
int afterTagOffset = documentElement != null ? beforeTagOffset + 1 + documentElement.getTagName().length() : beforeTagOffset;

StringBuilder insertText = new StringBuilder();

if (documentElement == null) {
if (document.getLastChild() != null) {
insertText.append(delimiter);
}
insertText.append("<");
insertText.append(PLACEHOLDER_ELEMENT_NAME);
}

insertText.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
insertText.append(delimiter);

insertText.append(" xsi:noNamespaceSchemaLocation=\"");
insertText.append(schemaFileName);
insertText.append("\"");

if (documentElement == null) {
insertText.append("></");
insertText.append(PLACEHOLDER_ELEMENT_NAME);
insertText.append(">");
}

Position position = document.positionAt(afterTagOffset);
return CodeActionFactory.insertEdit(insertText.toString(), position, document.getTextDocument());
}
Expand All @@ -266,10 +283,19 @@ public static TextDocumentEdit createXSISchemaLocationEdit(String schemaFileName
DOMDocument document) throws BadLocationException {
String delimiter = document.getTextDocument().lineDelimiter(0);
DOMElement documentElement = document.getDocumentElement();
int beforeTagOffset = documentElement.getStartTagOpenOffset();
int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length();
int beforeTagOffset = documentElement != null ? documentElement.getStartTagOpenOffset() : document.getLastChild() != null ? document.getLastChild().getEnd() : 0;
int afterTagOffset = documentElement != null ? beforeTagOffset + 1 + documentElement.getTagName().length() : beforeTagOffset;

StringBuilder insertText = new StringBuilder();

if (documentElement == null) {
if (document.getLastChild() != null) {
insertText.append(delimiter);
}
insertText.append("<");
insertText.append(PLACEHOLDER_ELEMENT_NAME);
}

insertText.append(" xmlns=\"");
insertText.append(targetNamespace);
insertText.append("\"");
Expand All @@ -284,6 +310,12 @@ public static TextDocumentEdit createXSISchemaLocationEdit(String schemaFileName
insertText.append(schemaFileName);
insertText.append("\"");

if (documentElement == null) {
insertText.append("></");
insertText.append(PLACEHOLDER_ELEMENT_NAME);
insertText.append(">");
}

Position position = document.positionAt(afterTagOffset);
return CodeActionFactory.insertEdit(insertText.toString(), position, document.getTextDocument());
}
Expand All @@ -292,7 +324,7 @@ public static TextDocumentEdit createXmlModelEdit(String schemaFileName, String
DOMDocument document, SharedSettings sharedSettings) throws BadLocationException {
String delimiter = document.getTextDocument().lineDelimiter(0);
DOMElement documentElement = document.getDocumentElement();
int beforeTagOffset = documentElement.getStartTagOpenOffset();
int beforeTagOffset = documentElement != null ? documentElement.getStartTagOpenOffset() : document.getLastChild() != null ? document.getLastChild().getEnd() : 0;

// Insert Text edit for xml-model
XMLBuilder xsdWithXmlModel = new XMLBuilder(sharedSettings, null, delimiter);
Expand All @@ -301,45 +333,51 @@ public static TextDocumentEdit createXmlModelEdit(String schemaFileName, String
xsdWithXmlModel.endPrologOrPI();
xsdWithXmlModel.linefeed();

String xmlModelInsertText = xsdWithXmlModel.toString();
String xmlModelInsertText = (documentElement == null && document.getLastChild() != null ? delimiter : "") + xsdWithXmlModel.toString();
Position xmlModelPosition = document.positionAt(beforeTagOffset);

if (StringUtils.isEmpty(targetNamespace)) {
return CodeActionFactory.insertEdit(xmlModelInsertText, xmlModelPosition, document.getTextDocument());
}

StringBuilder xmlNamespaceInsertText = new StringBuilder();
xmlNamespaceInsertText.append(" xmlns=\"");
xmlNamespaceInsertText.append(targetNamespace);
xmlNamespaceInsertText.append("\" ");
List<TextEdit> edits = new ArrayList<>(2);
edits.add(CodeActionFactory.insertEdit(xmlModelInsertText, xmlModelPosition));

int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length();
Position xmlNamespacePosition = document.positionAt(afterTagOffset);
if (documentElement != null) {
// insert namespace in root element
StringBuilder xmlNamespaceInsertText = new StringBuilder();
xmlNamespaceInsertText.append(" xmlns=\"");
xmlNamespaceInsertText.append(targetNamespace);
xmlNamespaceInsertText.append("\" ");

int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length();
Position xmlNamespacePosition = document.positionAt(afterTagOffset);
edits.add(CodeActionFactory.insertEdit(xmlNamespaceInsertText.toString(), xmlNamespacePosition));
}

List<TextEdit> edits = Arrays.asList( //
// insert xml-model before root tag element
CodeActionFactory.insertEdit(xmlModelInsertText, xmlModelPosition),
// insert xml namespace inside root tag element
CodeActionFactory.insertEdit(xmlNamespaceInsertText.toString(), xmlNamespacePosition));
return CodeActionFactory.insertEdits(document.getTextDocument(), edits);
}

public static TextDocumentEdit createDocTypeEdit(String dtdFileName, DOMDocument document,
SharedSettings sharedSettings) throws BadLocationException {
String delimiter = document.getTextDocument().lineDelimiter(0);
DOMElement documentElement = document.getDocumentElement();
int beforeTagOffset = documentElement.getStartTagOpenOffset();
int beforeTagOffset = documentElement != null ? documentElement.getStartTagOpenOffset() : document.getLastChild() != null ? document.getLastChild().getEnd() : 0;

XMLBuilder docType = new XMLBuilder(sharedSettings, null, delimiter);
docType.startDoctype();
docType.addParameter(documentElement.getLocalName());
if (documentElement != null) {
docType.addParameter(documentElement.getLocalName());
} else {
docType.addParameter(PLACEHOLDER_ELEMENT_NAME);
}
docType.addContent(" SYSTEM \"");
docType.addContent(dtdFileName);
docType.addContent("\"");
docType.endDoctype();
docType.linefeed();

String insertText = docType.toString();
String insertText = (documentElement == null && document.getLastChild() != null ? delimiter : "") + docType.toString();
Position position = document.positionAt(beforeTagOffset);
return CodeActionFactory.insertEdit(insertText, position, document.getTextDocument());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,11 @@ private static void noGrammarHint(boolean selfClose) throws BadLocationException
dtdTemplate), //
teOp("test.xml", 0, 0, 0, 0, //
"<?xml-model href=\"test.dtd\"?>" + lineSeparator())),
// Open binding wizard command
ca(d, new Command("Bind to existing grammar/schema", OPEN_BINDING_WIZARD,
Arrays.asList(new Object[] { "test.xml" }))));
// Open binding wizard command
ca(d, //
new Command("Bind to existing grammar/schema", OPEN_BINDING_WIZARD,
Arrays.asList(new Object[] { "test.xml" })))
);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import static org.eclipse.lemminx.XMLAssert.cl;
import static org.eclipse.lemminx.XMLAssert.r;
import static org.eclipse.lemminx.client.ClientCommands.OPEN_BINDING_WIZARD;
import static org.eclipse.lemminx.client.ClientCommands.OPEN_URI;
import static org.eclipse.lemminx.client.ClientCommands.SHOW_REFERENCES;

Expand Down Expand Up @@ -84,13 +85,13 @@ public void codeLensOnComplexTypeAndSimpleType() throws BadLocationException {
@Test
public void codeLensEmptyDocument() throws BadLocationException {
String xml = "";
XMLAssert.testCodeLensFor(xml);
XMLAssert.testCodeLensFor(xml, cl(r(0, 0, 0, 0), "Bind to grammar/schema...", OPEN_BINDING_WIZARD));
}

@Test
public void codeLensSpace() throws BadLocationException {
String xml = " ";
XMLAssert.testCodeLensFor(xml);
XMLAssert.testCodeLensFor(xml, cl(r(0, 0, 0, 0), "Bind to grammar/schema...", OPEN_BINDING_WIZARD));
}

}
Loading

0 comments on commit a5c7ece

Please sign in to comment.