转换XML,使其所有元素和属性有效地变成“minOccurs 1”

本文关键字:有效地 minOccurs 属性 XML 元素 转换 | 更新日期: 2023-09-27 18:10:19

我想转换一个复杂的XML元素,使其结构变得"规则"——该元素中任何位置出现的所有子元素和属性都将出现在其100%的节点中。

可能更容易表达我的意思…

样本输入:

<product>
    <size name="S"/>            
    <size>
        <stock>10</stock>
    </size>
</product>
所需输出:

<product>
    <size name="S">    
        <stock/>
    </size>
    <size name="">
        <stock>10</stock>
    </size>
</product>

事件:

第一个size元素提供了一个空的子元素stock(因为第二个size元素有一个)。

属性/size@name与一个空值被添加到第二个size子元素(因为第一个size元素有一个)。

先决条件:

  • 处理的XML不太可能很大(使用LINQ没有问题,将所有XML缓存到内存中等等)

  • 我事先不知道它的XML模式

转换XML,使其所有元素和属性有效地变成“minOccurs 1”

下面的代码应该符合您的期望。

使用test.xml文件作为输入

 <product xmlns="http://www.example.com/schemas/v0.1">
    <size name="S"/>
    <size>
      <stock>
        <a.el a.att="a.value"/>
      </stock>
    </size>
    <size>
      <stock>
        10
        <b.el b.att="b.value"/>
      </stock>
    </size>
    <size size.att="size.value" name="e">
      <stock>
        12
        <b.el b.att2="b.value2"/>
      </stock>
    </size>
  </product>

生成以下有效和规范化的输出

<product xmlns="http://www.example.com/schemas/v0.1">
  <size name="S" size.att="">
    <stock>
      <a.el a.att=""></a.el>
      <b.el b.att="" b.att2=""></b.el>
    </stock>
  </size>
  <size name="" size.att="">
    <stock>
      <a.el a.att="a.value" />
      <b.el b.att="" b.att2=""></b.el>
    </stock>
  </size>
  <size name="" size.att="">
    <stock>
        10
        <b.el b.att="b.value" b.att2="" /><a.el a.att=""></a.el></stock>
  </size>
  <size size.att="size.value" name="e">
    <stock>
        12
        <b.el b.att2="b.value2" b.att="" /><a.el a.att=""></a.el></stock>
  </size>
</product>

using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            XDocument xDoc = XDocument.Load("test.xml");
            XNamespace ns = xDoc.Root.Name.Namespace;
            var mgr = new XmlNamespaceManager(new NameTable());
            mgr.AddNamespace("ns", ns.ToString());
            var elements = xDoc.XPathSelectElements("/ns:product/ns:size", mgr);
            Descriptor desc = Descriptor.InferFrom(elements);
            desc.Normalize(elements);
            Console.Write(xDoc.ToString());
        }
    }
    public class Descriptor
    {
        private readonly IList<XName> _attributeNames = new List<XName>();
        private readonly IDictionary<XName, Descriptor> _elementDescriptors = new Dictionary<XName, Descriptor>();
        public XName Name { get; private set; }
        public IEnumerable<XName> AttributeNames { get { return _attributeNames; } }
        public IEnumerable<KeyValuePair<XName, Descriptor>> ElementDescriptors { get { return _elementDescriptors;  } }
        private void UpdateNameFrom(XElement element)
        {
            if (Name == null)
            {
                Name = element.Name;
                return;
            }
            if (element.Name == Name)
                return;
            throw new InvalidOperationException();
        }
        private void Add(XAttribute att)
        {
            XName name = att.Name;
            if (_attributeNames.Contains(name))
                return;
            _attributeNames.Add(name);
        }
        public static Descriptor InferFrom(IEnumerable<XElement> elements)
        {
            var desc = new Descriptor();
            foreach (var element in elements)
                InferFromInternal(element, desc);
            return desc;
        }
        private static void InferFromInternal(XElement element, Descriptor desc)
        {
            desc.UpdateNameFrom(element);
            foreach (var att in element.Attributes())
                desc.Add(att);
            foreach (var subElement in element.Elements())
                desc.Add(subElement);
        }
        private void Add(XElement subElement)
        {
            Descriptor desc;
            if (_elementDescriptors.ContainsKey(subElement.Name))
                desc = _elementDescriptors[subElement.Name];
            else
            {
                desc = new Descriptor();
                _elementDescriptors.Add(subElement.Name, desc);
            }
            InferFromInternal(subElement, desc);
        }
        public void Normalize(IEnumerable<XElement> elements)
        {
            foreach (var element in elements)
                NormalizeInternal(element);
        }
        private void NormalizeInternal(XElement element)
        {
            if (element.Name != Name)
                throw new InvalidOperationException();
            foreach (var attribute in AttributeNames)
            {
                var att = element.Attribute(attribute);
                if (att != null)
                    continue;
                element.Add(new XAttribute(attribute, string.Empty));
            }
            foreach (var attribute in _elementDescriptors)
            {
                XElement el = element.Element(attribute.Key);
                if (el == null)
                {
                    el = new XElement(attribute.Key, string.Empty);
                    element.Add(el);
                }
                attribute.Value.NormalizeInternal(el);
            }
        }
    }
}