【问题标题】:Insert tag in XML text for mixed words在 XML 文本中为混合词插入标签
【发布时间】:2021-11-08 17:23:19
【问题描述】:

我希望从代表命名实体的索引中注释 XML 中的文本:

glossary = ['Paris', 'Vincennes', 'France', 'Guy de Maupassant', 'Maurice Ravel']

我在 Stack 上找到了一个解决方案(由 @Brad Clements 提供)在此处对 XML 进行部分注释:insert tags in ElementTree text

对于 XML:<TEI><teiHeader></teiHeader><body><p>Paris est la capitale de France et Vincennes n'est pas loin.</p><p> Guy de Maupassant et Maurice Ravel inaugurent une nouvelle statue à Paris.</p></body></TEI>

它适用于小于 1 的单词,例如“Paris”、“Vincennes”或“France”,但不适用于索引中的其他单词。

我目前的输出是:<?xml version="1.0"?> <TEI><teiHeader/><body><p><entity>Paris</entity> est la capitale de <entity>France</entity> et <entity>Vincennes</entity> n'est pas loin. </p><p>Guy de Maupassant et Maurice Ravel inaugurent une nouvelle statue à <entity>Paris.</entity> </p></body></TEI>

预期输出:<?xml version="1.0"?> <TEI><teiHeader/><body><p><entity>Paris</entity> est la capitale de <entity>France</entity> et <entity>Vincennes</entity> n'est pas loin. </p><p><entity>Guy de Maupassant</entity> et <entity>Maurice Ravel</entity> inaugurent une nouvelle statue à <entity>Paris.</entity> </p></body></TEI>

我尝试改编的代码(取自insert tags in ElementTree text):

from lxml import etree
import string
import itertools

stylesheet = etree.XML("""
    <xsl:stylesheet version="1.0"
         xmlns:btest="uri:bolder"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

        <xsl:template match="*">
            <xsl:element name="{name(.)}">
                <xsl:copy-of select="@*" />
                <xsl:apply-templates select="text()" />
                <xsl:apply-templates select="./*" />
            </xsl:element>
        </xsl:template>

        <xsl:template match="text()">
            <xsl:copy-of select="btest:bolder(.)/node()" />
        </xsl:template>         
     </xsl:stylesheet>
""")


glossary = ['Paris', 'Vincennes', 'France', 'Guy de Maupassant', 'Maurice Ravel']


def bolder(context, s):
    results = []
    r = None
    # - Iteration over the index and the words contained in the sequence
    for gw, word in itertools.zip_longest(glossary, s[0].split()):
        # - Little preprocessing for words with punctuation
        word_clean = word.translate(str.maketrans('', '', string.punctuation))
        # 1) If word in sequence directly match with glossary (index) add tag
        if word_clean in glossary:
            if r is not None:
                results.append(r)
            r = etree.Element('r')
            b = etree.SubElement(r, 'entity')
            b.text = word
            b.tail = ' '
            results.append(r)
            r = None

        # 2) Otherwise take the word from the index and check that it is contained in the sequence 
        # (if the word is composed eg. First name + last name 
        # and that it is not None)
        elif gw is not None and gw in s[0] and len(gw.split()) > 1:
            # repeat the process to annotate
            if r is not None:
                results.append(r)
            r = etree.Element('r')
            b = etree.SubElement(r, 'entity')
            b.text = gw
            b.tail = ' '
            results.append(r)
            r = None

        # 3) if none of the prerequisites, add text to output with no tag
        else:
            if r is None:
                r = etree.Element('r')
            r.text = '%s%s ' % (r.text or '', word)

        if r is not None:
            results.append(r)

    return results

def test():
    ns = etree.FunctionNamespace('uri:bolder') 
    ns['bolder'] = bolder 
    transform = etree.XSLT(stylesheet)
    new = str(transform(etree.XML("""<TEI><teiHeader></teiHeader><body><p>Paris est la capitale de France et Vincennes n'est pas loin.</p><p> Guy de Maupassant et Maurice Ravel inaugurent une nouvelle statue à Paris.</p></body></TEI>""")))
    print(new)
    
if __name__ == "__main__":
    test()

但输出仍然不足(重复和遗漏):

<?xml version="1.0"?>
<TEI><teiHeader/><body><p><entity>Paris</entity> est la capitale de <entity>France</entity> et <entity>Vincennes</entity> n'est pas loin. </p><p>Guy de Maupassant <entity>Guy de Maupassant</entity> <entity>Maurice Ravel</entity> Ravel inaugurent une nouvelle statue à <entity>Paris.</entity> </p></body></TEI>

如何改进上述解决方案以正确匹配仅形成一个实体的混合词?提前致谢。美好的一天。

