【问题标题】:Is this the only workaround for dispersed schema hierarchy validation?这是分散模式层次结构验证的唯一解决方法吗?
【发布时间】:2015-01-27 04:25:44
【问题描述】:

我最近需要在 java 中使用导入另一个模式的模式来实现模式验证(从今以后我将把它称为模式层次结构)。令我惊讶的是,我发现模式层次结构极大地复杂了用于简单独立模式验证的代码。我对修复(以及我创建的)的理解是 LSResourceResolver 接口的 impl 和要返回的 LSInput 接口的 impl。我的理解是,当需要针对架构层次进行验证时,这是必要的。

我觉得这很令人沮丧,因为一旦验证器拥有根模式的句柄,任何导入都只是相对于该位置。为了使验证更容易和可重用,我开始创建一个解析器,最终将模式验证简化为每种情况下的两个输入。

  1. 什么是根架构
  2. 您要验证的有效负载是什么。

换句话说,我的目标是对任何模式结构进行如下工作:

XmlValidator validator = new XmlValidator("some/dir/root.xsd");
validator.validate("<xml><someXml/></xml>");

查看the documentation 以获取被调用以加载资源的函数时,您会发现第一个问题是未调用解析器来加载根资源(根模式)。您需要该根模式的路径才能从中查找其他相对路径。这可以通过将根路径传递给解析器的构造函数并手动跟踪它来克服。

然后是路障。 systemId 参数可靠地包含试图解析/加载的资源(此字符串正是 import/include/redefine schemaLocation 属性的内容)。例如: 如果您正在加载的当前架构有这一行:

<xsd:include schemaLocation="../given/redefine.xsd"/>

加载redefine.xsd时的systemId为:

"../given/redefine.xsd"

但是,baseURI 参数应该保存之前加载的资源(您必须知道这一点,因为您正在根据先前资源的位置创建相对路径)可以是null,根据我的经验,它适用于将要加载的模式的 2/3。

这就是我觉得 java 内部验证无法提供我正在寻找的解决方案的地方。我们试图解决的问题似乎很简单。给定一个根模式,根据根模式的位置加载所有其他包含的模式。除非我遗漏了什么,否则现在这是不可能的,因为 baseURI 可以是 null,因此无法跟踪以前的架构。

在 java 的生命周期中,我们当然不能走这么远,而且这个问题还没有解决。我在这里想念什么?现在不可能编写验证实用程序并且只提供上述两个输入,这是否正确?其他人使用什么进行架构验证?我必须相信其他人不会不断滚动自定义解析器类以围绕架构层次结构跳舞(这应该相当普遍)。

这是试图解决的问题的简单表示。我正在寻找最简单、最类似于 java 的方法来解决这个示例问题:

假设示例项目结构为:

src/main/java/sandbox/TestValidation.java

src/main/resources/sandbox/sample.xml
src/main/resources/sandbox/custom/wrapper.xsd
src/main/resources/sandbox/custom/candy.xsd
src/main/resources/sandbox/given/base.xsd
src/main/resources/sandbox/given/redefine.xsd

TestValidation.java:

import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;

import com.blarg.validation.XmlValidator;


public class SchemaValidationTest {

    public SchemaValidationTest() throws Exception {
//      The linked suggested solution which fails because
//      it cannot load the first referenced schema
        Source schemaFile = new StreamSource(
                getClass().getClassLoader()
                .getResourceAsStream("sandbox/custom/wrapper.xsd"));
        Source xmlFile = new StreamSource(
                getClass().getClassLoader()
                .getResourceAsStream("sandbox/sample.xml"));
        SchemaFactory schemaFactory = SchemaFactory
            .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(schemaFile);
        Validator validator = schema.newValidator();
        try {
          validator.validate(xmlFile);
          System.out.println(xmlFile.getSystemId() + " is valid");
        } catch (SAXException e) {
          System.out.println(xmlFile.getSystemId() + " is NOT valid");
          System.out.println("Reason: " + e.getLocalizedMessage());
        }

//      My custom validator which succeeds all the way until
//      it reaches the candy.xsd for reasons described above and again below. 
        XmlValidator customValidator = new XmlValidator("sandbox/custom/wrapper.xsd");
        customValidator.validate(getClass().getClassLoader().getResourceAsStream("sandbox/sample.xml"));
    }

    public static void main(String[] args) throws Exception {
        new SchemaValidationTest();
    }
}

sample.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Wrapper> <!-- wrapper.xsd -->
    <GiftBasket>
        <Fruit> <!-- base.xsd -->
            <Apple>
                <Size>medium</Size>
                <Color>Red</Color> <!-- redefine.xsd -->
            </Apple>
            <Orange>
                <Size>large</Size>
            </Orange>
        </Fruit>
        <Candy> <!-- candy.xsd -->
            <Caramel>salted</Caramel>
        </Candy>
    </GiftBasket>
</Wrapper>

