高效生成高质量XML Schema

在进行数据建模时,经常需要生成复杂的模式(schema)。本文概述了如何从样本XML快速原型化出高质量、可维护的XML Schema,以确保在所有平台上生成的派生对象模型都是干净的。

有许多工具可以用来创建和管理模式,但大多数工具使用起来都“不太有趣”。尤其是当使用像XML Schema(XSD)这样的抽象语言时,从复杂的工具的空白屏幕开始编写尤为令人畏惧。

最有益的方法是先创建一个样本(sample),然后使用现有工具生成模式。这些模式生成工具的问题在于它们会嵌套复杂类型... 嵌套复杂类型会导致两个问题:

  • 它很难看/难以维护
  • 生成器会从这种模式构建出非常丑陋的对象

它没有遵循XML Schema的一般行业实践(msdata命名空间)。

示例

使用样本开始了数据建模;在这种情况下,想对童子军松木车比赛数据进行建模(是的,有一个8岁的男孩)。

以下是XML样本:

<Derby> <Racers> <Group Name="Den7"> <Cub Id="1" First="Johny" Last="Racer" Place="1"/> <Cub Id="2" First="Stan" Last="Lee" Place="3"/> </Group> ... </Racers> </Derby>

如果在该XML上运行XSD.exe(包含在.NET SDK中),它会生成如下XSD:

<xs:schema id="Derby" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="Derby" msdata:IsDataSet="true" msdata:UseCurrentLocale="true"> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="Racers"> <xs:complexType> <xs:sequence> <xs:element name="Group" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="Cub" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> ... </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>

注意所有的嵌套... 当然后运行xsd.exe在生成的derby.xsd上时... 它会生成对象,名称如下:

DerbyRacersGroupCub

这太糟糕了!

更好的模式

以下是改进后的模式:

<xs:schema xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="Derby" type="DerbyInfo" /> <xs:complexType name="DerbyInfo"> <xs:sequence> <xs:element name="Racers" type="RacersInfo" /> <xs:element name="Races" type="RacesInfo" /> </xs:sequence> </xs:complexType> ... </xs:schema>

改进Xml2Xsd

为了解决所有这些问题,构建了一个更好/更简单的生成器。

打开一个XDocument用于样本XML。读取所有元素并构建一个XPath字典。使用了字典,但List与Distinct()也可以工作。

从XPath列表中,遍历所有XPath并构建属性和元素,确保引用所有新元素,而不是嵌套。

public static XDocument Generate(XDocument content, string targetNamespace) { xpaths.Clear(); elements.Clear(); recurseElements.Clear(); RecurseAllXPaths(string.Empty, content.Elements().First()); target = XNamespace.Get(targetNamespace); var compTypes = xpaths.Select(k => k.Key) .OrderBy(o => o) .Select(k => ComplexTypeElementFromXPath(k)) .Where(q => null != q).ToArray(); compTypes[0] = compTypes.First().Element(xs + "sequence").Element(xs + "element"); return new XDocument( new XElement(target + "schema", new XAttribute("elementFormDefault", "qualified"), new XAttribute("targetNamespace", targetNamespace), new XAttribute(XNamespace.Xmlns + "xs", "http://www.w3.org/2001/XMLSchema"), compTypes)); }

对于每个元素,检查它是否唯一,寻找重复的元素名称(递归定义的元素),并跟踪它们。

static void RecurseAllXPaths(string xpath, XElement elem) { var missingXpath = !xpaths.ContainsKey(xpath); var lclName = elem.Name.LocalName; var hasLcl = elements.ContainsKey(lclName); if (hasLcl && missingXpath) RecurseElements.Add(lclName); else if (!hasLcl) elements.Add(lclName, true); if (missingXpath) xpaths.Add(xpath, null); elem.Attributes().ToList().ForEach(attr => { var xpath1 = string.Format("{0}/@{1}", xpath, attr.Name); if (!xpaths.ContainsKey(xpath1)) xpaths.Add(xpath1, null); }); elem.Elements().ToList().ForEach(fe => RecurseAllXPaths(string.Format("{0}/{1}", xpath, lclName), fe)); }

现在有了XPath列表,需要为它们生成适当的模式。

private static XElement ComplexTypeElementFromXPath(string xp) { var parts = xp.Split('/'); var last = parts.Last(); var isAttr = last.StartsWith("@"); var parent = ParentElementByXPath(parts); return (isAttr) ? BuildAttributeSchema(xp, last, parent) : BuildElementSchema(xp, last, parent); }

这部分代码负责构建属性模式。

private static XElement BuildAttributeSchema(string k, string last, XElement parent) { var elem0 = new XElement(xs + "attribute", new XAttribute("name", last.TrimStart('@')), new XAttribute("type", "string")); if (null != parent) parent.Add(elem0); xpaths[k] = elem0; return null; }

这部分代码不像BuildAttribute那么简单;必须确保对父节点进行了适当的“类型引用”... 这有点复杂,但效果很好。

private static XElement BuildElementSchema(string k, string last, XElement parent) { XElement seqElem = null; if (null != parent) { seqElem = parent.Element(xs + "sequence"); if (null == seqElem && null != parent) parent.AddFirst(seqElem = new XElement(xs + "sequence")); } else { seqElem = new XElement(xs + "sequence"); } var lastInfo = last + "Info"; var elem0 = new XElement(xs + "element", new XAttribute("name", last), new XAttribute("type", lastInfo)); seqElem.Add(elem0); return xpaths[k] = new XElement(xs + "complexType", new XAttribute("name", lastInfo)); }

使用代码

下载示例项目,在VS2010或Express中构建,从调试解决方案中按F5执行,打开bin/Debug中的Derby.Xsd查看结果。如果还在阅读,强烈推荐使用F10/F11逐步执行项目,以了解细节。玩得开心!

增强功能

  • 没有子元素的元素(即值元素)
  • 从样本XML的内容派生数据类型(整数、布尔值、DateTime等)
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485