在混合词的 XML 文本中插入标签

Insert tag in XML text for mixed words

我希望从代表命名实体的索引中注释 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>

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

在 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.

一起使用

或者对 lxml 使用 Python 扩展函数作为

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)

这是一个示例,说明如何在 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>

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

结果

<?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>

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