65.9K
CodeProject 正在变化。 阅读更多。
Home

动态 XML API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (16投票s)

2014年4月23日

CPOL

6分钟阅读

viewsIcon

36069

downloadIcon

1173

一个 API,统治所有(所有 XML 文件格式)

引言

目标是提供一个可以读取所有 XML 文件格式的 API。目标是能够像访问任何 C# 对象模型一样访问 XML 中的数据,其中 XML 元素将是一个对象,它的属性和子元素将是该对象的属性,例如:Object.Property.Subproperty.Name。该 API 只包含一个类 DynamicXmlNode,并且它基于 Microsoft 的 动态字典示例。我将使用 W3Schools 上常用的 图书商店示例 XML 格式来描述 API 的工作原理。XML 使用 XDocument API 进行解析。

背景

这是我第一次在这里发布内容,所以请对我温柔点 :)。我的大部分工作都涉及解析 XML 文档,我几乎总是会自己构建一个自定义 API 来读取这些文件。最近我一直在玩 C# 动态特性,并产生了创建动态 XML API 的想法。我做到了,现在它在这里。

在将本文发布到这里之前,我曾四处搜索,看看网上已有哪些内容。我没有找到与我所创建的类似的东西(特别是数组的处理方式),所以我认为它值得分享。

处理重复元素(数组)

动态 API 可以通过检测具有相同名称的同级元素并将它们分组到集合中来处理重复的元素,但如果 XML 实例文档中只有一个元素,它无法判断该元素是否应添加到集合中。

解决方案

让用户在运行时告知。API 的用户知道他们正在解析的文件格式,以及哪些元素属于集合。因此,通过引入用于访问数组的属性命名约定,我们可以让用户告诉我们哪些元素属于集合。我选择的约定是 __Array(双下划线)。任何请求的属性名以 __Array 结尾,API 都将始终返回一个基于 __Array 前的属性名的数组,即使不存在这样的属性。这非常方便,因为您无需在迭代之前检查该属性是否为 null。

处理同时包含值和复杂元素的元素

当一个元素具有值或文本,同时还包含属性或子元素时,会有一个复杂情况。动态 API 将为这些元素创建 DynamicXmlNode 对象,因此访问相应的属性将不会返回字符串,而是返回 DynamicXmlNode。请看下面的示例:

<title lang="en">The Selfish Gene</title>

该元素同时包含一个属性和一个文本。如果您想访问 lang 值,只需执行 Book_Array[0].Title.Lang,但如果您只想访问文本,则无法做到,因为 Book_Array[0].Title 返回的是 DynamicXmlNode

解决方案

我为此提供了三种解决方案,它们都将返回 title 元素的值:

  1. 隐式字符串运算符:DynamicXmlNode 包含一个隐式运算符到字符串,因此将 DynamicXmlNode 对象赋值给字符串将始终返回被包装元素的值,例如:
    string title = bookstore.Book__Array[0].Title;
  2. Book_Array[0].Title.ToString() 也将返回被包装元素的值。
  3. Book_Array[0].Title._ 也将返回被包装元素的值。

下划线约定

  • __Array:如果调用者想要一个数组,他们就能得到一个数组!用户只需在属性名后附加 __Array(不区分大小写,双下划线),例如 BookStore.Book__Array
  • __PropertyName:在属性名前包含双下划线将返回用于生成该属性的 XML(属性或元素),例如 Book_Array[0].__Title
  • __:双下划线将返回被 DynamicXmlNode 包裹的 XML 元素,例如 Book_Array[0].__
  • _:单个下划线将始终返回元素的值,例如 Book__Array[0].Title._

工作原理

所有属性名均不区分大小写。

所有请求的不存在的属性都将返回 null,但如果请求的属性名以 __Array 结尾(如上所述),则例外。

包含子元素或属性的任何元素都将被包裹在一个 DynamicXmlNode 对象中。这意味着您可以轻松地像这样深入到文件中 BookStore.Book_Array[0].Title.Lang

使用代码

我将使用 W3Schools 常用的图书商店示例来演示如何使用该 API。这是图书商店的 xsd 架构。

book store schema

正如您所见,架构中有三个地方可以出现数组,即 books、book authors 和 CDs。这是一个图书商店实例文档(xml 文件)的示例片段。

