使用 XSLT 以字符串替换的形式将编辑应用到 HTML 文档
Applying redactions in the form of string substitutions to HTML documents using XSLT
我有大量 HTML(可能还有其他 xml)文档需要编辑。
密文通常采用 "John Doe" -> “[Person A]”的形式。要编辑的文本可能在 headers 或段落中,但几乎总是在段落中。
确实是简单的字符串替换。不是很复杂的东西。
但是,我确实想保留文档结构,并且我不想重新发明任何轮子。文档文本中的字符串替换可能会完成这项工作,但也可能会破坏文档结构,因此这将是最后的选择。
现在我已经盯着 XSLT 看了一个小时,并试图强迫 "str:replace" 执行我的命令。我不会让您看到我无效的尝试,但我会问这个问题:是否有一种简单且已知的方法来使用 XSLT 应用我的编辑,您可以 post 这里吗?
提前致谢。
更新: 应 Martin Honnen 的要求,我正在添加我的输入文件,以及我用来获取最新错误消息的命令。从这里可以明显看出,当涉及到 XSLT 时,我是一个完整的 n00b :-)
.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=""/>
$
第一个问题是找到真正支持字符串替换的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">
指令实现什么。
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>
XSLT 1.0 中没有对同一个字符串执行多次替换的简单方法。您需要使用递归命名模板,一次执行一个替换操作,然后移动到当前查找字符串的下一个实例,或者 - 当不存在下一个实例时 - 移动到下一个 find/replace 对。
考虑以下示例:
输入
<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 & 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 & 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="."/>
我有大量 HTML(可能还有其他 xml)文档需要编辑。
密文通常采用 "John Doe" -> “[Person A]”的形式。要编辑的文本可能在 headers 或段落中,但几乎总是在段落中。
确实是简单的字符串替换。不是很复杂的东西。
但是,我确实想保留文档结构,并且我不想重新发明任何轮子。文档文本中的字符串替换可能会完成这项工作,但也可能会破坏文档结构,因此这将是最后的选择。
现在我已经盯着 XSLT 看了一个小时,并试图强迫 "str:replace" 执行我的命令。我不会让您看到我无效的尝试,但我会问这个问题:是否有一种简单且已知的方法来使用 XSLT 应用我的编辑,您可以 post 这里吗?
提前致谢。
更新: 应 Martin Honnen 的要求,我正在添加我的输入文件,以及我用来获取最新错误消息的命令。从这里可以明显看出,当涉及到 XSLT 时,我是一个完整的 n00b :-)
.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=""/>
$
第一个问题是找到真正支持字符串替换的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">
指令实现什么。
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>
XSLT 1.0 中没有对同一个字符串执行多次替换的简单方法。您需要使用递归命名模板,一次执行一个替换操作,然后移动到当前查找字符串的下一个实例,或者 - 当不存在下一个实例时 - 移动到下一个 find/replace 对。
考虑以下示例:
输入
<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 & 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 & 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="."/>