XDocument 保存删除节点前缀

clo*_*seq 1 c# xml linq-to-xml

我有一个 XML 文档(自产),其结构如下:

<?xml version="1.0" encoding="utf-8"?>
    <wf:wf version="1.0a" xmlns:wf="http://example.com/workflow" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://example.com/workflow">
  <wf:assemblies />
  <wf:dataDefinitions />
  <wf:processes />
  <wf:workflows>
    <wf:workflow id="g08615517-cdfd-4091-a053-217a965f7118">
      <wf:arguments />
      <wf:variables>
        <wf:variable id="g39ffecc9-f570-41c1-9ee0-b9358d63da3c" parameterType="Hidden">
          <wf:name>UIPTaskId</wf:name>
          <wf:dataDefinitionId>gc8f3715c-4a82-42d2-916c-51515083e7e5</wf:dataDefinitionId>
        </wf:variable>
        <wf:variable id="g46663a0c-7e60-4bd2-80df-16cd544087ad" parameterType="Hidden">
          <wf:name>UIPTaskName</wf:name>
          <wf:dataDefinitionId>g359FC555-9CC7-47D4-8ED3-EF973E7D74D7</wf:dataDefinitionId>
          <wf:value>Responsible Individual</wf:value>
        </wf:variable>
        <wf:variable id="gb32914d5-6761-4e82-b571-c8944a796fd9" parameterType="Hidden">
          <wf:name>Search?</wf:name>
          <wf:dataDefinitionId>g57201da8-62b4-46f2-9329-c71d86f39ffc</wf:dataDefinitionId>
          <wf:value>True</wf:value>
        </wf:variable>
    </wf:variables>
</wf:workflow>
</wf:workflows>
</wf:wf>
Run Code Online (Sandbox Code Playgroud)

我有一个实用程序来清理 XML 文档,并使用 XDocument 加载文件,然后循环遍历某些节点并替换值。完成后,我调用 Save 方法将文件保存在新位置,经过进一步检查,Save 方法将删除每个节点上的 wf 前缀。我怎样才能保存这个?难道我做错了什么?这是我的代码示例:

string wf = "wf";
string wkfl = "C:\\MyFiles\\Temp\\myfile1.rrr";

XDocument xdoc = XDocument.Load(wkfl);
XElement variables= xdoc.Descendents(wf + "variables").Single();

foreach(XElement variable in variables.Elements(wf + "variable"))
{
    XElement name = variable.Element(wf + "name");
    name.Value = name.Value + "_MODIFIED";  
}

xdoc.Save(wkfl.Replace("\\Temp\\", "\\Modified\\"));
Run Code Online (Sandbox Code Playgroud)

Save 方法生成以下 XML:

<?xml version="1.0" encoding="utf-8"?>
        <wf version="1.0a" xmlns:wf="http://example.com/workflow" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://example.com/workflow">
      <assemblies />
      <dataDefinitions />
      <processes />
      <workflows>
        <workflow id="g08615517-cdfd-4091-a053-217a965f7118">
          <arguments />
          <variables>
            <variable id="g39ffecc9-f570-41c1-9ee0-b9358d63da3c" parameterType="Hidden">
              <name>UIPTaskId</name>
              <dataDefinitionId>gc8f3715c-4a82-42d2-916c-51515083e7e5</dataDefinitionId>
            </variable>
            <variable id="g46663a0c-7e60-4bd2-80df-16cd544087ad" parameterType="Hidden">
              <name>UIPTaskName</name>
              <dataDefinitionId>g359FC555-9CC7-47D4-8ED3-EF973E7D74D7</dataDefinitionId>
              <value>Responsible Individual</value>
            </variable>
            <variable id="gb32914d5-6761-4e82-b571-c8944a796fd9" parameterType="Hidden">
              <name>Search?</name>
              <dataDefinitionId>g57201da8-62b4-46f2-9329-c71d86f39ffc</dataDefinitionId>
              <value>True</value>
            </variable>
        </variables>
    </workflow>
    </workflows>
    </wf>
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 6

这种行为可以简单地通过加载您的 XML 并重新编写它而不进行任何编辑来重现。正在做:

        var xdoc = XDocument.Parse(xml);
        Debug.WriteLine(xdoc.ToXml());
Run Code Online (Sandbox Code Playgroud)

产生输出:

<wf version="1.0a" xmlns:wf="http://example.com/workflow" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://example.com/workflow">
  <assemblies />
  <dataDefinitions />
  <processes />
  <workflows>
      ...
Run Code Online (Sandbox Code Playgroud)

使用辅助方法:

public static class XmlSerializationHelper
{
    public static string ToXml(this XDocument xDoc)
    {
        using (TextWriter writer = new StringWriter())
        {
            xDoc.Save(writer);
            return writer.ToString();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

  1. 您有两个具有相同值的命名空间,默认命名空间和带有前缀的命名空间wf

    xmlns="http://example.com/workflow"
    xmlns:wf="http://example.com/workflow"
    
    Run Code Online (Sandbox Code Playgroud)
  2. 因此,前缀与元素和所有子元素完全没有前缀的wf:含义完全相同wf

  3. 因此,当将自身写回 XML 时,XElement可以有效地使用前缀wf:,或者根本不使用前缀,而不会改变输出 XML 的语义。

  4. 那么如何XElement在多个有效前缀之间进行选择呢? 事实证明,从for的参考源XElement,命名空间/前缀属性对在写入时按添加顺序推送到下推堆栈,然后从堆栈的顶部到底部检查与元素命名空间的匹配- 有效以与添加属性相反的顺序进行匹配。

  5. 因此,您XElements将获得两个可能的有效前缀中的第二个 - 即没有前缀。

总而言之,带前缀的 XML 和不带前缀的 XML 在语义上是相同的。没有合适的 XML 解析器应该关心差异。

然而,如果由于某种原因您正在使用的某些代码假定wf:前缀而不是检查实际的名称空间名称(尽管它不应该),您可以通过将默认名称空间重新排序到开头来强制使用该前缀写出您的 XML根文档属性列表:

    public static void ReorderDefaultNamespaceToBeginning(XElement xElement)
    {
        var attrArray = xElement.Attributes().ToArray();

        int defaultIndex = -1;
        for (int i = 0; i < attrArray.Length && defaultIndex == -1; i++)
        {
            var attr = attrArray[i];
            if (attr.Name == XName.Get("xmlns", string.Empty))
                defaultIndex = i;
        }

        if (defaultIndex < 0)
            return; // No default namespace

        int firstIndex = -1;
        for (int i = 0; i < attrArray.Length && firstIndex == -1; i++)
        {
            if (i == defaultIndex)
                continue;
            var attr = attrArray[i];
            if (attr.Name.NamespaceName == "http://www.w3.org/2000/xmlns/"
                && attr.Value == attrArray[defaultIndex].Value)
                firstIndex = i;
        }

        if (defaultIndex != -1 && firstIndex != -1 && defaultIndex > firstIndex)
        {
            foreach (var attr in attrArray)
                attr.Remove();
            attrArray.Swap(defaultIndex, firstIndex);
            foreach (var attr in attrArray)
                xElement.Add(attr);
        }
    }

public static class ListHelper
{
    public static void Swap<T>(this T[] list, int i, int j)
    {
        if (i != j)
        {
            T temp = list[i];
            list[i] = list[j];
            list[j] = temp;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(这利用了未公开的事实,即以与出现的相反顺序检查名称空间前缀。)一旦您这样做,wf:前缀将返回。

  • 从原始文档中删除 `xmlns=...` 属性也解决了“问题”。 (3认同)