如何将子元素移动到父元素的属性 (XML)

How to Move Child Elements to Attributes of Parent Elements (XML)

我目前有一个 XML 文件,该文件相当大(大约 800MB)。我已经尝试了一些尝试(here 是一种处理压缩的尝试)在当前条件下使用它;然而,他们并没有很成功,因为他们需要相当长的时间。

XML 文件结构类似于下面(生成时间早于我):

<Name>Something</Name>
<Description>Some description.</Description>
<CollectionOfObjects>
    <Object>
        <Name>Name Of Object</Name>
        <Description>Description of object.</Description>
        <AltName>Alternate name</AltName>
        <ContainerName>Container</ContainerName>
        <Required>true</Required>
        <Length>1</Length>
            <Info>
                <Name>Name</Name>
                <File>Filename</File>
                <Size>20</Size>
                <SizeUnit>MB</SizeUnit>
            </Info>
    </Object>
</CollectionOfObjects>

每个object下都有相当大的数据块,其中很多子节点都可以做成parents上的属性:

<CollectionOfObjects Name="Something" Description="Some description.">
    <Object Name="Name Of Object" AltName="Alternate name" Container="Container" Required="true" Length="1" Description="Description of object.">
            <Info Name="Name" File="Filename" Size="20" SizeUnit="MB" />
    </Object>
</CollectionOfObjects>

现在,显然不是每个节点下的所有东西都会成为一个属性;以上只是一个例子。此文件中的数据太多,导致 Notepad 中断,甚至需要 Visual Studio 大约 2 分钟才能打开。如果您尝试搜索该文件,上帝会帮助您,因为它需要一个小时或更长时间。

你可以看出这是多么有问题。我已经对大小差异进行了测试(显然不是使用此文件),而是使用了一个演示文件。我创建了一个文件并将不必要的子节点转换为属性,它使演示文件的大小减少了 53%。我毫不怀疑,对该文件执行相同的工作会将其大小减少 30% 或更多(希望更多)。

既然您了解了原因,那么让我们进入问题;如何将这些子节点移动到属性。该文件是通过 XmlSerializer 生成的,并使用反射根据 类 和可用属性构建节点:

internal class DemoClass {
    [CategoryAttribute("Properties"), DescriptionAttribute("The name of this object.")]
    public string Name { get; set; }
}

internal bool Serialize(DemoClass demo, FileStream fs) {
    XmlSerializer serializer = new XmlSerializer(typeof(DemoClass));
    XmlWriterSettings settings = null;
    XmlWriter writer = null;
    bool result = true;
    try {
        settings = new XmlWriterSettings() {
            Indent = true,
            IndentChars = ("\t"),
            Encoding = Encoding.UTF8,
            NewLineOnAttributes = false,
            NewLineChars = Environment.NewLine,
            NewLineHandling = NewLineHandling.Replace
        };
        writer = XmlWriter.Create(fs, settings);
        serializer.Serialize(writer, demo);
    } catch { result = false; } finally { writer.Close(); }
    return result;
}

据我了解,我可以向其中添加 XmlAttribute 标签,它会写入该文件的所有未来版本,并将该标签作为属性;但是,有人告诉我,为了将数据从旧方式转换为新方式,我可能需要某种我不确定的 "binder"

这里的任何建议都会有所帮助。

注意:我知道可以通过以下方式减少文件大小(减少 28%):

Indent = false,
Encoding = Encoding.UTF8,
NewLineOnAttributes = false,

Update:我目前正尝试简单地在属性上使用 XmlAttribute 标签,但我遇到了错误(这是我所期望的)反序列化时反射失败的地方:

There was an error reflecting type DemoClass.

更新 2:现在在这里工作一个新的角度;我决定复制所有需要的 类,用 XmlAttribute 标签更新它们;然后用旧 类 加载旧文件并用新 类 写入新文件。如果这有效,那么它将是一个很好的解决方法。但是,我确信有一种方法可以在没有这种解决方法的情况下做到这一点。

更新 3更新 2(上) 中的方法无效我期望的方式,我最终遇到了 this 问题。由于这种方法也涉及很多,所以我最终编写了一个自定义转换方法,该方法使用原始序列化来加载 XML,然后使用 System.Xml.Linq 命名空间中的 XDocument,我创建了一个新的XML 手工记录。这最终成为一项耗时的任务,但在漫长的 运行 中整体变化较小。它以预期的方式序列化文件(当然在这里和那里进行一些调整)。既然旧文件已经转换,下一步就是更新旧的序列化。我已经完成了大约 80% 的过程,但仍然在这里和那里遇到一些路障:

The type for XmlAttribute may not be specified for primitive types.

尝试反序列化 enum 值时会发生这种情况。序列化程序似乎认为它是一个 string 值。

这是对我有用的代码。

static void Main()
{
    var element = XElement.Load(@"C:\Users\user\Downloads\CollectionOfObjects.xml");
    ElementsToAttributes(element);
    element.Save(@"C:\Users\user\Downloads\CollectionOfObjects-copy.xml");
}

static void ElementsToAttributes(XElement element)
{
    foreach(var el in element.Elements().ToList())
    {
        if(!el.HasAttributes && !el.HasElements)
        {
            var attribute = new XAttribute(el.Name, el.Value);
            element.Add(attribute);
            el.Remove();
        }
        else
            ElementsToAttributes(el);
    }
} 

CollectionOfObjects.xml中的Xml

<CollectionOfObjects>
  <Name>Something</Name>
  <Description>Some description.</Description>
  <Object>
    <Name>Name Of Object</Name>
    <Description>Description of object.</Description>
    <AltName>Alternate name</AltName>
    <ContainerName>Container</ContainerName>
    <Required>true</Required>
    <Length>1</Length>
    <Info>
      <Name>Name</Name>
      <File>Filename</File>
      <Size>20</Size>
      <SizeUnit>MB</SizeUnit>
    </Info>
  </Object>
</CollectionOfObjects>

CollectionOfObjects中的结果Xml-copy.xml

<?xml version="1.0" encoding="utf-8"?>
<CollectionOfObjects Name="Something" Description="Some description.">
  <Object Name="Name Of Object" Description="Description of object." AltName="Alternate name" ContainerName="Container" Required="true" Length="1">
    <Info Name="Name" File="Filename" Size="20" SizeUnit="MB" />
  </Object>
</CollectionOfObjects>