解析 html 文件,使用 xslt 3 从嵌套类别层次结构中获取数据

用户10691668

给定以下 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 World history 下找到的子子类别示例:

909.7 18th century, 1700-1799
909.8 1800-
909.82 1900-

输出我更喜欢你认为最好的方法。每个键都是 ID,即 900、901、902 等,对应的值是名称:地理与历史、哲学与理论、杂项。这个输出 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-jsonhttps://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>

https://xsltfiddle.liberty-development.net/3NzcBud/4

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章