根据第一个点 (.) 拆分方程

Posted

技术标签:

【中文标题】根据第一个点 (.) 拆分方程【英文标题】:Split the equation based on first dot (.) 【发布时间】:2015-03-26 02:56:54 【问题描述】:

请建议如何根据第一个点将方程分成两部分。之前我从 michael.hor257k 得到了基于 BREAK 评论文本的suggestion to Split the Equation,现在需要按句点拆分。

XML:

<root>
    <body><sec><title>The sec 1</title><p>Text 1</p></sec></body>
    <inline-formula>
        <math display="inline">
            <mi>A</mi>
            <mn>4.651</mn>
            <mi>The next text</mi>
        </math>
    </inline-formula>
    <inline-formula>
        <math display="inline">
            <mrow>
                <mrow><mi>B</mi></mrow>
                <mrow><mn>4.651</mn></mrow>
            </mrow>
            <mi>The next text</mi>
        </math>
    </inline-formula>
</root>

XSLT:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:variable name="root" select="//inline-formula/*" />

    <xsl:template match="/">
        <xsl:for-each select="//inline-formula">
                <xsl:for-each select="text()">
                    <xsl:if test="contains(., '.')">
                        <xsl:apply-templates select="$root">
                            <xsl:with-param name="i" select="." tunnel="yes"/>
                        </xsl:apply-templates>
                    </xsl:if>
                </xsl:for-each >
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:param name="i" tunnel="yes"/>
            <xsl:if test="descendant-or-self::text()[contains(., '.')]">
                <xsl:copy>
                    <xsl:apply-templates select="@*|node()"/>
                </xsl:copy>
            </xsl:if>
    </xsl:template>

</xsl:stylesheet>

要求的结果:

<root>
    <body><sec><title>The sec 1</title><p>Text 1</p></sec></body>
    <inline-formula>
        <math display="inline">
            <mi>A</mi>
            <mn>4.</mn>
        </math>
    </inline-formula>
    <inline-formula>
        <math display="inline">
            <!--Text node, before dot is removed -->
            <mn>651</mn>
            <mi>The next text</mi>
        </math>
    </inline-formula>

    <inline-formula>
        <math display="inline">
            <mrow>
                <mrow><mi>B</mi></mrow>
                <mrow><mn>4.</mn></mrow>
            </mrow>
        </math>
    </inline-formula>
    <inline-formula>
        <math display="inline">
            <mrow>
                <!--Text node, before dot is removed -->
                <mrow><mn>651</mn></mrow>
            </mrow>
            <mi>The next text</mi>
        </math>
    </inline-formula>
</root>

【问题讨论】:

您现在已经在 SO 和 xslt 角落花费了一些时间。请学习正确使用标签系统。有关 XSLT 2.0 的问题应该这样标记。 【参考方案1】:

我也想知道您为什么需要这样的转换,但这里有一个可能的解决方案。我不清楚规则,例如

mn 中的元素是否可以超过 2 个 inline-formula? 是否总是需要将mn 的字符串值拆分为单独的元素? 您是说拆分应该发生在第一个 .mn 的值中,但在 MathML 中的单个 mn 元素中多个点没有意义

但抛开所有这些,也许通过两个单独的转换来解决问题会更容易。第一个简单地将mn元素的内容分开:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="mn[contains(.,'.')]">
        <xsl:for-each select="tokenize(.,'\.')">
            <mn>
                <xsl:value-of select="."/>
                <xsl:if test="position() = 1">
                    <xsl:text>.</xsl:text>
                </xsl:if>
            </mn>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

中间结果是

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <body>
      <sec>
         <title>The sec 1</title>
         <p>Text 1</p>
      </sec>
   </body>
   <inline-formula>
      <math display="inline">
         <mi>A</mi>
         <mn>4.</mn>
         <mn>651</mn>
         <mi>The next text</mi>
      </math>
   </inline-formula>
   <inline-formula>
      <math display="inline">
         <mrow>
            <mrow>
               <mi>B</mi>
            </mrow>
            <mrow>
               <mn>4.</mn>
               <mn>651</mn>
            </mrow>
         </mrow>
         <mi>The next text</mi>
      </math>
   </inline-formula>
</root>

然后,应用类似于以下内容的第二个转换。顺便说一句,使用特殊模式关键字#all#current 似乎是个好机会。

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="inline-formula[count(//mn) gt 1]">
        <xsl:apply-templates select="." mode="first"/>
        <xsl:apply-templates select="." mode="second"/>
    </xsl:template>

    <xsl:template match="mn[position() = 2] | mi[. = 'The next text']" mode="first"/>
    <xsl:template match="mi[. != 'The next text']" mode="second"/>

    <xsl:template match="mn[position() = 1]" mode="second">
        <xsl:comment>Text node, before dot is removed</xsl:comment>
    </xsl:template>


    <xsl:template match="@*|node()" mode="#all">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="#current"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

