如何在 XPath 3.1 和 XSLT 中构建具有错误处理功能的健壮 XML 解析器

问题描述 投票:0回答:1

我的用例:我想分析一个大型 XML 文档,其中包含名为ownedComment 的元素。每个元素都有一个称为 body 的属性。该属性的内容应该是一个字符串,它是一个序列化的XML文档片段。一个例子是

<ownedComment body="&lt;p>This is a &lt;i>comment&lt;/i>&lt;p>"/>

另一个复杂之处是序列化文档引用在外部其他地方(在文件中)定义的 XML 实体。

我正在使用 XSLT 3 和 XPath 3.1,环境是 Oxygen 中的 SAXON EE。我已经成功创建了一个名为 uml:documentation-parser 的函数,它为特定实体定义文件创建解析器。它使用闭包技术(用于实体定义)和高阶函数,因为它返回一个带有签名

function (element()) as element()*
的函数。该函数的语义是: 对于给定的元素 $e,返回其解析为 XML 文档的 $e/ownedComment/@body 的内容,并考虑特定文件中的实体定义。 该函数的概要是如下:

  <xsl:function name="uml:documentation-parser" as="function (element()) as element()*">
        <xsl:param name="entity-file" as="xs:string?"/>
        <xsl:sequence select="
                let $doctype := if ($entity-file) then
                    '&lt;!DOCTYPE root [&lt;!ENTITY % entities SYSTEM ''' || $entity-file || '''&gt;%entities;]&gt;'
                else
                    '',
                    $prolog := '&lt;root xmlns=''http://docbook.org/ns/docbook''&gt;',
                    $epilog := '&lt;/root&gt;'
                return
                    function ($element as element(*)) as element()* {
                        let $text := $element/ownedComment/@body
                        return
                            if ($text) then
                                (concat($doctype, $prolog, $text, $epilog) => parse-xml())/*/*
                            else
                                ()
                    }"/>
    </xsl:function>

需要 $prolog 和 $epilog,因为 @body 中的文档片段可能包含比序列化 XML 元素更多的元素。他们保证始终有一个根元素,并设置命名空间。

当 @body 中的字符串可以被解析为 XML 文档时,这非常好。但是,如果内容不是格式良好且命名空间格式良好的 XML 文档,则 parse-xml() 函数可能会引发动态错误 err:FODC0006。

这就是为什么我想将返回函数(解析器)的签名更改为

function (element()) as map()
。这个想法是解析器永远不应该引发错误,而总是返回包含这些条目的映射:

  • text:始终是原始(未解析的)文本。一根绳子。
  • xml:如果内容格式良好,解析结果如上。 docBook 命名空间中的元素序列。如果 parse-xml 不成功则不存在。
  • err:如果出现解析错误,由 parse-xml() 引发的错误

我的问题是,XPath 中没有 try/catch 机制。这是 XSLT 的一个功能。

我的问题是:有没有办法结合 XPATH 和 XSLT 来构建一个强大的 XML 解析器,作为高阶函数的结果,能够捕获动态错误?

提前致谢, 弗兰克·史泰姆克

xml xpath xslt saxon higher-order-functions
1个回答
1
投票

我已经根据您的代码编写了一个样式表,并添加了一个辅助函数

uml:parse-xml-robustly
,它返回您指定的
map
,并且我更改了您现有的函数,以便它使用这个新函数代替
parse-xml()
,并从它返回的映射中提取已解析的 XML(如果有)。

尚不完全清楚您想对错误采取什么措施。您说您希望强大的解析器返回一个映射,其

error
键将与错误值关联。所以我选择以另一个映射的形式返回错误,键为
code
description
value
,都带有字符串值。

如果

map
返回的
uml:parse-xml-robustly()
不包含已解析的 XML,那么我使用另一个辅助函数以元素的形式从该映射返回
error
映射(因为该函数被声明为返回一个元素)。

作为测试,我添加了一个模板来匹配名为

element
的元素并调用该函数。

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:uml="https://example.com/uml"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:err="http://www.w3.org/2005/xqt-errors"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all">
  
  <xsl:output method="xml" indent="yes"/>
  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="element">
    <xsl:variable name="parser" select="uml:documentation-parser(())"/>
    <xsl:copy>
      <xsl:sequence select="$parser(.)"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:function name="uml:parse-xml-robustly" as="map(*)">
    <xsl:param name="text" as="xs:string"/>
    <xsl:try>
      <xsl:sequence select="
        map{
          'text': $text,
          'xml': parse-xml($text),
          'error': ()
        }
      "/>
      <xsl:catch select="
        map{
          'text': $text,
          'xml': (),
          'error': map{
            'code': 'Q{' || namespace-uri-from-QName($err:code) || '}' || local-name-from-QName($err:code),
            'description': $err:description,
            'value': $err:value
          }
        }
      "/>
    </xsl:try>
  </xsl:function>
  
  <xsl:function name="uml:error-as-element" as="element(error)">
    <xsl:param name="error" as="map(*)"/>
    <error>
      <xsl:for-each select="map:keys($error)">
        <xsl:attribute name="{.}" select="$error(.)"/>
      </xsl:for-each>
    </error>
  </xsl:function>
  
  <xsl:function name="uml:documentation-parser" as="function (element()) as element()*">
    <xsl:param name="entity-file" as="xs:string?"/>
    <xsl:sequence select="
      let $doctype := if ($entity-file) then
          '&lt;!DOCTYPE root [&lt;!ENTITY % entities SYSTEM ''' || $entity-file || '''&gt;%entities;]&gt;'
      else
          '',
          $prolog := '&lt;root xmlns=''http://docbook.org/ns/docbook''&gt;',
          $epilog := '&lt;/root&gt;'
      return
          function ($element as element(*)) as element()* {
              let $text := $element/ownedComment/@body
              return
                  if ($text) then
                    let $result := 
                      concat($doctype, $prolog, $text, $epilog) => uml:parse-xml-robustly()
                    return
                      ($result('xml')/*/*, $result('error')!uml:error-as-element(.))[1]
                  else
                      ()
          }"/>
    </xsl:function>
</xsl:stylesheet>

测试文件:

<root>
  <element>
    <ownedComment body="&lt;p>This is a &lt;i>comment&lt;/i>&lt;/p>"/>
  </element>
  <element>
    <ownedComment body="&lt;p>This is a &lt;i>comment&lt;/i>&lt;p>"/>
  </element>
</root>

结果:

<root>
   <element>
      <p xmlns="http://docbook.org/ns/docbook">This is a <i>comment</i>
      </p>
   </element>
   <element>
      <error code="Q{http://www.w3.org/2005/xqt-errors}FODC0006"
             value="org.xml.sax.SAXParseException; systemId: urn:from-string; lineNumber: 1; columnNumber: 77; The element type &#34;p&#34; must be terminated by the matching end-tag &#34;&lt;/p&gt;&#34;."
             description="First argument to parse-xml() is not a well-formed and namespace-well-formed XML document. org.xml.sax.SAXParseException; systemId: urn:from-string; lineNumber: 1; columnNumber: 77; The element type &#34;p&#34; must be terminated by the matching end-tag &#34;&lt;/p&gt;&#34;.The element type &#34;p&#34; must be terminated by the matching end-tag &#34;&lt;/p&gt;&#34;."/>
   </element>
</root>

© www.soinside.com 2019 - 2024. All rights reserved.