结合XML节点

时间:2015-03-09 15:56:47

标签: c# xml data-structures

我正在寻找一些帮助,看看如何组合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本身,因为它只是我程序开头的一次性加载。我将添加一些类或结构来表示这些数据。

1 个答案:

答案 0 :(得分:0)

您尝试设置的复杂数据结构不是简化XML的最佳方法。最终,尝试读取您的数据文件需要花费一些精力,而保存它可能会非常烦人。

要考虑的一些要点:

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

更新原始实施,请在下方查看更新

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

我现在看到的结构很像:

产品 - &gt;有0个或更多问题

问题 - &gt;正好有一个类别

分类

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

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

它可以简化读取节点,使其更具可读性(从人的角度来看),长期可维护性更高(面对它,数据结构将保持原始设计的次数?),它会可以将类别与产品问题分开。

我明确地从每个单独的产品中删除了空的Category 2选项,因为没有必要使用这种结构(这是重要的问题,imho)

下面的

是一个实现,用于了解实际到目前为止读取原始xml需要做多少工作

<强> ORIGINAL

我检查过创建一个优雅的读者设计,但这最终只能帮助你到目前为止,并且非常依赖于你在这里发布的简化数据结构与实际要求的符合程度。

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

[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)。问题类目前是非常基本的,但我想在实际实现中会有所不同。

要读取原始数据集,可以使用标准的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 ***;)