在进行数据建模时,经常需要生成复杂的模式(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>
为了解决所有这些问题,构建了一个更好/更简单的生成器。
打开一个XDocument用于样本XML。读取所有元素并构建一个XPath字典。使用了字典,但List
从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逐步执行项目,以了解细节。玩得开心!