Skip to content

Commit

Permalink
RelaxNG validation with XInclude / File association report DOCTYPE error
Browse files Browse the repository at this point in the history
Fixes #1421

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
azerr authored and JessicaJHee committed Dec 19, 2022
1 parent 486955a commit 131090f
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 14 deletions.
Expand Up @@ -115,14 +115,16 @@ && isSchemaValidationEnabled(document, validationSettings)
&& isNoNamespaceSchemaValidationEnabled(document, validationSettings)));
parser.setFeature("http://apache.org/xml/features/validation/schema", schemaValidationEnabled); //$NON-NLS-1$

boolean hasGrammar = document.hasDTD() || hasSchemaGrammar || (document.hasExternalGrammar()
&& !DOMUtils.isRelaxNGUri(document.getExternalGrammarFromNamespaceURI()));
boolean hasRelaxNG = hasRelaxNGReference(document, parser);
boolean hasGrammar = document.hasDTD() || hasSchemaGrammar
|| (!hasRelaxNG && document.hasExternalGrammar());
if (hasSchemaGrammar && !schemaValidationEnabled) {
hasGrammar = false;
}
parser.setFeature("http://xml.org/sax/features/validation", hasGrammar); //$NON-NLS-1$

boolean namespacesValidationEnabled = isNamespacesValidationEnabled(document, validationSettings, parser);
boolean namespacesValidationEnabled = isNamespacesValidationEnabled(document, validationSettings,
hasRelaxNG);
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,18 +148,11 @@ && isSchemaValidationEnabled(document, validationSettings)
}

private static boolean isNamespacesValidationEnabled(DOMDocument document,
XMLValidationSettings validationSettings, SAXParser reader) {
try {
if (reader.getProperty(IExternalGrammarLocationProvider.RELAXNG) != null) {
return true;
}
} catch (Exception e) {
// Do nothing
}
if (validationSettings == null) {
XMLValidationSettings validationSettings, boolean hasRelaxNG) {
if (hasRelaxNG) {
return true;
}
if (hasRelaxNGReference(document)) {
if (validationSettings == null) {
return true;
}
NamespacesEnabled enabled = NamespacesEnabled.always;
Expand All @@ -177,7 +172,14 @@ private static boolean isNamespacesValidationEnabled(DOMDocument document,
}
}

private static boolean hasRelaxNGReference(DOMDocument document) {
private static boolean hasRelaxNGReference(DOMDocument document, SAXParser parser) {
try {
if (parser.getProperty(IExternalGrammarLocationProvider.RELAXNG) != null) {
return true;
}
} catch (Exception e) {
// Do nothing
}
List<XMLModel> models = document.getXMLModels();
for (XMLModel xmlModel : models) {
if (DOMUtils.isRelaxNGUri(xmlModel.getHref())) {
Expand Down
@@ -0,0 +1,120 @@
/*******************************************************************************
* 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.io.File;
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.ContentModelSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLFileAssociation;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings;
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 XMLFileAssociationRNCDiagnosticsTest 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, "file:///test/addressBook.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, "file:///test/addressBook.xml", //
d(2, 5, 12, RelaxNGErrorCode.unknown_element), //
d(3, 5, 10, RelaxNGErrorCode.unexpected_element_required_element_missing));
}

@Test
public void xinclude() throws Exception {
ContentModelSettings settings = new ContentModelSettings();
XMLValidationRootSettings validation = new XMLValidationRootSettings();
validation.getXInclude().setEnabled(true);
settings.setValidation(validation);
String fileURI = new File("src/test/resources/relaxng/xinclude/foo.xml").toURI().toString();

String xml = "<foo xmlns:xi=\"http://www.w3.org/2001/XInclude\">\r\n" + //
" <xi:include href=\"bar.xml\" />\r\n" + //
"</foo>";
testDiagnosticsFor(xml, fileURI, settings);

xml = "<foo xmlns:xi=\"http://www.w3.org/2001/XInclude\">\r\n" + //
" <xi:include href=\"bar2.xml\" />\r\n" + //
"</foo>";
testDiagnosticsFor(xml, fileURI, settings, //
d(0, 1, 4, null), //
d(0, 1, 4, RelaxNGErrorCode.incomplete_element_required_element_missing));
}

private static void testDiagnosticsFor(String xml, String fileURI, Diagnostic... expected) {
ContentModelSettings settings = new ContentModelSettings();
settings.setValidation(new XMLValidationRootSettings());
testDiagnosticsFor(xml, fileURI, settings, expected);
}

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

XMLAssert.testDiagnosticsFor(xml, null, configuration, fileURI, true, settings,
expected);
}

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

}
@@ -0,0 +1,120 @@
/*******************************************************************************
* 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.io.File;
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.ContentModelSettings;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLFileAssociation;
import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationRootSettings;
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 XML syntax by using XML file
* association.
*
*/
public class XMLFileAssociationRNGDiagnosticsTest 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, "file:///test/addressBook.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, "file:///test/addressBook.xml", //
d(2, 5, 12, RelaxNGErrorCode.unknown_element), //
d(3, 5, 10, RelaxNGErrorCode.unexpected_element_required_element_missing));
}

@Test
public void xinclude() throws Exception {
ContentModelSettings settings = new ContentModelSettings();
XMLValidationRootSettings validation = new XMLValidationRootSettings();
validation.getXInclude().setEnabled(true);
settings.setValidation(validation);
String fileURI = new File("src/test/resources/relaxng/xinclude/foo.xml").toURI().toString();

String xml = "<foo xmlns:xi=\"http://www.w3.org/2001/XInclude\">\r\n" + //
" <xi:include href=\"bar.xml\" />\r\n" + //
"</foo>";
testDiagnosticsFor(xml, fileURI, settings);

xml = "<foo xmlns:xi=\"http://www.w3.org/2001/XInclude\">\r\n" + //
" <xi:include href=\"bar2.xml\" />\r\n" + //
"</foo>";
testDiagnosticsFor(xml, fileURI, settings, //
d(0, 1, 4, null), //
d(0, 1, 4, RelaxNGErrorCode.incomplete_element_required_element_missing));
}

private static void testDiagnosticsFor(String xml, String fileURI, Diagnostic... expected) {
ContentModelSettings settings = new ContentModelSettings();
settings.setValidation(new XMLValidationRootSettings());
testDiagnosticsFor(xml, fileURI, settings, expected);
}

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

XMLAssert.testDiagnosticsFor(xml, null, configuration, fileURI, true, settings,
expected);
}

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

}
@@ -0,0 +1,2 @@

<bar></bar>
@@ -0,0 +1,2 @@

<bar2></bar2>
@@ -0,0 +1,5 @@
element foo {
element bar {
attribute xml:base{text}
}
}
14 changes: 14 additions & 0 deletions org.eclipse.lemminx/src/test/resources/relaxng/xinclude/foo.rng
@@ -0,0 +1,14 @@
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
xmlns:xi="http://www.w3.org/2001/XInclude">
<start>
<ref name="foo" />
</start>
<define name="foo">
<element name="foo">
<element name="bar">
<attribute name="xml:base" />
</element>
</element>
</define>
</grammar>
@@ -0,0 +1,3 @@
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="bar.xml" />
</foo>

0 comments on commit 131090f

Please sign in to comment.