Skip to content

Commit

Permalink
Relax NG schema validation not working when DOCTYPE declaration missing
Browse files Browse the repository at this point in the history
with file association

Fixes redhat-developer/vscode-xml#831

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
azerr authored and datho7561 committed Dec 8, 2022
1 parent 3f43451 commit 13f2fb4
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 12 deletions.
Expand Up @@ -122,7 +122,7 @@ && isSchemaValidationEnabled(document, validationSettings)
}
parser.setFeature("http://xml.org/sax/features/validation", hasGrammar); //$NON-NLS-1$

boolean namespacesValidationEnabled = isNamespacesValidationEnabled(document, validationSettings);
boolean namespacesValidationEnabled = isNamespacesValidationEnabled(document, validationSettings, parser);
parser.setFeature("http://xml.org/sax/features/namespace-prefixes", false); //$NON-NLS-1$
parser.setFeature("http://xml.org/sax/features/namespaces", namespacesValidationEnabled); //$NON-NLS-1$

Expand All @@ -146,7 +146,14 @@ && isSchemaValidationEnabled(document, validationSettings)
}

private static boolean isNamespacesValidationEnabled(DOMDocument document,
XMLValidationSettings validationSettings) {
XMLValidationSettings validationSettings, SAXParser reader) {
try {
if (reader.getProperty(IExternalGrammarLocationProvider.RELAXNG) != null) {
return true;
}
} catch (Exception e) {
// Do nothing
}
if (validationSettings == null) {
return true;
}
Expand Down Expand Up @@ -204,15 +211,15 @@ private static boolean isSchemaValidationEnabled(DOMDocument document, XMLValida
/**
* Returns true if the given DOM document declares a xsi:schemaLocation hint for
* the document root element is valid and false otherwise.
*
*
* The xsi:schemaLocation is valid if:
*
*
* <ul>
* <li>xsi:schemaLocation defines an URI for the namespace of the document
* element.</li>
* <li>the URI can be opened</li>
* </ul>
*
*
* @param document the DOM document.
* @return true if the given DOM document declares a xsi:schemaLocation hint for
* the document root element is valid and false otherwise.
Expand Down Expand Up @@ -255,14 +262,14 @@ private static boolean isNoNamespaceSchemaValidationEnabled(DOMDocument document
/**
* Returns true if the given DOM document declares a
* xsi:noNamespaceSchemaLocation which is valid and false otherwise.
*
*
* The xsi:noNamespaceSchemaLocation is valid if:
*
*
* <ul>
* <li>xsi:noNamespaceSchemaLocation defines an URI.</li>
* <li>the URI can be opened</li>
* </ul>
*
*
* @param document the DOM document.
* @return true if the given DOM document declares a xsi:schemaLocation hint for
* the document root element is valid and false otherwise.
Expand Down Expand Up @@ -320,7 +327,7 @@ private static void parseXML(String content, String uri, SAXParser parser) throw

/**
* Returns true is DTD validation must be disabled and false otherwise.
*
*
* @param document the DOM document
* @return true is DTD validation must be disabled and false otherwise.
*/
Expand All @@ -339,7 +346,7 @@ private static boolean isDisableOnlyDTDValidation(DOMDocument document) {
// because they are not declared in the DOCTYPE.
// In this case, DTD validation must be disabled.
if (!document.hasDTD()) {
return false;
return true;
}
DOMDocumentType docType = document.getDoctype();
if (docType.getKindNode() != null) {
Expand All @@ -351,7 +358,7 @@ private static boolean isDisableOnlyDTDValidation(DOMDocument document) {

/**
* Warn if XML document is not bound to a grammar according the settings
*
*
* @param document the XML document
* @param diagnostics the diagnostics list to populate
* @param validationSettings the settings to use to know the severity of warn.
Expand Down
Expand Up @@ -58,7 +58,7 @@ public void completionForAttributeNames() throws BadLocationException {
c("id", te(2, 6, 2, 6, "id=\"\""), "id"));
}

public static void testCompletionFor(String value, CompletionItem... expectedItems) throws BadLocationException {
private static void testCompletionFor(String value, CompletionItem... expectedItems) throws BadLocationException {
XMLAssert.testCompletionFor(new XMLLanguageService(), value, null, null, "src/test/resources/relaxng/test.xml",
null, true, expectedItems);
}
Expand Down
@@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.relaxng.xml.completion;

import static org.eclipse.lemminx.XMLAssert.c;
import static org.eclipse.lemminx.XMLAssert.te;

import java.util.function.Consumer;

import org.eclipse.lemminx.AbstractCacheBasedTest;
import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLFileAssociation;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lsp4j.CompletionItem;
import org.junit.jupiter.api.Test;

/**
* XML file associations completion tests with RelaxNG compact syntax.
*/
public class XMLFileAssociationCompletionBasedOnRelaxNGCompactSyntaxTest extends AbstractCacheBasedTest {

@Test
public void completionInRoot() throws BadLocationException {
// completion on <|
String xml = "<|";
testCompletionFor(xml, //
c("addressBook", te(0, 0, 0, 1, "<addressBook></addressBook>"), "<addressBook"));
}

@Test
public void completionForElements() throws BadLocationException {
// completion on <|
String xml = "<addressBook>\r\n" + //
"<|\r\n" + //
"</addressBook>";
testCompletionFor(xml, //
c("card", te(1, 0, 1, 1, "<card></card>"), "<card"));
}

@Test
public void completionForAttributeNames() throws BadLocationException {
// completion on <|
String xml = "<addressBook>\r\n" + //
"<card |></card>\r\n" + //
"</addressBook>";
testCompletionFor(xml, //
c("id", te(1, 6, 1, 6, "id=\"\""), "id"));
}

private static void testCompletionFor(String value, CompletionItem... expectedItems) throws BadLocationException {
Consumer<XMLLanguageService> configuration = ls -> {
ContentModelManager contentModelManager = ls.getComponent(ContentModelManager.class);
contentModelManager
.setFileAssociations(createXMLFileAssociation("src/test/resources/relaxng/"));
};
XMLAssert.testCompletionFor(new XMLLanguageService(), value, null, configuration,
"file:///test/addressBook.xml",
null, true, expectedItems);
}

private static XMLFileAssociation[] createXMLFileAssociation(String baseSystemId) {
XMLFileAssociation addressBook = new XMLFileAssociation();
addressBook.setPattern("**/addressBook.xml");
addressBook.setSystemId(baseSystemId + "addressBook.rnc");
return new XMLFileAssociation[] { addressBook };
}

}
@@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.relaxng.xml.diagnostics;

import static org.eclipse.lemminx.XMLAssert.d;

import java.util.function.Consumer;

import org.eclipse.lemminx.AbstractCacheBasedTest;
import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLFileAssociation;
import org.eclipse.lemminx.extensions.relaxng.xml.validator.RelaxNGErrorCode;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

/**
* XML Validation tests with RelaxNG compact syntax by using XML file
* association.
*
*/
public class XMLFileAssociationRelaxNGCompactSyntaxDiagnosticsTest extends AbstractCacheBasedTest {

@Test
public void valid() throws Exception {
String xml = "<addressBook>\r\n" + //
" <card>\r\n" + //
" <name>John Smith</name>\r\n" + //
" <email>js@example.com</email>\r\n" + //
" </card>\r\n" + //
" <card>\r\n" + //
" <name>Fred Bloggs</name>\r\n" + //
" <email>fb@example.net</email>\r\n" + //
" </card>\r\n" + //
"</addressBook>";
testDiagnosticsFor(xml);
}

@Test
public void unkwown_element() throws Exception {
String xml = "<addressBook>\r\n" + //
" <card>\r\n" + //
" <nameXXX>John Smith</nameXXX>\r\n" + // unknown_element -> element "nameXXX" not allowed anywhere;
// expected element "name"
" <email>js@example.com</email>\r\n" + // unexpected_element_required_element_missing -> "element
// "email" not allowed yet; missing required element "name""
" </card>\r\n" + //
" <card>\r\n" + //
" <name>Fred Bloggs</name>\r\n" + //
" <email>fb@example.net</email>\r\n" + //
" </card>\r\n" + //
"</addressBook>";
testDiagnosticsFor(xml, //
d(2, 5, 12, RelaxNGErrorCode.unknown_element), //
d(3, 5, 10, RelaxNGErrorCode.unexpected_element_required_element_missing));
}

private static void testDiagnosticsFor(String xml, Diagnostic... expected) {
Consumer<XMLLanguageService> configuration = ls -> {
ContentModelManager contentModelManager = ls.getComponent(ContentModelManager.class);
contentModelManager
.setFileAssociations(createXMLFileAssociation("src/test/resources/relaxng/"));
};

XMLAssert.testDiagnosticsFor(xml, null, configuration, "file:///test/addressBook.xml", expected);
}

private static XMLFileAssociation[] createXMLFileAssociation(String baseSystemId) {
XMLFileAssociation addressBook = new XMLFileAssociation();
addressBook.setPattern("**/addressBook.xml");
addressBook.setSystemId(baseSystemId + "addressBook.rnc");
return new XMLFileAssociation[] { addressBook };
}

}

0 comments on commit 13f2fb4

Please sign in to comment.