包装器.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xsd:include schemaLocation="../given/redefine.xsd"/>
    <xsd:include schemaLocation="./candy.xsd"/>
    <xsd:element name="Wrapper">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="GiftBasket" type="GiftBasket_Type" minOccurs="1" maxOccurs="1"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:complexType name="GiftBasket_Type">
        <xsd:sequence>
            <!-- From base.xsd (and apple is redefined in redefine.xsd) -->
            <xsd:element name="Fruit" type="Fruit_Type" minOccurs="1" maxOccurs="1"/>
            <!-- From candy.xsd -->
            <xsd:element name="Candy" type="Candy_Type" minOccurs="0" maxOccurs="1"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

base.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" attributeFormDefault="unqualified">
    <xsd:complexType name="Fruit_Type">
        <xsd:sequence>
            <xsd:element name="Apple" type="Apple_Type" minOccurs="0" maxOccurs="unbounded" />
            <xsd:element name="Orange" type="Orange_Type" minOccurs="0" maxOccurs="unbounded" />
        </xsd:sequence>
    </xsd:complexType>
    <!-- This is redefined in redefine.xsd to include additional elements -->
    <xsd:complexType name="Apple_Type">
        <xsd:sequence>
            <xsd:element name="Size" type="xsd:string" minOccurs="0" maxOccurs="1" />
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="Orange_Type">
        <xsd:sequence>
            <xsd:element name="Size" type="xsd:string" minOccurs="0" maxOccurs="1" />
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

重新定义.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" attributeFormDefault="unqualified">
    <xsd:redefine schemaLocation="./base.xsd">
        <xsd:complexType name="Apple_Type">
            <xsd:complexContent>
                <xsd:extension base="Apple_Type">
                    <xsd:sequence>
                        <xsd:element name="Color" type="xsd:string"/>
                    </xsd:sequence>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:redefine>
</xsd:schema>

candy.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xsd:complexType name="Fruit_Type">
        <xsd:choice>
            <xsd:element name="Chocolate" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
            <xsd:element name="Caramel" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
        </xsd:choice>
    </xsd:complexType>
</xsd:schema>

如果您想查看我当前的 LSResourceResolver 实现,它可以让我接近解决方案,如下所示。如果 candy.xsd 的导入和引用的元素从 wrapper.xsd 和 sample.xml 中删除,则此验证。它不起作用的原因是因为在加载 candy.xsd 时,之前加载的路径在 sandbox/given 并且传入的 systemId 将是 ./candy.xsd 所以它会在错误的位置查找 candy.xsd:

package com.blarg.validation;

import java.io.InputStream;
import java.util.LinkedList;

import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;

import com.blarg.validation.exception.SchemaNotFoundException;

public class SchemaResolver implements LSResourceResolver {

    private String path;
    private ClassLoader classLoader;
    private SchemaTracker tracker;

    public SchemaResolver(String path, ClassLoader classLoader, SchemaTracker tracker) {
        this.path = path;
        this.tracker = tracker;
        this.classLoader = classLoader;
    }

    public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
        String classloaderPath = generateClassloaderResourcePath(path, systemId);
        tracker.setLastLoadedSchema(classloaderPath);
        InputStream is = classLoader.getResourceAsStream(classloaderPath);
        if (is == null) {
            throw new SchemaNotFoundException("Loading the root schema succeeded, but the following referenced schema could not be found: '"
                    + classloaderPath
                    + "' Make sure the root schema and referenced schemas are all in the same directory. Then verify any <xsd:include>, "
                    + "<xsd:import>, or <xsd:redefine> tags all have correct 'schemaLocation' attribute values.");
        }
        /*
         * Store the last used path so the next schema lookup is relative to it.
         * This is a hack and will only work if:
         * some/dir/a.xsd imports some/dir/another/b.xsd
         * and some/dir/another/b.xsd imports some/dir/other/c.xsd
         * etc..
         * 
         * It will *not* work for:
         * some/dir/a.xsd imports some/dir/another/b.xsd
         * some/dir/another/b.xsd imports some/dir/other/c.xsd
         * etc..
         * AND
         * some/dir/a.xsd also imports some/dir/d.xsd
         * 
         * It will fail loading d.xsd because the last stored path
         * will be /some/dir/other and the systemId coming in will
         * be "./d.xsd"
         */
        path = classloaderPath.substring(0, classloaderPath.lastIndexOf("/") + 1);
        return new SchemaInput(publicId, systemId, is);
    }


    private String generateClassloaderResourcePath(String path, String systemId) {
//          fullPath may contain ./ or ../ which is not allowed in classloader resource lookups.
        String fullPath = path + systemId;

        LinkedList<String> linkedList = new LinkedList<String>();
        String current = first(fullPath);
        while (current != null) {
            if (".".equals(current)) {
//                  Do nothing, dot represents the current directory so we have it already
            } else if ("..".equals(current)) {
//                  Remove the lastly added directory because we need to go up
                linkedList.removeLast();
            } else {
//                  The directory is just a normal directory or filename, add it
                linkedList.add(current);
            }
            fullPath = removeFirst(fullPath);
            current = first(fullPath);
        }

        String classLoaderPath = "";
        while (linkedList.size() > 0) {
            classLoaderPath = classLoaderPath + linkedList.removeFirst() + "/";
        }
        classLoaderPath = classLoaderPath.substring(0, classLoaderPath.length() - 1);
        System.out.println("classLoaderPath: " + classLoaderPath);
        System.out.println();
        return classLoaderPath;
    }

    private String first(String path) {
        if (path == null) {
            return null;
        } else if (path.contains("/")) {
            return path.substring(0, path.indexOf("/"));
        } else {
            return path;
        }
    }

    private String removeFirst(String path) {
        if (path.contains("/")) {
            return path.substring(path.indexOf("/") + 1);
        } else {
            return null;
        }
    }
}

