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 authored and angelozerr committed Jan 8, 2023
1 parent 2d5ebad commit 1663001
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@
/**
* XML Command "xml.associate.grammar.insert" to associate a grammar to a given
* DOM document.
*
*
* The command parameters {@link ExecuteCommandParams} must be filled with 3
* parameters:
*
*
* <ul>
* <li>document URI (String) : the DOM document file URI to bind with a grammar.
* </li>
Expand All @@ -52,7 +52,7 @@
* <li>binding type (String) : which can takes values "standard", "xml-model" to
* know which binding type must be inserted in the DOM document.</li>
* </ul>
*
*
* @author Angelo ZERR
*
*/
Expand Down Expand Up @@ -112,14 +112,14 @@ protected Object executeCommand(DOMDocument document, ExecuteCommandParams param
// Insert inside <foo /> ->
// xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance"
// xsi:noNamespaceSchemaLocation=\"xsd/tag.xsd\"
return NoGrammarConstraintsCodeAction.createXSINoNamespaceSchemaLocationEdit(grammarURI, document);
return NoGrammarConstraintsCodeAction.createXSINoNamespaceSchemaLocationEdit(grammarURI, document, sharedSettings);
}
// Insert inside <foo /> ->
// xmlns="team_namespace"
// xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// xsi:schemaLocation="team_namespace xsd/team.xsd"
return NoGrammarConstraintsCodeAction.createXSISchemaLocationEdit(grammarURI, targetNamespace,
document);
document, sharedSettings);
} else {
// DTD file
// Insert before <foo /> -> <!DOCTYPE foo SYSTEM "dtd/tag.dtd">
Expand Down
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 @@ -22,14 +22,14 @@

/**
* Code action resolver participant used to:
*
*
* <ul>
* <li>generate the XSD file for the given DOM document</li>
* <li>generate the association xsi:noNamespaceSchemaLocation in the XML to bind
* it with the generated XSD</li>
*
*
* </ul>
*
*
* @author Angelo ZERR
*
*/
Expand All @@ -42,7 +42,7 @@ public class GenerateXSINoNamespaceSchemaCodeActionResolver
@Override
protected TextDocumentEdit createFileEdit(String grammarFileName, DOMDocument document,
SharedSettings sharedSettings) throws BadLocationException {
return createXSINoNamespaceSchemaLocationEdit(grammarFileName, document);
return createXSINoNamespaceSchemaLocationEdit(grammarFileName, document, sharedSettings);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4j.jsonrpc.messages.Either;

Expand All @@ -58,6 +57,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 if possible
private static final String PLACEHOLDER_ELEMENT_NAME = "root-element";
private final Map<String, ICodeActionResolvesParticipant> resolveCodeActionParticipants;

public NoGrammarConstraintsCodeAction() {
Expand Down Expand Up @@ -176,7 +177,7 @@ private static CodeAction createNoNamespaceSchemaLocationCodeAction(String schem
GenerateXSINoNamespaceSchemaCodeActionResolver.PARTICIPANT_ID);
} else {
TextDocumentEdit noNamespaceSchemaLocationEdit = createXSINoNamespaceSchemaLocationEdit(schemaFileName,
document);
document, request.getSharedSettings());
return createGrammarFileAndBindIt(title, schemaURI, schemaTemplate, noNamespaceSchemaLocationEdit,
diagnostic);
}
Expand Down Expand Up @@ -275,14 +276,21 @@ private static CodeAction createGrammarFileAndBindIt(String title, String gramma
return codeAction;
}

public static TextDocumentEdit createXSINoNamespaceSchemaLocationEdit(String schemaFileName, DOMDocument document)
public static TextDocumentEdit createXSINoNamespaceSchemaLocationEdit(String schemaFileName, DOMDocument document,
SharedSettings sharedSettings)
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();
XMLBuilder insertText = new XMLBuilder(sharedSettings, null, delimiter);

if (documentElement == null) {
generateStartTag(insertText, document);
}

insertText.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
insertText.append(delimiter);
Expand All @@ -291,18 +299,29 @@ public static TextDocumentEdit createXSINoNamespaceSchemaLocationEdit(String sch
insertText.append(schemaFileName);
insertText.append("\"");

if (documentElement == null) {
generateEndTag(insertText);
}

Position position = document.positionAt(afterTagOffset);
return CodeActionFactory.insertEdit(insertText.toString(), position, document.getTextDocument());
}

public static TextDocumentEdit createXSISchemaLocationEdit(String schemaFileName, String targetNamespace,
DOMDocument document) throws BadLocationException {
DOMDocument document, SharedSettings sharedSettings) 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;

XMLBuilder insertText = new XMLBuilder(sharedSettings, null, delimiter);

if (documentElement == null) {
generateStartTag(insertText, document);
}

StringBuilder insertText = new StringBuilder();
insertText.append(" xmlns=\"");
insertText.append(targetNamespace);
insertText.append("\"");
Expand All @@ -317,6 +336,10 @@ public static TextDocumentEdit createXSISchemaLocationEdit(String schemaFileName
insertText.append(schemaFileName);
insertText.append("\"");

if (documentElement == null) {
generateEndTag(insertText);
}

Position position = document.positionAt(afterTagOffset);
return CodeActionFactory.insertEdit(insertText.toString(), position, document.getTextDocument());
}
Expand All @@ -325,7 +348,8 @@ 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 @@ -334,47 +358,85 @@ 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)) {
if (documentElement != null && StringUtils.isEmpty(targetNamespace)) {
return CodeActionFactory.insertEdit(xmlModelInsertText, xmlModelPosition, document.getTextDocument());
}

StringBuilder xmlNamespaceInsertText = new StringBuilder();
xmlNamespaceInsertText.append(" xmlns=\"");
xmlNamespaceInsertText.append(targetNamespace);
xmlNamespaceInsertText.append("\" ");
// Generate root element (if needed) and insert namespace in root element (if
// needed)
XMLBuilder xmlNamespaceInsertText = new XMLBuilder(sharedSettings, null, delimiter);

if (documentElement == null) {
generateStartTag(xmlNamespaceInsertText, document);
}

if (!StringUtils.isEmpty(targetNamespace)) {
xmlNamespaceInsertText.append(" xmlns=\"");
xmlNamespaceInsertText.append(targetNamespace);
xmlNamespaceInsertText.append("\"");
}

if (documentElement == null) {
generateEndTag(xmlNamespaceInsertText);
}

int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length();
int afterTagOffset = beforeTagOffset
+ (documentElement != null ? 1 + documentElement.getTagName().length() : 0);
Position xmlNamespacePosition = document.positionAt(afterTagOffset);

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);
return CodeActionFactory.insertEdits(document.getTextDocument(), //
Arrays.asList(CodeActionFactory.insertEdit(xmlModelInsertText, xmlModelPosition), //
CodeActionFactory.insertEdit(xmlNamespaceInsertText.toString(), xmlNamespacePosition)));
}

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();

