XDocument保存删除节点前缀

时间:2015-02-09 22:44:28

标签: 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>

我有一个实用程序来清理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>

1 个答案:

答案 0 :(得分:4)

只需加载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元素和所有子元素都没有前缀。

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

  4. 那么XElement如何在多个有效前缀之间进行选择? As it turns out,从XElement XElements开始,命名空间/前缀属性对在写入时按照添加顺序被推送到reference source,然后检查与元素名称空间的匹配来自堆栈的push-down stack - 有效地按照添加属性的相反顺序进行匹配。

  5. 因此,您的wf:被赋予两个可能的有效前缀中的第二个 - 即没有前缀。

  6. 总而言之,带有前缀的XML和没有前缀的XML在语义上是相同的。没有合适的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; } } } 前缀而不是检查实际的命名空间名称(尽管它不应该),您可以强制将XML写出来该前缀通过将默认命名空间重新排序到根文档属性列表的开头:

    wf:

    (这利用了未记录的这一事实,即以相反的出现顺序检查名称空间前缀。)执行此操作后,{{1}}前缀将返回。