Применение редакций в виде подстановки строк к HTML-документам с использованием XSLT

У меня есть большое количество документов HTML (и, возможно, других XML), которые мне нужно отредактировать.

Редакции обычно имеют вид «Джон Доу» -> «[Человек А]». Редактируемый текст может быть в заголовках или абзацах, но почти всегда будет в абзацах.

Простые замены строк на самом деле. Не очень сложные вещи.

Однако я хочу сохранить структуру документа, и я бы предпочел не изобретать велосипеды. Подстановка строк в тексте документа может помочь, но также может нарушить структуру документа, поэтому это будет последний вариант.

Прямо сейчас я смотрел на XSLT в течение часа и пытался заставить "str:replace" выполнять мои приказы. Я избавлю вас от просмотра моих слабых попыток, которые не сработали, но я спрошу: есть ли простой и известный способ применить мои правки с помощью XSLT, и не могли бы вы опубликовать его здесь?

Заранее спасибо.

Обновление: по просьбе Мартина Хоннена я добавляю свои входные файлы, а также команду, которую использовал для получения последнего сообщения об ошибке. Из этого будет видно, что я полный ноль, когда дело доходит до XSLT :-)

.html файл:


    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
    <html>
      <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>TodaysDate</title>
        <meta name="created" content="2020-11-04T30:45:00"/>
      </head>
      <body>
        <ol start="2">
          <li><p> John Doe on 9. fux 2057 together with Henry
          Fluebottom formed the company Doe &; Fluebottom Widgets
          Inc. </p>
        </ol>
      </body>
    </html>

Файл преобразования XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        >
<xsl:template match="p">
  <xsl:copy>
<xsl:attribute name="matchesPattern">
  <xsl:copy-of select='str:replace("John Doe", ".*",  "[Person A]")'/>
</xsl:attribute>
  <xsl:copy-of select='str:replace("Henry Fluebottom", ".*",  "[Person B]")'/>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

Команда и вывод:

$  xsltproc -html transform.xsl example.html
xmlXPathCompOpEval: function replace bound to undefined prefix str
xmlXPathCompiledEval: 2 objects left on the stack.
<?xml version="1.0"?>



    TodaysDate




      <p matchesPattern=""/>  

$ 

person Rmz    schedule 23.03.2020    source источник
comment
Непонятно, почему str:replace (в XSLT 1 с EXSLT) или replace в XSLT 2 и более поздних версиях не выполняет эту работу за вас, если только каждая строка, которую вы хотите заменить, не содержится в одном текстовом узле (например, <p>John Doe is sick</p>), а в смешанном содержимое или распределяться по нескольким элементам, например <p><prename>John</prename> <surname>Doe</surname> is sick.</p>). Таким образом, по крайней мере, покажите нам репрезентативную небольшую выборку входных и выходных данных, даже если вы чувствуете, что ваша попытка кодирования не удалась. Но вы могли бы также показать один и рассказать нам, как именно он не удался.   -  person Martin Honnen    schedule 23.03.2020
comment
Не могли бы вы привести минимальный пример ввода и вывода, пожалуйста?   -  person Pierre François    schedule 23.03.2020
comment
Спасибо вам обоим за ваши комментарии. Теперь я добавил минимальный пример ввода/вывода.   -  person Rmz    schedule 23.03.2020


Ответы (3)


xsltproc основан на libxslt и таким образом поддерживает различные функции EXSLT, такие как str:replace, для его использования вам нужно будет объявить пространство имен

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:str="http://exslt.org/strings"
    exclude-result-prefixes="str"
    version="1.0">

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

    <xsl:template match="p//text()">
        <xsl:value-of select="str:replace(., 'John Doe', '[Person A]')"/>
    </xsl:template>

</xsl:stylesheet>
person Martin Honnen    schedule 23.03.2020
comment
libxslt НЕ поддерживает str:replace. - person michael.hor257k; 23.03.2020
comment
У меня есть xsltproc, скомпилированный с libxml 20904, libxslt 10129 и libexslt 817, он поддерживает str:replace, исходный код github.com/GNOME/libxslt/blob/mainline/libexslt/strings.c#L597 также предполагает, что он поддерживается. Поэтому я не уверен, предназначен ли ваш комментарий для различия между libxslt и libexslt или вы говорите об отсутствии поддержки str:replace в более старой версии. - person Martin Honnen; 23.03.2020
comment
Большое спасибо. Это решение имеющейся проблемы. - person Rmz; 23.03.2020