【问题讨论】:

    标签: python-3.x xml xslt lxml


    【解决方案1】:

    在 XSLT 3 中,您可以使用 analyze-string:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:fn="http://www.w3.org/2005/xpath-functions"
        expand-text="yes"
        exclude-result-prefixes="#all"
        version="3.0">
        
      <xsl:param name="entity-strings" as="xs:string*" select="'Paris', 'Vincennes', 'France', 'Guy de Maupassant', 'Maurice Ravel'"/>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="text()">
          <xsl:apply-templates select="analyze-string(., string-join($entity-strings, '|'))" mode="entity"/>
      </xsl:template>
      
      <xsl:template match="fn:match" mode="entity">
          <entity>{.}</entity>
      </xsl:template>
      
    </xsl:stylesheet>
    

    Saxonica 的 Saxon-C(任何版本)具有 Python API,因此可以与 Python 3 一起使用。

    或者使用Python扩展函数来lxml as

    from lxml import etree as ET
    import re
    
    
    stylesheet = ET.XML("""
        <xsl:stylesheet version="1.0"
             xmlns:btest="uri:bolder"
             xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
            <xsl:template match="@* | node()">
              <xsl:copy>
                <xsl:apply-templates select="@* | node()"/>
              </xsl:copy>
            </xsl:template>
    
            <xsl:template match="text()">
                <xsl:copy-of select="btest:bolder(.)/node()" />
            </xsl:template>         
         </xsl:stylesheet>
    """)
    
    def bolder(context, s):
        results = []
        splits = re.split(r'(Paris|Vincennes|France|Guy de Maupassant|Maurice Ravel)', s[0])
        for p, split in enumerate(splits):
          if p % 2 == 0:
            el = ET.Element("element")
            el.text = split
          else:
            el = ET.Element("element")
            entity = ET.SubElement(el, "entity")
            entity.text = split
          results.append(el)
        return results 
          
    ns = ET.FunctionNamespace('uri:bolder') 
    ns['bolder'] = bolder 
    transform = ET.XSLT(stylesheet)
    new = str(transform(ET.XML("""<TEI><teiHeader></teiHeader><body><p>Paris est la capitale de France et Vincennes n'est pas loin.</p><p> Guy de Maupassant et Maurice Ravel inaugurent une nouvelle statue à Paris.</p></body></TEI>""")))
    print(new)
    

    【讨论】:

    • 我不喜欢安装 Saxon-C。第二种解决方案是完美的,它允许我根据提取的命名实体的标签来调整输出中的标签类型,而无需通过 XSLT。非常感谢。
    【解决方案2】:

    这是一个示例,说明如何在 XSLT 样式表中完成所有操作(假设使用 libxslt 处理器):

    XSLT 1.0 + EXSLT str:tokenize()

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:str="http://exslt.org/strings"
    extension-element-prefixes="str">
    <xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
    
    <xsl:param name="glossary">Paris|Vincennes|France|Guy de Maupassant|Maurice Ravel</xsl:param>
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="p/text()">
        <xsl:call-template name="tag-terms">
            <xsl:with-param name="string" select="."/>
            <xsl:with-param name="terms" select="str:tokenize($glossary, '|')"/>
        </xsl:call-template>
    </xsl:template>
    
    <xsl:template name="tag-terms">
        <xsl:param name="string"/>
        <xsl:param name="terms"/>
        <xsl:choose>
            <xsl:when test="$terms">
                <xsl:variable name="term" select="$terms[1]" />
                <xsl:choose>
                    <xsl:when test="contains($string, $term)">
                        <!-- process substring-before with the remaining terms -->
                        <xsl:call-template name="tag-terms">
                            <xsl:with-param name="string" select="substring-before($string, $term)"/>
                            <xsl:with-param name="terms" select="$terms[position() > 1]"/>
                        </xsl:call-template>
                        <!-- matched term -->
                        <entity>
                            <xsl:value-of select="$term"/>
                        </entity>
                        <!-- continue with substring-after -->
                        <xsl:call-template name="tag-terms">
                            <xsl:with-param name="string" select="substring-after($string, $term)"/>
                            <xsl:with-param name="terms" select="$terms"/>
                        </xsl:call-template>
                    </xsl:when>
                    <xsl:otherwise>
                        <!-- pass the entire string for processing with the remaining terms -->
                        <xsl:call-template name="tag-terms">
                            <xsl:with-param name="string" select="$string"/>
                            <xsl:with-param name="terms" select="$terms[position() > 1]"/>
                        </xsl:call-template>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$string"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    </xsl:stylesheet>
    

    应用于您的输入示例,这将返回:

    结果

    <?xml version="1.0" encoding="utf-8"?>
    <TEI>
      <teiHeader/>
      <body>
        <p><entity>Paris</entity> est la capitale de <entity>France</entity> et <entity>Vincennes</entity> n'est pas loin.</p>
        <p> <entity>Guy de Maupassant</entity> et <entity>Maurice Ravel</entity> inaugurent une nouvelle statue à <entity>Paris</entity>.</p>
      </body>
    </TEI>
    

    词汇表可以在运行时作为分隔字符串传递给样式表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-19
      • 2010-12-30
      • 1970-01-01
      • 1970-01-01
      • 2016-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多