最后的结果是

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <body>
      <sec>
         <title>The sec 1</title>
         <p>Text 1</p>
      </sec>
   </body>
   <inline-formula>
      <math display="inline">
         <mi>A</mi>
         <mn>4.</mn>
      </math>
   </inline-formula>
   <inline-formula>
      <math display="inline"><!--Text node, before dot is removed-->
         <mn>651</mn>
         <mi>The next text</mi>
      </math>
   </inline-formula>
   <inline-formula>
      <math display="inline">
         <mrow>
            <mrow>
               <mi>B</mi>
            </mrow>
            <mrow>
               <mn>4.</mn>
            </mrow>
         </mrow>
      </math>
   </inline-formula>
   <inline-formula>
      <math display="inline">
         <mrow>
            <mrow/>
            <mrow><!--Text node, before dot is removed-->
               <mn>651</mn>
            </mrow>
         </mrow>
         <mi>The next text</mi>
      </math>
   </inline-formula>
</root>

结果包含一个空的mrow 元素。如果它很重要,您可以添加另一个模板

<xsl:template match="mrow/mrow[not(mn)]" mode="second"/>

到第二个转换,但是,同样不清楚应该如何处理空元素。

【讨论】:

感谢建议,这是渲染团队的实时要求之一,这里我们需要根据FIRST dot进行方程拆分,当然是MN 元素和“下一个文本”可以是任何内容或任何数量的元素,可以是第一个 MN(带点)。【参考方案2】:

查看 michael.hor257k 提供的上一个问题的答案,与您在此问题中使用的 XSLT 有几个关键区别。在上一个在 cmets 上拆分的答案中,它会迭代此类 cmets 出现的次数

<xsl:for-each select="0 to count(//comment()[.='Break'])">

因此,在新的解决方案中,您需要迭代文本节点出现的次数,其中包含点:

<xsl:for-each select="0 to count(//text()[contains(., '.')])">

然后,在“身份”模板中,前面的答案会检查当前节点下方的 cmets 数量,看是否显示为被复制:

<xsl:if test="descendant-or-self::text()[count(preceding::comment()[.='Break'])=$i]">

这意味着,在新的解决方案中,您可以这样开始:

<xsl:if test="descendant-or-self::text()[count(preceding::text()[contains(., '.')])=$i]">

但是,这并不完全正确,因为带有点的节点将被复制到拆分的第一部分,但拆分的第二部分根本不包含该节点。

实际需要的表达式是这样的:

<xsl:if test="descendant-or-self::text()[(count(preceding::text()[contains(., '.')])=($i - 1) and contains(., '.')) or count(preceding::text()[contains(., '.')])=$i]">

这会将包含点的节点复制到拆分的两个部分。然后,您将需要一个全新的模板来实际拆分文本。

试试这个 XSLT

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:variable name="root" select="/*" />

<xsl:template match="/*">
    <xsl:copy>
        <xsl:copy-of select="*[not(self::inline-formula)]" />
        <xsl:for-each select="0 to count(//text()[contains(., '.')])">
            <xsl:apply-templates select="$root/inline-formula">
                <xsl:with-param name="i" select="." tunnel="yes"/>
            </xsl:apply-templates>
        </xsl:for-each >
    </xsl:copy>
</xsl:template>

<xsl:template match="@*">
    <xsl:copy />
</xsl:template>

<xsl:template match="node()">
    <xsl:param name="i" tunnel="yes"/>
    <xsl:if test="descendant-or-self::text()[(count(preceding::text()[contains(., '.')])=($i - 1) and contains(., '.')) or count(preceding::text()[contains(., '.')])=$i]">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:if>
</xsl:template>

<xsl:template match="text()[contains(., '.')]">
    <xsl:param name="i" tunnel="yes"/>
    <xsl:choose>
        <xsl:when test="count(preceding::text()[contains(., '.')]) = $i">
            <xsl:value-of select="substring-before(., '.')" /><xsl:text>.</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="substring-after(., '.')" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
</xsl:stylesheet>

【讨论】:

非常感谢关于代码的建议和很好的解释,完美运行,+1 请稍微澄清一下,方程应该只在第一个点处中断,其余文本或方程的一部分不应中断,即使方程在后面的文本中有第二个点。 在内联公式之间,如果出现任何测试,则文本(非方程)在所有方程之上。 我不确定我是否完全理解,恐怕。您问题中的输入 XML 有两个带有点的文本节点,您的预期输出显示它们都被拆分。我的回答是这样做的,输出应该与您的预期输出相匹配。

以上是关于根据第一个点 (.) 拆分方程的主要内容,如果未能解决你的问题,请参考以下文章

如何根据表格第一列拆分出不同的sheet?

在第一个空格出现时拆分字符串

拆分beeswarm 2

luogu P1415 拆分数列 序列DP

仅在指定字符的第一个实例上拆分字符串

MIT线性代数公开课学习笔记第16~20课