XmlToXsd - 更好的模式生成器






4.93/5 (13投票s)
构建更好的模式,用于快速数据模型原型设计。
引言
在业务项目中,您经常需要生成复杂的模式。 本文概述了从示例 XML 快速原型化高质量/可维护模式,以便在所有平台中干净地生成派生对象模型。
背景
有许多用于创建和管理模式的工具,大多数都“不好玩”。 从复杂工具的空白屏幕开始令人畏惧,尤其是在使用 XML 模式 (XSD) 等抽象语言编写时。
最好创建样本,然后使用现有工具生成模式。 这些模式生成工具的问题在于它们嵌套复杂类型... 嵌套复杂类型会导致两个问题
- 它难看/难以维护
- 生成器将从此类模式构建非常难看的对象
- 它不遵循 XML 模式(
msdata
命名空间)的一般行业惯例
举例说明
我开始使用一个示例进行数据建模; 在这种情况下,我想对童子军车赛数据进行建模(是的,我有一个 8 岁的男孩)。
<Derby>
<Racers>
<Group Name="Den7">
<Cub Id="1" First="Johny" Last="Racer" Place="1"/>
<Cub Id="2" First="Stan" Last="Lee" Plac="3"/>
</Group>
...
如果我在该 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>
...
请注意所有嵌套... 当您随后在生成的 derby.xsd 上运行 xsd.exe 时... 它将生成名称为 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>
...
改进 Xml2Xsd
因此,我着手解决所有这些问题,并构建了一个更好/更简单的生成器。
算法概述
- 为示例 XML 打开一个
XDocument
。 - 读取所有元素并构建一个 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();
// The first one is our root element... it needs to be extracted and massage
compTypes[0] = compTypes.First().Element(xs +
"sequence").Element(xs + "element");
// Warning: Namespaces are tricky/hinted here, be careful
return new XDocument(new XElement(target + "schema",
// Why 'qualified'?
// All "qualified" elements and
// attributes are in the targetNamespace of the
// schema and all "unqualified"
// elements and attributes are in no namespace.
// All global elements and attributes are qualified.
new XAttribute("elementFormDefault", "qualified"),
// Specify the target namespace,
// you will want this for schema validation
new XAttribute("targetNamespace", targetNamespace),
// hint to xDocument that we want
// the xml schema namespace to be called 'xs'
new XAttribute(XNamespace.Xmlns + "xs",
"http://www.w3.org/2001/XMLSchema"),
compTypes));
}
递归所有 XPath
对于每个元素,查找它是否不同,查找重复的元素名称(递归定义)元素,并跟踪它们。
static void RecurseAllXPaths(string xpath, XElement elem)
{
var missingXpath = !xpaths.ContainsKey(xpath);
var lclName = elem.Name.LocalName;
var hasLcl = elements.ContainsKey(lclName);
// Check for recursion in the element name (same name different level)
if (hasLcl && missingXpath)
RecurseElements.Add(lclName);
else if (!hasLcl)
elements.Add(lclName, true);
// if it's not in the xpath, then add it.
if (missingXpath)
xpaths.Add(xpath, null);
// add xpaths for all attributes
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 生成模式
现在我们有了一个 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);
}
BuildAttributeSchema
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;
}
BuildElementSchema
这一个不像 BuildAttribute
那么简单; 我们必须确保对父节点进行了适当的“类型引用”... 这有点棘手,但它运行良好。
private static XElement BuildElementSchema(string k,
string last, XElement parent)
{
XElement seqElem = null;
if (null != parent)
{
seqElem = parent.Element(xs + "sequence");
// Add a new squence if one doesn't already exist
if (null == seqElem && null != parent)
// Note: add sequence to the start,
// because sequences need to come before any
// attributes in XSD syntax
parent.AddFirst(seqElem = new XElement(xs + "sequence"));
}
else
{
// In this case, there's no existing parent
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); // add the ref to the existing sequence
return xpaths[k] = new XElement(xs + "complexType",
new XAttribute("name", lastInfo));
}
Using the Code
- 下载示例项目
- 在 VS2010 或 Express 中构建
- 从调试解决方案按 F5 将执行
- 在 bin/Debug 中打开 Derby.Xsd 以查看结果
如果您还在阅读,我强烈建议通过项目按 F10/F11 进入细节。 玩得开心!
增强功能
- 没有子元素的元素(又名值元素)
- 从示例 XML 的内容派生数据类型(整数、布尔值、DateTime 等)
未来改进
- 使递归定义的元素工作
历史
- 2010 年 12 月 4 日 - 创建。