我的用例:我想分析一个大型 XML 文档,其中包含名为ownedComment 的元素。每个元素都有一个称为 body 的属性。该属性的内容应该是一个字符串,它是一个序列化的XML文档片段。一个例子是
<ownedComment body="<p>This is a <i>comment</i><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
'<!DOCTYPE root [<!ENTITY % entities SYSTEM ''' || $entity-file || '''>%entities;]>'
else
'',
$prolog := '<root xmlns=''http://docbook.org/ns/docbook''>',
$epilog := '</root>'
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()
。这个想法是解析器永远不应该引发错误,而总是返回包含这些条目的映射:
我的问题是,XPath 中没有 try/catch 机制。这是 XSLT 的一个功能。
我的问题是:有没有办法结合 XPATH 和 XSLT 来构建一个强大的 XML 解析器,作为高阶函数的结果,能够捕获动态错误?
提前致谢, 弗兰克·史泰姆克
我已经根据您的代码编写了一个样式表,并添加了一个辅助函数
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
'<!DOCTYPE root [<!ENTITY % entities SYSTEM ''' || $entity-file || '''>%entities;]>'
else
'',
$prolog := '<root xmlns=''http://docbook.org/ns/docbook''>',
$epilog := '</root>'
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="<p>This is a <i>comment</i></p>"/>
</element>
<element>
<ownedComment body="<p>This is a <i>comment</i><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 "p" must be terminated by the matching end-tag "</p>"."
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 "p" must be terminated by the matching end-tag "</p>".The element type "p" must be terminated by the matching end-tag "</p>"."/>
</element>
</root>