使用C#正则表达式替换XML元素内容

4
我是一位能够处理日志XML数据的程序员,我希望能替换文档中特定元素(例如密码)的内容。但我不想序列化和解析文档,因为我的代码将处理多种模式。

样例输入文档:

文档1:

   <user>
       <userid>jsmith</userid>
       <password>myPword</password>
    </user>

文档 #2:

<secinfo>
       <ns:username>jsmith</ns:username>
       <ns:password>myPword</ns:password>
 </secinfo>

What I'd like my output to be:

output doc #1:

<user>
       <userid>jsmith</userid>
       <password>XXXXX</password>
 </user>

输出文档 #2:

<secinfo>
       <ns:username>jsmith</ns:username>
       <ns:password>XXXXX</ns:password>
 </secinfo>

由于我要处理的文档可能有各种不同的模式,所以我希望能想出一个好的通用正则表达式解决方案,可以找到包含密码的元素并相应地屏蔽内容。

我能否使用正则表达式和C#来解决这个问题,还是有更有效的方法?


1
当有许多其他好用的工具可以完成你想要做的事情时,我肯定会避免使用正则表达式。 - Robert P
即使正则表达式能够做到这一点(它们不能),拥有各种模式使得使用某种形式的解析器变得更加必要,而不是减少。 - annakata
7个回答

21

使用XSLT是解决这个问题的最佳方法:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 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="//password">
        <xsl:copy>
            <xsl:text>XXXXX</xsl:text>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

只要你正确处理命名空间,这将适用于两种输入。

编辑:关于“正确处理命名空间”的澄清

确保具有ns名称前缀的源文档已定义文档的命名空间,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<secinfo xmlns:ns="urn:foo">
    <ns:username>jsmith</ns:username>
    <ns:password>XXXXX</ns:password>
</secinfo>

9

我建议您使用.NET XmlDocument对象解析内容,并使用XPath查找密码元素,然后更改其innerXML属性。这样做的优点是更加准确(因为XML本身就不是正则的),并且易于理解。


8

从尝试在没有合适解析器的情况下解析和/或修改XML的系统经验来看,让我说一句:别这么做。使用XML解析器(这里有其他简便快捷的方法)。

使用非XML方法解析和/或修改XML流肯定会在未来某个时候带来痛苦。我知道,因为我曾经感受过那种痛苦。

我知道,如果你使用正则表达式解决方案,似乎在运行时更快/编码更简单/更易理解/等等。但你只会让将来的某个人遭受痛苦。


你说得很好,我认为这里提出的其他一些解决方案(XSLT、XPATH或XDocument)将在未来让我省去一些麻烦。 - Millhouse
1
很少有绝对的规则没有例外。"绝不要使用字符串操作工具来解析或修改XML"就是其中之一。 - Robert Rossney

4

如果您对要匹配的内容了解足够多,您可以使用正则表达式。例如,如果您正在查找任何标签中包含单词“password”的标签,并且没有内部标签,则此正则表达式将起作用:

(<([^>]*?password[^>]*?)>)([^<]*?)(<\/\2>)

您可以使用zowat答案中相同的C#替换语句,但是对于替换字符串,您需要使用"$1XXXXX$4"。


不,你不能(或者不应该)这样做,因为正则表达式不知道层次结构,无法加载和解析命名实体或数字实体,更别提外部文档类型,还会在处理 CDATA 和命名空间时出现问题。请使用 XSLT 替代。如果你这样尝试,只会引发一系列麻烦。 - Abel
问题明确提到了正则表达式,为什么每个人都试图提供不可行的解决方案呢? - SPDenver

1
XSLT存在的主要原因是能够转换XML结构,这意味着XSLT是一种样式表类型,可用于更改元素顺序或更改元素内容。因此,在这种典型情况下,强烈建议使用XSLT而不是解析,正如安德鲁·哈尔在先前的帖子中所说。

1

正则表达式不是解决这个问题的正确方法,我曾经见过它在最不经意的时候出现严重错误。

XDocument才是更有趣的方法:

XDocument doc = XDocument.Parse(@"
            <user>
                <userid>jsmith</userid>
                <password>password</password>
            </user>");

doc.Element("user").Element("password").Value = "XXXX";

// Temp namespace just for the purposes of the example -
XDocument doc2 = XDocument.Parse(@"
            <secinfo xmlns:ns='http://tempuru.org/users'>
                <ns:userid>jsmith</ns:userid>
                <ns:password>password</ns:password>
            </secinfo>");

doc2.Element("secinfo").Element("{http://tempuru.org/users}password").Value = "XXXXX";

1

当我使用XMLDocument时,这就是我的成果。虽然可能不像XSLT那么出色,但应该足以处理各种文档:

            //input is a String with some valid XML
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(input);
            XmlNodeList nodeList = doc.SelectNodes("//*");

            foreach (XmlNode node in nodeList)
            {
                if (node.Name.ToUpper().Contains("PASSWORD"))
                {
                    node.InnerText = "XXXX";
                }
                else if (node.Attributes.Count > 0)
                {
                    foreach (XmlAttribute a in node.Attributes)
                    {
                        if (a.LocalName.ToUpper().Contains("PASSWORD"))
                        {
                            a.InnerText = "XXXXX";
                        }
                    }
                }    
            }

我认为你想要在元素和属性中都使用LocalName。此外,如果将其作为递归函数来处理XML树,则不必首先构建文档中所有元素的列表。 - Robert Rossney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接