В XSLT 1.0 нет простого способа выполнить несколько замен одной и той же строки. Вам нужно использовать рекурсивный именованный шаблон, выполняя одну операцию замены за раз, а затем переходя к следующему экземпляру текущей строки поиска или, если следующего экземпляра не существует, к следующей паре поиска/замены.

Рассмотрим следующий пример:

Ввод

<html>
    <head>
        <title>John Doe and Henry Fluebottom</title>
    </head>
    <body>
        <p>John Doe is a person. John Doe on 9. fux 2057 together with Henry Fluebottom formed the company Doe &amp; Fluebottom Widgets Inc. Henry Fluebottom is also a person.</p>
    </body>
</html>

XSLT 1.0 (+ функция EXSLT node-set())

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>

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

<xsl:variable name="dictionary">
    <entry find="John Doe" replace="[Person A]"/>
    <entry find="Henry Fluebottom" replace="[Person B]"/>
</xsl:variable>

<xsl:template match="text()">
    <xsl:call-template name="multi-replace">
        <xsl:with-param name="string" select="normalize-space(.)"/>
        <xsl:with-param name="entries" select="exsl:node-set($dictionary)/entry"/>"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="multi-replace">
    <xsl:param name="string"/>
    <xsl:param name="entries"/>
    <xsl:choose>
        <xsl:when test="$entries">
            <xsl:call-template name="multi-replace">
                <xsl:with-param name="string">
                    <xsl:call-template name="replace">
                        <xsl:with-param name="string" select="$string"/>
                        <xsl:with-param name="search-string" select="$entries[1]/@find"/>
                        <xsl:with-param name="replace-string" select="$entries[1]/@replace"/>
                    </xsl:call-template>
                </xsl:with-param>
                <xsl:with-param name="entries" select="$entries[position() > 1]"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$string"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="replace">
    <xsl:param name="string"/>
    <xsl:param name="search-string"/>
    <xsl:param name="replace-string"/>
    <xsl:choose>
        <xsl:when test="contains($string, $search-string)">
            <xsl:value-of select="substring-before($string, $search-string)"/>
            <xsl:value-of select="$replace-string"/>
            <xsl:call-template name="replace">
                <xsl:with-param name="string" select="substring-after($string, $search-string)"/>
                <xsl:with-param name="search-string" select="$search-string"/>
                <xsl:with-param name="replace-string" select="$replace-string"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$string"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Результат

<html>
    <head>
        <title>[Person A] and [Person B]</title>
    </head>
    <body>
        <p>[Person A] is a person. [Person A] on 9. fux 2057 together with [Person B] formed the company Doe &amp; Fluebottom Widgets Inc. [Person B] is also a person.</p>
    </body>
</html>

Как видите, это заменяет все экземпляры строк поиска в любом месте входного документа (кроме атрибутов) при сохранении структуры документа.


Обратите внимание, что ввод в вашем примере на самом деле не содержит строку поиска "Henry Fluebottom". Возможно, вы захотите обойти это, вызвав первый шаблон с помощью:

<xsl:with-param name="string" select="normalize-space(.)"/>

вместо:

<xsl:with-param name="string" select="."/>
person michael.hor257k    schedule 23.03.2020
comment
Большое спасибо. Это решение имеющейся проблемы. - person Rmz; 23.03.2020

Первая проблема заключается в том, чтобы найти XSLT-процессор, который действительно поддерживает замену строк. Функция replace() является стандартной в XSLT 2.0+, но не существует в XSLT 1.0. Некоторые процессоры XSLT 1.0 поддерживают функцию расширения str:replace() в другом пространстве имен, но, по крайней мере, вам нужно добавить объявление пространства имен xmlns:str="http://exslt.org/strings" в вашу таблицу стилей, чтобы найти функцию. Я не знаю, сработает ли это (я не знаю, есть ли способ использовать эту функцию с xsltproc); я бы посоветовал вместо этого использовать процессор XSLT 2.0+.

Следующая проблема заключается в том, как вы вызываете функцию. Как правило, правильный вызов будет

replace(., "John Doe", "[Person A]")

хотя вам придется прыгнуть еще через несколько обручей, чтобы сделать несколько замен в одной и той же строке.

Я понятия не имею, чего вы пытаетесь достичь с помощью инструкции <xsl:attribute name="matchesPattern">.

person Michael Kay    schedule 23.03.2020