问题简要

最近在尝试折腾opds相关的东西,将opds转为特定格式的html并由rclone挂载,但是遇到了一个命名空间相关的问题,opds使用的xml格式设置了一个命名空间,导致无法正常使用xpath解析opds原始文件,在此就尝试解决的内容进行记录。

命名空间简述

xml的命名空间是用于避免多个xml发生冲突的方法。

其在xml中使用 xmlns 以类似于属性的方式进行规定。可以在xmlns关键字后面加上冒号以规定名称空间简写,如果没有简写,则在该xml子树中视为默认命名空间(没特别声明的标签将会归类至该标签中)

该xmlns允许主动声明为空值,表示归类在空 命名空间。也就是未主动声明命名空间的情况下所属的命名空间。

1
2
3
4
5
6
7
<xml xmlns="http://www.w3.org/TR/html4/" xmlms:dc="http://purl.org/dc/terms/">
  <test>test</test>
  <dc:test>test</test>
  <body xmlns=''>
    <p>test</test>
  </body>
</xml>

比如说上面这个xml中,标签属于’http://www.w3.org/TR/html4/'命名空间下,而dc:test标签属于’http://purl.org/dc/terms/'命名空间下。而以及里面的子树则被归类在了空命名空间。

类似于这样,允许一个xml中使用多个命名空间。能够方便配置,以及避免冲突……?

纯xpath匹配(通常很麻烦)

假设一个xml如下,并使用了一个默认命名空间

1
2
3
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/terms/" xmlns:opds="http://opds-spec.org/2010/catalog">
  <test>xxx</test>
  </feed>

这样一个xml不能使用 /feed/test 来匹配,因为feed位于一个另外的命名空间,这时可以用 /*['feed'=local-name()] 来忽略命名空间强行匹配,不过,需要注意的是,此处命名空间设置,不会递归传递到后面,因此,如果需要多层嵌套的话,会非常麻烦。 /*['feed'=local-name()]/*['test'=local-name()] 而且,也有可能(极小概率的)导致出现命名空间冲突(毕竟命名空间本来就是用来规避冲突的嘛)。

而在xpath2.0版本中,以上语法也能记作 “\*:foo"来忽略命名空间匹配本地名称,不过,该新版标准并不太普及(至少截至本文,xnllint并不支持该语法),如果不支持则还需要记作 [local-name() = 'foo']

在xslt中设置命名空间前戳

如果只是需要在xslt中使用xpath匹配其他xml的命名空间的话,则可以考虑将对应的命名空间对应一个前戳来使用。

只需要在xsl:stylesheet标签中将对应的xmlns:前戳的属性加上去即可。

1
2
3
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/terms/" xmlns:opds="http://opds-spec.org/2010/catalog">

</xsl:stylesheet>

类似于这样,在该xsl文件中,可以直接使用 ’\/atom:标签/‘ 这样的xpath来匹配属于’http://www.w3.org/2005/Atom'命名空间的标签。能够大幅度简化xpath的配置,并提高xpath的准确性。(毕竟 ['标签'=local-name()] 这样的xpath,是直接忽略掉命名空间进行匹配的呢)

链接记录,参考文章

https://www.w3school.com.cn/xml/xml%5Fnamespaces.asp
https://www.cnblogs.com/bmaker/p/5605738.html
https://hustlei.github.io/2015/05/xml-xsl-xpath-namespace.html