【问题标题】:Surrounding bits of text with xml elements带有 xml 元素的文本位
【发布时间】:2019-12-22 14:10:49
【问题描述】:

我正在寻找一种基于正则表达式使用 XML 节点动态包围部分文本的方法。

考虑以下示例

<speak>The test number is 123456789, and some further block of text.</speak>

现在假设我有一个针对数字的正则表达式,可以选择性地用一个新标签包围它,这样它就会变成:

<speak>The test number is <say-as interpret-as="characters">123456789</say-as>, and some further block of text.</speak>

我考虑过使用 DomDocument 来创建标签,但不确定替换部分。有什么建议吗?

【问题讨论】:

    标签: php xml ssml


    【解决方案1】:

    DOM 是正确的方法。它允许您查找和遍历文本节点。对这些节点的内容使用 RegEx,并将新节点构建为片段。

    function wrapMatches(\DOMNode $node, string $pattern, string $tagName, $tagAttributes = []) {
        $document = $node instanceof DOMDocument ? $node : $node->ownerDocument;
        $xpath = new DOMXpath($document);
        // iterate all descendant text nodes
        foreach ($xpath->evaluate('.//text()', $node) as $textNode) {
            $content = $textNode->textContent;
            $found = preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE);
            $offset = 0;
            if ($found) {
                // fragments allow to treat multiple nodes as one
                $fragment = $document->createDocumentFragment();
                foreach ($matches[0] as $match) {
                    list($matchContent, $matchStart) = $match;
                    // add text from last match to current
                    $fragment->appendChild(
                      $document->createTextNode(substr($content, $offset, $matchStart - $offset))
                    );
                    // add wrapper element, ...
                    $wrapper = $fragment->appendChild($document->createElement($tagName));
                    // ... set its attributes ...
                    foreach ($tagAttributes as $attributeName => $attributeValue) {
                        $wrapper->setAttribute($attributeName, $attributeValue);
                    }
                    // ... and add the text content
                    $wrapper->textContent = $matchContent;
                    $offset = $matchStart + strlen($matchContent);
                }
                // add text after last match
                $fragment->appendChild($document->createTextNode(substr($content, $offset)));
                // replace the text node with the new fragment
                $textNode->parentNode->replaceChild($fragment, $textNode);
            }
        }
    }
    
    
    $xml = <<<'XML'
    <speak>The test number is 123456789, and some further block of text.</speak>
    XML;
    
    $document = new DOMDocument();
    $document->loadXML($xml);
    
    wrapMatches($document, '(\d+)u', 'say-as', ['interpret-as' => 'characters']);
    
    echo $document->saveXML();
    

    【讨论】:

    • 看看它的复杂性,我可能最好使用 preg_replace 和一个将标签构造为字符串的简单方法。所以也许第一个毕竟是更优雅的解决方案。
    • 简单的正则表达式不考虑 XML 结构。它可能适用于某些特定情况,但它确实很脆弱。
    • DOM 是进行这种转换的一种非常低级的方式。 XSLT 的代码通常要少得多。
    • 它将文本转换为 XML。 XSLT 1.0 对此只有有限的功能。 (XSLT 2.0 需要像 Saxon/C 这样的东西)。我正在尝试将 Xpath 2.0 函数添加到 PHP ext/xsl,实际上:github.com/ThomasWeinert/XSLT-Functions/blob/master/examples/…
    【解决方案2】:

    这可以使用 XSLT 2.0 中的 xsl:analyze-string 指令方便地处理。例如你可以定义规则:

    <xsl:template match="speak">
      <xsl:analyze-string select="." regex="\d+">
        <xsl:matching-substring>
          <say-as interpret-as="characters">
            <xsl:value-of select="."/>
          </say-as>
        </xsl:matching-substring>
      </xsl:analyze-string>
    </xsl:template>
    

    【讨论】:

      【解决方案3】:

      你可以像这样使用preg_replace

      $str = '<speak>The test number is 123456789, and some further block of text.</speak>';
      echo preg_replace('/(\d+)/','<say-as interpret-as="characters">$1</say-as>',$str);
      

      输出将是:

      <speak>The test number is <say-as interpret-as="characters">123456789</say-as>, and some further block of text.</speak>
      

      【讨论】:

      • 我希望有一个更优雅的解决方案,我可以使用某种类型的 xml 生成器来创建标签和属性,否则我必须创建一大堆额外的逻辑来手动处理。以上只是一个例子,实际的替换规则存储在数据库中,可能有不同的属性。
      • 如果你把输入改成$str = '&lt;speak id="12"&gt;The test...,那么输出也会替换id属性——&lt;speak id="&lt;say-as interpret-as="characters"&gt;12&lt;/say-as&gt;"&gt;The test...
      • @NigelRen 正确形成正则表达式而不是让替换变得流氓取决于正则表达式。其次,实际上可以在最后添加 标签,这是我目前的做法,因此它们不会出现在正在处理的原始文本中。
      • @Nicolas,这只是解决方案可能出错的一个例子。说它适用于一个示例是可以的,但是在 6 个月的时间里,其他开发人员开始使用它,他们可能不会意识到其中的陷阱。您还必须记住,这些答案不仅供您个人使用,而且其他人可能会在自己的情况下使用。
      【解决方案4】:

      我最终以简单的方式完成了它,因为我不需要处理嵌套节点和其他 XML 特定的东西。所以只是做了一个简单的方法来将标签创建为字符串。已经足够好了。

      protected function createTag($name, $attributes = [], $content = null)
          {
              $openingTag = '<' . $name;
      
              if ($attributes) {
                  foreach ($attributes as $attribute => $value) {
                      $openingTag .= sprintf(' %s="%s"', $attribute, $value);
                  }
              }
      
              $openingTag .= '>';
      
              $closingTag = '</' . $name . '>';
      
              $content = $content ?: '$1';
      
              return $openingTag . $content . $closingTag;
          }
      
      $tag = $this->createTag($tagName, $attributes);
      
      $text = preg_replace($regex, $tag, $text);
      
      

      【讨论】:

        猜你喜欢
        • 2011-04-19
        • 1970-01-01
        • 2012-08-21
        • 1970-01-01
        • 2017-07-23
        • 2016-12-06
        • 1970-01-01
        • 2013-07-02
        • 1970-01-01
        相关资源
        最近更新 更多