【问题标题】:Combining XML Nodes组合 XML 节点
【发布时间】:2015-03-09 15:56:46
【问题描述】:

我正在寻求一些帮助,以了解如何组合来自 XML 文件的两个单独部分的节点。这个想法是有一个部分包含默认信息,另一个部分可以添加更多信息或删除一些默认信息。这是它的外观示例。

<data>
    <products>
        <product name="Product A" />
        <product name="Product B">
            <category name="Category 2">
                <issue name="Special Issue" />
            </category>
        </product>
        <product name="Product C">
            <category name="Category 1" remove="true" />
            <category name="Special Category">
                <issue name="Secret Issue" />
            </category>
        </product>
        <product name="Product D">
            <category name="Category 1">
                <issue name="Standard Issue" remove="true"/>
                <issue name="Complex Issue">
            </category>
        </product>
    </products>
    <categories>
        <category name="Category 1">
            <issue name="Standard Issue" />
            <issue name="Advanced Issue" />
        </category>
        <category name="Category 2" />
    </categories>
</data>

我的想法是我可以将产品与类别/问题分开定义,因为这些信息有很多重叠之处。但是,某些产品需要有稍微不同的类别或问题。下面是它之后的样子。

Product A
    Category 1
        Standard Issue
        Advanced Issue
    Category 2
Product B
    Category 1
        Standard Issue
        Advanced Issue
    Category 2
        Special Issue
Product C
    Category 2
    Special Category
        Secret Issue
Product D
    Category 1
        Advanced Issue
        Complex Issue
    Category 2

我可以使用一堆 for 循环来迭代信息,但是,我正在尝试看看是否有更优雅的方法来做到这一点。

PS - 现在只输出应有的信息就可以了。我不想编辑 XML 本身,因为它只是我程序开始时的一次性加载。我将添加一些类或结构来表示这些数据。