你当然需要正确地实例化它(给它正确的根模式路径并使用 schemaFactory 注册它:

schemaFactory.setResourceResolver(new SchemaResolver(pathToSchemas, classLoader, tracker));

【问题讨论】:

  • 架构验证不需要自定义资源解析器来处理xsd:import
  • 那么 xsd:include 和 xsd:redefine 呢?我很高兴尝试您提供的任何代码示例,但我尝试仅加载我的根架构,但它无法加载引用的资源。我的特定架构布局是 a.xsd 包括 b.xsd,它重新定义了 c.xsd。
  • 默认资源分辨率就足够了。如果您不能准确说明您的需求为何不同,那么您可能会在错误的树上编写自定义资源解析器。用于验证的示例 Java 代码比比皆是,例如:What's the best way to validate an XML file against an XSD file?
  • 另一条评论:试图提供帮助,但我知道它可能不会被欣赏。如果你想从这方面的专家那里得到帮助,那么他们很有可能每天都成功地使用它,并且认为任何认为它被破坏的人都是一个混蛋。他们很可能是错的,但你希望他们站在你这边。所以善待他们,尊重他们最喜欢的技术。顺便说一句,我不是其中之一:我已经实现了这些接口并且我知道它们的错误。但它们并非不可挽回地损坏。
  • Java 是我的主要语言,也是我“最喜欢的技术”。所以我就是那些人中的一员。如果批评我每天将要使用的语言并希望它通过指出它的问题并努力在像 SO 这样的公共网站上解决它们以便其他开发人员可以受益让我成为一个“混蛋”,那么随它吧。 kjhughes 提供的解决方案是我尝试的第一个解决方案,但不起作用。令人惊讶的是,如果手头的问题没有任何具体答案,这些 cmets 会变得多么重要。坚持技术主题或节省您的呼吸。

标签: java xml validation


【解决方案1】:

不确定它是否能回答你的问题,但这是我的看法。

“Java 内部架构验证解决方案”本质上是重新打包的 Xerces。

因此,如果您要问 Xerces 是否损坏 - 不,不是。

如果你问它是否有错误 - 是的,它可能有一些。

“它坏了吗?”这个问题的答案是什么?真的吗?

“不,它不是”可能会与您的体验相矛盾 - 这将是我们的任务,以某种方式说服您它没有被破坏。

“是的”——嗯,很好,很多事情都是,我们该怎么做的问题。

我认为正确的做法是创建一个可重现的示例and file an issue in XercesJ

在某些情况下你会得到null baseURI?提出问题。

相对 URI 没有得到解析?提出问题。

虽然有一些注意事项,但我不能真正确认架构验证是否完全被破坏。我通常将所有模式作为资源放入类路径中,并从类路径资源 URI 加载根模式。以我的经验,相对引用的模式通常是 OOTB 解决的。所以我猜你遇到了一些极端情况。然而,我normally work with 的模式也远非理想。在某些情况下,我不得不使用目录解析器来重写绝对 URI,但总的来说,我通常会在最后完成工作。

我真的理解你的痛苦。我也有hit a couple of corners with resolvers(但在不同的环境中),所以你感到沮丧也就不足为奇了。但归根结底,重要的不是你如何论证它坏了,而是你是否设法修复它。这才是最重要的。

祝你好运,保持建设性。 :)

【讨论】:

  • 这当然是我的目标——修复它。这篇文章有两个目标:指出 java 的模式验证被破坏(示例模式和示例测试类显示),并提供更好的方法(我提供的模式解析器类)。你们感受到的挫败感是我无法提供一种单一的、更好的、非常简单的方法来验证任何模式结构。我非常想让模式验证对所有人来说“非常容易”。归根结底,唯一必要的输入是根模式和有效负载。我非常非常想实现这个目标。
  • 您还知道其他 XercesJ 替代品吗?
  • @Russ 那么我希望你能看到这不适合 SO 格式。 “指出它已损坏”和“提供更好的方法”并不是我的 PoV 中关于 SO 的主题。至于另一个问题(这也是题外话“推荐一个工具”) - 我不太清楚,去年只使用了 Xerces/built-in。当时还有 MSV,除了 XSD 之外,我还使用 Schematron 和 RelaxNG,但这并不能回答您的问题。目前我(个人)不知道有其他选择。
  • @Russ 我不会说愤怒地咆哮和诉诸侮辱是一个好策略。如果您对人们对您的问题做出反应的原因感兴趣,请随时通过meta 提问。如果您在那里提出要求,我很乐意提供反馈。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-30
  • 2012-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多