对 XPath 查询使用 XSD 架构验证

ago*_*dev 7 php xml xsd domdocument xml-validation

我正在使用以下代码创建一个 DOMDocument 并针对外部 xsd 文件对其进行验证。

<?php

$xmlPath = "/xml/some/file.xml";
$xsdPath = "/xsd/some/schema.xsd";
    
$doc = new \DOMDocument();
$doc->loadXML(file_get_contents($xmlPath), LIBXML_NOBLANKS);

if (!$doc>schemaValidate($xsdPath)) {
    throw new InvalidXmlFileException();
}
Run Code Online (Sandbox Code Playgroud)

更新 2(重写的问题)

这工作正常,这意味着如果 XML 与 XSD 的定义不匹配,它将抛出一个有意义的异常。

现在,我想使用 Xpath 从 DOMDocument 中检索信息。它也可以正常工作,但是,从这一点开始,DOMDocument 与 XSD 完全分离!例如,如果我有一个DOMNode,我不知道它是simpleType类型还是complexType类型。我可以检查节点是否有子节点(hasChild()),但这不一样。此外,XSD 中还有大量信息(例如,最小和最大出现次数等)。

问题真的是,我必须自己查询 XSD 还是有一种编程方式来询问这些问题。即这个DOMNode是复杂类型还是简单类型?

另一篇文章中,有人建议“使用真正的模式处理器处理模式,然后使用其 API 询问有关模式内容的问题”。XPath 是否有一个 API 来检索 XSD 的信息,或者是否有与 DOMDocument 不同的便捷方式?

为了记录,原始问题

现在,我想继续使用 XPath 解析 DOMDocument 中的信息。为了提高我存储到数据库中的数据的完整性并向客户端提供有意义的错误消息,我想不断使用模式信息来验证查询。即我想根据 xsd 中定义的允许的子节点验证获取的 childNodes。我想通过在 xsd 文档上使用 XPath 来做到这一点。

但是,我偶然发现了这篇文章。它基本上是说它是你自己的一种奇怪的方式,你应该使用真正的模式处理器并使用它的 API 来进行查询。如果我理解正确,我正在使用一个真正的模式处理器schemaValidate,但是使用它的 API 意味着什么?

我已经猜到我没有以正确的方式使用模式,但我不知道如何研究正确的用法。

问题

如果我schemaValidate在 DOMDocument 上使用的是一次性验证(真或假),还是与 DOMDocument 绑定的时间更长?确切地说,我可以使用验证来以某种方式添加节点,还是可以使用它来选择我感兴趣的节点,如引用的 SO 帖子所建议的那样?

更新

这个问题被评为不清楚,所以我想再试一次。假设我想添加一个节点或编辑一个节点值。我可以使用 xsd 中提供的架构来验证用户输入吗?最初,为了做到这一点,我想使用另一个 XPath 实例手动查询 xsd 以获取某个节点的规格。但正如链接文章中所建议的那样,这不是最佳实践。所以问题是,DOM 库是否提供任何 API 来进行这样的验证?

可能是我多虑了。也许我只是添加节点并再次运行验证,看看它在哪里/为什么会中断?在这种情况下,自定义错误处理的答案将是正确的。你可否确认?

mik*_*n32 8

Your question is not very clear, but it sounds like you want to get detailed reporting about any schema validation failures. While DomDocument::validateSchema() only returns a boolean, you can use internal libxml functions to get some more detailed information.

We can start with your original code, only changing one thing at the top:

<?php
// without this, errors are echoed directly to screen and/or log
libxml_use_internal_errors(true);
$xmlPath = "file.xml";
$xsdPath = "schema.xsd";

$doc = new \DOMDocument();
$doc->loadXML(file_get_contents($xmlPath), LIBXML_NOBLANKS);

if (!$doc->schemaValidate($xsdPath)) {
    throw new InvalidXmlFileException();
}
Run Code Online (Sandbox Code Playgroud)

And then we can make the interesting stuff happen in the exception which is presumably (based on the code you've provided) caught somewhere higher up in the code.

<?php

class InvalidXmlFileException extends \Exception
{
    private $errors = [];

    public function __construct()
    {
        foreach (libxml_get_errors() as $err) {
            $this->errors[] = self::formatXmlError($err);
        }
        libxml_clear_errors();
    }

    /**
     * Return an array of error messages
     *
     * @return array
     */
    public function getXmlErrors(): array
    {
        return $this->errors;
    }

    /**
     * Return a human-readable error message from a libxml error object
     *
     * @return string
     */
    private static function formatXmlError(\LibXMLError $error): string
    {
        $return = "";
        switch ($error->level) {
        case \LIBXML_ERR_WARNING:
            $return .= "Warning $error->code: ";
            break;
         case \LIBXML_ERR_ERROR:
            $return .= "Error $error->code: ";
            break;
        case \LIBXML_ERR_FATAL:
            $return .= "Fatal Error $error->code: ";
            break;
        }

        $return .= trim($error->message) .
               "\n  Line: $error->line" .
               "\n  Column: $error->column";

        if ($error->file) {
            $return .= "\n  File: $error->file";
        }

        return $return;
    }
}
Run Code Online (Sandbox Code Playgroud)

So now when you catch your exception you can just iterate over $e->getXmlErrors():

try {
    // do stuff
} catch (InvalidXmlFileException $e) {
    foreach ($e->getXmlErrors() as $err) {
        echo "$err\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

For the formatXmlError function I just copied an example from the PHP documentation that parses the error into something human readable, but no reason you couldn't return some structured data or whatever you like.

  • 更新使事情变得更清晰(至少对我来说!)您希望在文档中的任何添加发生之前对其进行验证。就我个人而言,我认为仅添加节点并重新验证整个事情没有什么问题。PHP 没有专用的模式解析器,但如果您想要一个,这看起来很有前途:https://github.com/goetas-webservices/xsd-reader/blob/master/README.md (3认同)