XDocument 保存删除节点前缀

XDocument Save Removing Node Prefixes

我有一个 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>

我有一个实用程序可以清理 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\"));

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>

只需加载您的 XML 并在不进行任何编辑的情况下再次写入,即可重现此行为。正在做:

        var xdoc = XDocument.Parse(xml);
        Debug.WriteLine(xdoc.ToXml());

产生输出:

<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>
      ...

使用辅助方法:

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

为什么会这样?

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

    的命名空间
    xmlns="http://example.com/workflow"
    xmlns:wf="http://example.com/workflow"
    
  2. 因此前缀 wf: 的意思 wf 元素和所有子元素完全没有前缀相同 元素.

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

  4. 那么XElement如何在多个有效前缀之间进行选择呢?堆栈的 As it turns out, from the reference source for XElement, the namespace/prefix attribute pairs are pushed onto a push-down stack in order of addition while writing, then checked for matches against the element namespace from top to bottom——有效地以添加属性的相反顺序进行匹配。

  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;
        }
    }
}

(这利用了 未记录的 事实,即名称空间前缀按出现的相反顺序进行检查。)一旦你这样做, wf: 前缀将 return.