<bookstore>
    <book category="COOKING">
        <title lang="en">Everyday Italian</title>
        <author>Giada De Laurentiis</author>
        <year>2005</year>
        <price>30.00</price>
    </book>
    <book category="POPULAR SCIENCE">
        <title lang="en">The Selfish Gene</title>
        <author>Richard Dawkins</author>
        <year>1976</year>
        <price>15.00</price>
    </book>
    <book category="CHILDREN">
        <title lang="en">Harry Potter</title>
        <author>J K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
    <book category="WEB">
        <title lang="en">XQuery Kick Start</title>
        <author>James McGovern</author>
        <author>Per Bothner</author>
        <author>Kurt Cagle</author>
        <author>James Linn</author>
        <author>Vaidyanathan Nagarajan</author>
        <year>2003</year>
        <price>49.99</price>
    </book>
    <book category="WEB">
        <title lang="en">Learning XML</title>
        <author>Erik T. Ray</author>
        <year>2003</year>
        <price>39.95</price>
    </book>
<bookstore>

假设我们要将第一本书的标题写入控制台。

dynamic bookstore = DynamicXmlNode.Load(File);
Console.WriteLine(bookstore.Book_Array[0].Title);

控制台输出:Everyday Italian

请注意,Console.WriteLine 调用 Title 属性的 ToString() 方法,因此上面描述的复杂元素问题在这里被掩盖了。

现在,假设我们要将理查德·道金斯(Richard Dawkins)写的第一本书的标题写入控制台。

dynamic bookstore = DynamicXmlNode.Load(File);
Console.WriteLine
(
   (from book in bookstore.Book_Array as IEnumerable<dynamic>
    where book.Author == "Richard Dawkins" 
    select book).First().Title
);

控制台输出:Selfish Gene

现在,假设我们要找到第一本有多位作者的书并将其标题写入控制台。

Console.WriteLine
(
     (from book in bookstore.Book_Array as IEnumerable<dynamic> 
     where book.Author_Array.Count > 1 
     select book).First().Title
);

控制台输出:XQuery Kick Start

关注点

RunTimeBinderExceptions

在使用动态时,DLR(动态语言运行时)首先尝试通过查找动态对象上静态定义的成员来解析成员调用。当找不到任何成员时,它会在调用动态对象的 TryGetMemberTrySetMember 方法之前抛出 RunTimeBinderException。这些只是一次机会的异常,无需担心,但当调试器配置为在异常处中断时,它们可能会让调试变得很麻烦。一个简单的解决方案是将 RunTimeBinderException 添加到中断异常列表中,然后取消选中它。

book store schema

元素或属性命名与所用约定冲突

我使用的下划线约定可能会与元素名冲突,但这种情况非常罕见。例如,为了让 __Array 约定造成冲突,所遵循的 XML 架构必须使用名称类似于 xx__Array 的同级元素。这些元素更有可能具有父子关系,即 x__Array/x。此外,我特意选择使用双下划线以避免潜在冲突。

我本可以利用 XML 元素命名限制,即元素名不能以数字或“xml”开头,但这在 API 使用中会显得很难看。

包含点的元素或属性名

XML 元素或属性的名称中可以包含点字符('.'),这是完全有效的。然而,C# 中的点字符是指定类型或命名空间成员的特殊运算符。由于我们将元素/属性名用作 C# 属性名,因此需要将这些点字符替换为 C# 属性友好的字符。您永远也猜不到我选择了哪个字符来替换它们。下划线!这是一个示例场景:

<element some.attribute="12">

注意:在下面的代码中,元素名中的点已被替换为下划线。

Element.Some_Attribute

处理命名空间

API 使用元素的本地名称(即不带命名空间前缀),因此支持包含命名空间的文件,但如果命名空间被用来唯一限定同级元素,则有可能会覆盖元素。

未来功能构想

惰性实例化

目前,整个 XML 文件都已加载到内存中。可能需要一个惰性版本的 API。

写入功能

动态读取是一回事,但动态写入是一个更复杂的问题。当调用 setter 时,需要知道何时添加属性或元素,处理命名空间,以及满足元素序列约束等问题。这些都是您需要处理的事项。

© . All rights reserved.