解析 html 文件,使用 xslt 3 从嵌套类别层次结构中获取数据
parse html file, obtaining data from nested categories hierarchy using 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
等等,
如何获得显示类别层次结构的输出?
/---------------------
| 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
等...
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 应该是嵌套的,显示类别的层次结构。
我使用 saxon HE 版本 9.8
您的数据结构似乎很差(只检查了 http://bpeck.com/references/DDC/ddc_mine900.htm but that doesn't pass HTML validation at https://validator.w3.org/check?uri=http%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 解析器来输入您可以尝试使用 David Carlisle 在纯 XSLT 2 中实现的 htmlparse
函数,它可以在 https://github.com/davidcarlisle/web-xslt/blob/master/htmlparse/htmlparse.xsl 在线获得,如果您想使用它来解析您的 HTML,请确保下载本地副本] 在 XSLT 2 或 3 中具有良好的性能。
这是一个使用在线副本并将输入 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-mapping);在下面的示例中,我使用采用先前结果 XML 的模式扩展上面的样式表,以创建可以提供给 xml-to-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="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 table 和列表是否与之前输入的结构相同。
作为使用 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>
给定以下 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
等等,
如何获得显示类别层次结构的输出?
/---------------------
| 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
等...
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 应该是嵌套的,显示类别的层次结构。 我使用 saxon HE 版本 9.8
您的数据结构似乎很差(只检查了 http://bpeck.com/references/DDC/ddc_mine900.htm but that doesn't pass HTML validation at https://validator.w3.org/check?uri=http%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 解析器来输入您可以尝试使用 David Carlisle 在纯 XSLT 2 中实现的 htmlparse
函数,它可以在 https://github.com/davidcarlisle/web-xslt/blob/master/htmlparse/htmlparse.xsl 在线获得,如果您想使用它来解析您的 HTML,请确保下载本地副本] 在 XSLT 2 或 3 中具有良好的性能。
这是一个使用在线副本并将输入 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-mapping);在下面的示例中,我使用采用先前结果 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 table 和列表是否与之前输入的结构相同。
作为使用 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>