XMLBuilder docType = new XMLBuilder(sharedSettings, null, delimiter);
docType.startDoctype();
docType.addParameter(documentElement.getLocalName());
docType.addContent(" SYSTEM \"");
docType.addContent(dtdFileName);
docType.addContent("\"");
docType.endDoctype();
docType.linefeed();

String insertText = docType.toString();
int beforeTagOffset = documentElement != null ? documentElement.getStartTagOpenOffset()
: document.getLastChild() != null ? document.getLastChild().getEnd() : 0;

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

if (documentElement == null) {
builder.addContent("<");
builder.addContent(PLACEHOLDER_ELEMENT_NAME);
generateEndTag(builder);
}

String insertText = (documentElement == null && document.getLastChild() != null ? delimiter : "")
+ builder.toString();
Position position = document.positionAt(beforeTagOffset);
return CodeActionFactory.insertEdit(insertText, position, document.getTextDocument());
}

private static void generateStartTag(XMLBuilder builder, DOMDocument document) {
if (document.getLastChild() != null) {
builder.linefeed();
}
builder.addContent("<");
builder.addContent(PLACEHOLDER_ELEMENT_NAME);
}

private static void generateEndTag(XMLBuilder builder) {
builder.addContent("></");
builder.addContent(PLACEHOLDER_ELEMENT_NAME);
builder.addContent(">");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void associateWithXMLModelAXSDWithTargetNamespace() throws InterruptedExc

assertEquals(actual, tde(xmlPath, 1, //
te(1, 0, 1, 0, "<?xml-model href=\"xsd/team.xsd\"?>\r\n"), //
te(1, 4, 1, 4, " xmlns=\"team_namespace\" ")));
te(1, 4, 1, 4, " xmlns=\"team_namespace\"")));
}

@Test
Expand Down
Loading

0 comments on commit 1663001

Please sign in to comment.