xslt-3.0 - 解析 html 文件,使用 xslt 3 从嵌套类别层次结构中获取数据
问题描述
给定以下html文件:
http://bpeck.com/references/DDC/ddc_mine900.htm
http://bpeck.com/references/DDC/ddc_mine200.htm
http://bpeck.com/references/DDC/ddc_mine500.htm
ETC,
我怎样才能得到一个显示类别层次结构的输出?
/---------------------
| ID | Name
| 1 | Main Category
| 3 | Sub Category
| 5 | Sub-Sub Category
| 4 | Sub Category
| 2 | Next Main Category
\----------------------
理想情况下,如果输出结果可以是 json 格式,但我猜 xml 可以。
与串行解析器 (SAX) 作斗争,但失败了,正在寻找一个优雅的解决方案。
主要类别
900 World History
910 Geography and travel [see area subdivisions]
920 Biography, genealogy, insignia
930 History of the ancient world
940 General history of Europe [check schedules for date subdivisions]
950 General history of Asia, Far East
ETC...
900 的子类别:
900 Geography & history
901 Philosophy & theory
902 Miscellany
903 Dictionaries & encyclopedias
904 Collected accounts of events
905 Serial publications
906 Organizations & management
907 Education, research, related topics
908 With respect to kinds of persons
...
在 909 世界历史下找到的子子类别示例:
909.7 18th century, 1700-1799
909.8 1800-
909.82 1900-
输出我更喜欢你认为最好的方法。每个键都是 ID,即 900、901、902 等,对应的值是名称:Geography & history、Philosophy & theory、Miscellany。此输出 json 应该是嵌套的,显示类别的层次结构。我使用撒克逊 HE 版本 9.8
解决方案
您拥有的数据似乎结构不佳(仅检查了http://bpeck.com/references/DDC/ddc_mine900.htm但未通过https://validator.w3.org/check?uri=http%的 HTML 验证3A%2F%2Fbpeck.com%2Freferences%2FDDC%2Fddc_mine900.htm&charset=%28detect+automatically%29&doctype=Inline&group=0,特别是子类别列表没有正确嵌套,因此需要一些 XSLT 管道)。
至于使用 XSLT 2 或 3 解析 HTML,如果您无法将 Saxon 设置为使用 TagSoup 之类的 HTML 解析器而不是 XML 解析器作为输入,您可以尝试使用htmlparse
纯 XSLT 2 中实现的 David Carlisle 的函数,它是可用的在https://github.com/davidcarlisle/web-xslt/blob/master/htmlparse/htmlparse.xsl在线,如果你想用它来解析你在 XSLT 2 或 3 中的 HTML,请确保你下载了一个本地副本。表现。
这是一个使用在线副本并将输入 HTML 解析为我编写的 XML 格式的示例:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="data:,dpc"
xmlns:mf="http://example.com/mf"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:import href="https://github.com/davidcarlisle/web-xslt/raw/master/htmlparse/htmlparse.xsl"/>
<xsl:param name="html-file" as="xs:string">http://bpeck.com/references/DDC/ddc_mine900.htm</xsl:param>
<xsl:param name="html-text" as="xs:string" select="unparsed-text($html-file)"/>
<xsl:variable name="html-doc" select="d:htmlparse($html-text, '', true())"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/" name="xsl:initial-template">
<categories>
<xsl:apply-templates select="tail($html-doc//table)"/>
</categories>
</xsl:template>
<xsl:template match="table">
<category>
<xsl:sequence select="mf:create-attributes(tr[1]/td[1])"/>
<xsl:apply-templates select="head(tr)/td[2], tail(tr)/td[1]"/>
</category>
</xsl:template>
<xsl:template match="td">
<subcategory>
<xsl:sequence select="mf:create-attributes(.)"/>
<xsl:apply-templates select="following-sibling::td[1]/ul"/>
</subcategory>
</xsl:template>
<xsl:template match="ul">
<xsl:for-each-group select="*" group-starting-with="li">
<sub-sub-category>
<xsl:sequence select="mf:create-attributes(.)"/>
<xsl:apply-templates select="tail(current-group())"/>
</sub-sub-category>
</xsl:for-each-group>
</xsl:template>
<xsl:function name="mf:create-attributes" as="attribute()*">
<xsl:param name="input" as="xs:string"/>
<xsl:variable name="input-components" as="xs:string*" select="tokenize(normalize-space($input))"/>
<xsl:attribute name="name" select="head($input-components)"/>
<xsl:attribute name="title" select="tail($input-components)"/>
</xsl:function>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/3NzcBud
要使用 XSLT 3 创建 JSON,您有几个选择,一个是让您的样式表创建xml-to-json
函数期望的格式(https://www.w3.org/TR/xslt-30/#json-to-xml-映射);在下面的示例中,我使用一种模式扩展了上述样式表,该模式采用先前的结果 XML 来创建您可以提供的 XML 输入xml-to-json
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="data:,dpc"
xmlns:mf="http://example.com/mf"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:import href="https://github.com/davidcarlisle/web-xslt/raw/master/htmlparse/htmlparse.xsl"/>
<xsl:param name="html-file" as="xs:string">http://bpeck.com/references/DDC/ddc_mine900.htm</xsl:param>
<xsl:param name="html-text" as="xs:string" select="unparsed-text($html-file)"/>
<xsl:variable name="html-doc" select="d:htmlparse($html-text, '', true())"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="categories">
<categories>
<xsl:apply-templates select="tail($html-doc//table)"/>
</categories>
</xsl:variable>
<xsl:apply-templates select="$categories" mode="json"/>
</xsl:template>
<xsl:template match="table">
<category>
<xsl:sequence select="mf:create-attributes(tr[1]/td[1])"/>
<xsl:apply-templates select="head(tr)/td[2], tail(tr)/td[1]"/>
</category>
</xsl:template>
<xsl:template match="td">
<subcategory>
<xsl:sequence select="mf:create-attributes(.)"/>
<xsl:apply-templates select="following-sibling::td[1]/ul"/>
</subcategory>
</xsl:template>
<xsl:template match="ul">
<xsl:for-each-group select="*" group-starting-with="li">
<sub-sub-category>
<xsl:sequence select="mf:create-attributes(.)"/>
<xsl:apply-templates select="tail(current-group())"/>
</sub-sub-category>
</xsl:for-each-group>
</xsl:template>
<xsl:function name="mf:create-attributes" as="attribute()*">
<xsl:param name="input" as="xs:string"/>
<xsl:variable name="input-components" as="xs:string*" select="tokenize(normalize-space($input))"/>
<xsl:attribute name="name" select="head($input-components)"/>
<xsl:attribute name="title" select="tail($input-components)"/>
</xsl:function>
<xsl:mode name="json" on-no-match="shallow-skip"/>
<xsl:template match="category | subcategory | sub-sub-category" mode="json">
<fn:map>
<fn:map key="{@name}">
<fn:string key="title">{@title}</fn:string>
<xsl:where-populated>
<fn:array key="children">
<xsl:apply-templates mode="#current"/>
</fn:array>
</xsl:where-populated>
</fn:map>
</fn:map>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/3NzcBud/1
最后一步将使用函数xml-to-json
( https://www.w3.org/TR/xpath-functions/#func-xml-to-json ) 输出 JSON 而不是 XML:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="data:,dpc"
xmlns:mf="http://example.com/mf"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:import href="https://github.com/davidcarlisle/web-xslt/raw/master/htmlparse/htmlparse.xsl"/>
<xsl:param name="html-file" as="xs:string">http://bpeck.com/references/DDC/ddc_mine900.htm</xsl:param>
<xsl:param name="html-text" as="xs:string" select="unparsed-text($html-file)"/>
<xsl:variable name="html-doc" select="d:htmlparse($html-text, '', true())"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="text"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="categories">
<categories>
<xsl:apply-templates select="tail($html-doc//table)"/>
</categories>
</xsl:variable>
<xsl:variable name="json-xml">
<xsl:apply-templates select="$categories" mode="json"/>
</xsl:variable>
<xsl:sequence select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
<xsl:template match="table">
<category>
<xsl:sequence select="mf:create-attributes(tr[1]/td[1])"/>
<xsl:apply-templates select="head(tr)/td[2], tail(tr)/td[1]"/>
</category>
</xsl:template>
<xsl:template match="td">
<subcategory>
<xsl:sequence select="mf:create-attributes(.)"/>
<xsl:apply-templates select="following-sibling::td[1]/ul"/>
</subcategory>
</xsl:template>
<xsl:template match="ul">
<xsl:for-each-group select="*" group-starting-with="li">
<sub-sub-category>
<xsl:sequence select="mf:create-attributes(.)"/>
<xsl:apply-templates select="tail(current-group())"/>
</sub-sub-category>
</xsl:for-each-group>
</xsl:template>
<xsl:function name="mf:create-attributes" as="attribute()*">
<xsl:param name="input" as="xs:string"/>
<xsl:variable name="input-components" as="xs:string*" select="tokenize(normalize-space($input))"/>
<xsl:attribute name="name" select="head($input-components)"/>
<xsl:attribute name="title" select="tail($input-components)"/>
</xsl:function>
<xsl:mode name="json" on-no-match="shallow-skip"/>
<xsl:template match="category | subcategory | sub-sub-category" mode="json">
<fn:map>
<fn:map key="{@name}">
<fn:string key="title">{@title}</fn:string>
<xsl:where-populated>
<fn:array key="children">
<xsl:apply-templates mode="#current"/>
</fn:array>
</xsl:where-populated>
</fn:map>
</fn:map>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/3NzcBud/2
https://xsltfiddle.liberty-development.net/3NzcBud/3是相同的代码应用于不同的输入文件,至少 XML -> XML -> JSON 生成没有中断,我没有检查是否 HTML表和列表的结构与之前的输入相同。
作为使用 XSLT 3 创建 JSON 并支持 XPath 3.1 映射和数组数据类型(所有版本中的 Saxon 9.8/9.9 以及 Altova 2017/2018/2019 都提供)的另一个选项,您可以直接创建映射和数组并使用序列化方法json
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:d="data:,dpc"
xmlns:mf="http://example.com/mf"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:import href="https://github.com/davidcarlisle/web-xslt/raw/master/htmlparse/htmlparse.xsl"/>
<xsl:param name="html-file" as="xs:string"
>http://bpeck.com/references/DDC/ddc_mine900.htm</xsl:param>
<xsl:param name="html-text" as="xs:string" select="unparsed-text($html-file)"/>
<xsl:variable name="html-doc" select="d:htmlparse($html-text, '', true())"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="json" indent="yes"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:apply-templates select="tail($html-doc//table)"/>
</xsl:template>
<xsl:template match="table">
<xsl:sequence select="mf:category-map(tr[1]/td[1], (head(tr)/td[2], tail(tr)/td[1]))"/>
</xsl:template>
<xsl:template match="td">
<xsl:sequence select="mf:category-map(., following-sibling::td[1]/ul)"/>
</xsl:template>
<xsl:template match="ul">
<xsl:for-each-group select="*" group-starting-with="li">
<xsl:sequence select="mf:category-map(., tail(current-group()))"/>
</xsl:for-each-group>
</xsl:template>
<xsl:function name="mf:split-index-title" as="xs:string*">
<xsl:param name="input" as="xs:string"/>
<xsl:sequence
select="
let $components := tokenize(normalize-space($input))
return
(head($components), string-join(tail($components), ' '))"
/>
</xsl:function>
<xsl:function name="mf:category-map" as="map(xs:string, item())">
<xsl:param name="category" as="element()"/>
<xsl:param name="subcategories" as="element()*"/>
<xsl:variable name="components" select="mf:split-index-title($category)"/>
<xsl:map>
<xsl:map-entry key="$components[1]">
<xsl:map>
<xsl:map-entry key="'title'" select="$components[2]"/>
<xsl:if test="$subcategories">
<xsl:map-entry key="'children'">
<xsl:sequence select="array{ mf:child-categories($subcategories) }"/>
</xsl:map-entry>
</xsl:if>
</xsl:map>
</xsl:map-entry>
</xsl:map>
</xsl:function>
<xsl:function name="mf:child-categories" as="map(xs:string, item())*">
<xsl:param name="subcategories" as="element()*"/>
<xsl:apply-templates select="$subcategories"/>
</xsl:function>
</xsl:stylesheet>
推荐阅读
- vue.js - 如何使用 vuejs 制作可安装的 PWA?
- arrays - uint256 值数组的 Solidity 硬编码初始值失败
- javascript - 如何在反应中控制滚动
- typescript - 基于数组参数的动态返回类型
- python - 根据条件交换索引值
- mongodb - 不是停止字符的 MongoDB 全文索引字符(标记化分隔符)
- javascript - 如何将 JavaScript 变量传递给后续函数?
- javascript - 向 JavaScript Firefox 扩展添加功能时出现问题?
- pandas - 从数据框中获取唯一用户直到当前日期 | 熊猫
- rust - 在 id_tree Rust 中重新排列兄弟节点