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

XmlToXsd - 更好的模式生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (13投票s)

2010 年 12 月 4 日

CPOL

2分钟阅读

viewsIcon

44448

downloadIcon

1737

构建更好的模式,用于快速数据模型原型设计。

引言

在业务项目中,您经常需要生成复杂的模式。 本文概述了从示例 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 日 - 创建。
© . All rights reserved.