【问题讨论】:

    标签: c# xml data-structures


    【解决方案1】:

    您尝试建立的复杂数据结构并不是简化 XML 的最佳方式。最终,尝试读取您的数据文件需要付出相当大的努力,而保存它可能会很烦人。

    需要考虑的几点:

    • 如何保存数据
    • 如果你的一半产品突然需要一个新的标准问题或一个新的类别会发生什么,你当时决定将其添加到一半的节点,还是将其添加到默认数组并添加删除指令到您的 product.category 或 product.category.issues 节点?
    • remove 指令真的是您需要/想要添加的要求吗?
    • 如果您想以局外人的身份“阅读”数据库,您会自己了解其结构吗(我觉得很难,而且这只是 4 个产品)

    更新对于原始实现,请查看下面的更新

    我越想这个问题,我会说你应该从顶部重新检查你的数据结构。

    我现在看到的结构很像:

    产品 -> 有 0 个或多个问题

    问题 -> 恰好有 1 个类别

    类别

    因此,在我看来,表示数据的更简单方法是从产品中删除类别标签,并直接在产品下添加问题。然后,这些类别仍可能位于包含潜在额外信息的单独节点列表中,例如:

    <?xml version="1.0" encoding="utf-8" ?>
    <data>
      <products>
        <product name="Product A">
          <issue name="Standard Issue" category="Category 1" />
          <issue name="Advanced Issue" category="Category 1" />
        </product>
        <product name="Product B">
          <issue name="Standard Issue" category="Category 1" />
          <issue name="Advanced Issue" category="Category 1" />
          <issue name="Special Issue" category="Category 2" />
        </product>
        <product name="Product C">
          <issue name="Secret Issue" category="Special Category" />
        </product>
        <product name="Product D">
          <issue name="Advanced Issue" category="Category 1" />
          <issue name="Complex Issue" category="Category 1" />
        </product>
      </products>
      <categories>
        <category name="Category 1" />
        <category name="Category 2" />
        <category name="Special category" />
      </categories>
    </data>
    

    它将简化节点的读取,使其更具可读性(从人类的角度来看也是如此),更易于长期维护(面对现实,数据结构将保持原设计多少次?),并且它会可以将类别与产品的问题分开。

    我从每个产品中明确删除了空的类别 2 选项,因为这种结构不需要它们(这是重要的问题,恕我直言)

    下面是一个实现,让您了解实际阅读原始 xml 需要付出多少努力

    原创

    我检查以创建一个优雅的阅读器设计,但这最终只会对您有所帮助,并且很大程度上取决于您在此处发布的简化数据结构与实际要求的符合程度。

    作为基础,我为数据创建了一些类,我使用抽象类来提供 Name & Remove 属性,以便更容易“概括”读者,但需要一些特殊的实现

    [XmlRoot("data")]
    public class Data
    {
        [XmlArray("products")]
        [XmlArrayItem("product")]
        public Product[] Products { get; set; }
    
        [XmlArray("categories")]
        [XmlArrayItem("category")]
        public Category[] Categories { get; set; }
    }
    
    public abstract class AbstractNamedNode
    {
        [XmlAttribute("name")]
        public string Name { get; set; }
    
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
            if (obj is AbstractNamedNode)
            {
                return string.Equals(((AbstractNamedNode)obj).Name, this.Name);
            }
            return base.Equals(obj);
        }
    
        public override int GetHashCode()
        {
            if (string.IsNullOrEmpty(Name))
            {
                return base.GetHashCode();
            }
            return Name.GetHashCode();
        }
    
        public override string ToString()
        {
            return string.Format("{0}", Name);
        }
    
        public virtual T CloneBasic<T>()
            where T: AbstractNamedNode, new()
        {
            T result = new T();
            result.Name = this.Name;
            return result;
        }
    }
    
    public abstract class AbstractNamedRemovableNode : AbstractNamedNode
    {
        [XmlAttribute("remove")]
        public bool Remove { get; set; }
    
        public override T CloneBasic<T>()
        {
            var result = base.CloneBasic<T>() as AbstractNamedRemovableNode;
            result.Remove = this.Remove;
            return result as T;
        }
    }
    
    public class Product : AbstractNamedNode
    {
        [XmlElement("category")]
        public Category[] Categories { get; set; }
    }
    
    public class Category : AbstractNamedRemovableNode
    {
        [XmlElement("issue")]
        public Issue[] Issues { get; set; }
    }
    
    public class Issue : AbstractNamedRemovableNode
    {
        // intended blank
    }
    

    这提供了读取 XML 原始格式的结构(使用 XmlSerializer)。 Issue 类目前非常基础,但我想在实际实现中会有所不同。

    要读取原始数据集,您可以使用标准的 XmlSerializer 方式:

    Data dataFromXml = null;
    string path = Path.Combine(Environment.CurrentDirectory, "datafile.xml");
    using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
    {
        XmlSerializer xs = new XmlSerializer(typeof(Data));
        dataFromXml = xs.Deserialize(fs) as Data;
    }
    

    此时(并且在尝试解析数据时未发生异常),dataFromXML 将包含所有已定义的产品(及其各自的类别和问题)和类别节点。

    要将其读入您的最终设置,您必须比较每个类别(并复制它们的信息),然后再次将您的结果集与您的标准集进行比较(以查看您的产品中哪些未定义的类别仍应添加)。

    对于此实现,此 DataProvider 将从原始 dataFromXML 文件返回一组组合的克隆产品。通过依赖抽象类(具有通用规范),可以稍微压缩所有循环,只是变得更难阅读

    public static class DataProvider
    {
        private static T GetMatchingItem<T, K>(T[] SourceArray, K MatchingNode) 
            where T: AbstractNamedRemovableNode
            where K: AbstractNamedRemovableNode
        {
            if (SourceArray == null || SourceArray.Length == 0 || MatchingNode == null)
            {
                return null;
            }
            var query = from i in SourceArray
                        where !i.Remove && i.Equals(MatchingNode)
                        select i;
    
            return query.SingleOrDefault();
        }
    
        private static T[] CombineArray<T>(T[] sourceArray, T[] baseArray)
            where T : AbstractNamedRemovableNode, new()
        {
            IList<T> results = new List<T>();
    
            if (sourceArray != null)
            {
                foreach (var item in sourceArray)
                {
                    if (item.Remove)
                    {
                        continue;
                    }
                    T copy = default(T);
                    copy = item.CloneBasic<T>();
                    if (copy is Category)
                    {
                        Category category = copy as Category;
                        Category original = item as Category;
                        Category matching = GetMatchingItem(baseArray, item) as Category;
                        if (matching != null)
                        {
                            category.Issues = CombineArray(original.Issues, matching.Issues);
                        }
                        else
                        {
                            category.Issues = CombineArray(original.Issues, null);
                        }
                    }
                    results.Add(copy);
                }
            }
    
            if (baseArray != null)
            {
                foreach (var item in baseArray)
                {
                    if (results.Contains(item))
                    {
                        continue;
                    }
                    if (sourceArray != null && sourceArray.Contains(item))
                    {
                        // the remove option would have worked here
                        continue;
                    }
                    T copy = item as T;
                    if (copy is Category)
                    {
                        Category category = copy as Category;
                        Category original = item as Category;
                        category.Issues = CombineArray(original.Issues, null);
                    }
                    results.Add(copy);
                }
            }
    
            return results.OrderBy((item) => item.Name).ToArray();
        }
    
        public static Product[] GetCombinedProductInfoFromData(Data data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            IList<Product> products = new List<Product>();
    
            if (data.Products != null)
            {
                foreach (var originalProduct in data.Products)
                {
                    Product product = originalProduct.CloneBasic<Product>();
                    if (originalProduct.Categories != null && originalProduct.Categories.Length > 0)
                    {
                        product.Categories = CombineArray(originalProduct.Categories, data.Categories);
                    }
                    else
                    {
                        product.Categories = CombineArray(data.Categories, null);
                    }
                    products.Add(product);
                }
            }
    
            return products.ToArray();
        }
    }
    

    通过使用以下显示程序,它最终可以按规定工作,但构建数据不应该这么“难”,而且它高度依赖于产品的大小和标准节点(类别和问题)算法的速度有多快。

    var productList = DataProvider.GetCombinedProductInfoFromData(dataFromXml);
    foreach (var product in productList)
    {
        Console.WriteLine(product);
        if (product.Categories == null)
        {
            continue;
        }
        foreach (var category in product.Categories)
        {
            Console.WriteLine("\t{0}", category);
    
            if (category.Issues == null)
            {
                continue;
            }
    
            foreach (var issue in category.Issues)
            {
                Console.WriteLine("\t\t{0}", issue);
            }
        }
    }
    

    然而,结果将是一个克隆产品列表,其中克隆的默认类别与指定的类别相结合,类别中的问题也是如此。

    我想指出,唯一的事情是仔细考虑这是否真的是您想要构建数据的方式。特别是“删除”指令是一个 b*** ;)

    【讨论】:

      猜你喜欢
      • 2019-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-07
      • 2015-08-04
      • 1970-01-01
      相关资源
      最近更新 更多