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

MBG XML 到类生成器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2010年11月3日

CPOL

3分钟阅读

viewsIcon

28469

downloadIcon

1426

代码生成器,用于通过扫描 XML 文件本身来创建 XML 可序列化类。

Capture.PNG

引言

MBG XML 到类生成器是一个工具,用于通过扫描 XML 文件来创建 .NET (C#) 类文件。 它生成所有必需的类,以及用于序列化目的的 XML 属性。

背景

有时,我会遇到来自客户或其他来源的 XML 文件,并且必须手动浏览该文件以便在我的代码中创建实体。 现在,熟悉相关 XML 文件的结构绝对是一个好主意,但不得不键入许多类和属性却不是一件有趣的事情。 因此,XML 到类生成器诞生了。

从哪里开始?

好吧,我们需要一些实体来保存数据,以便在扫描 XML 文件和创建 .cs 文件之间使用。 因此,让我们定义其中的一些。 我们需要

  1. Entity(用于保存与将成为类的 XML 元素相关的数据)
  2. Property(与 XML 属性和一些元素相关)
  3. 集合属性(我没有仅仅在 Property 类上包含“IsCollection”属性是有原因的,我现在不会详细介绍)

类实体是主要实体,包含子类、属性和集合属性的集合。

解析 XML

对于解析 XML,我决定将此代码放入自定义 TreeView 控件中:XmlTreeView。 我这样做是为了显示数据,并且在我需要生成它时有一个方便的地方可以获取它(我将数据存储在 TreeNodeTag 属性中)。 当用户选择要加载的文件时,代码会将文件名传递给 XmlTreeView.ParseXml(),这开始了神奇的过程

public void ParseXml(string fileName)
{
    this.Nodes.Clear();
    XDocument document = XDocument.Load(fileName);
    Class mainClass = CreateClass(document.Root, true);
    this.Nodes.Add(CreateNode(mainClass));
}

private Class CreateClass(XElement element, bool isMain)
{
    Class newClass = new Class
    {
        Name = element.Name.LocalName.ToPascal(),
        IsMain = isMain
    };
    newClass.ClassNameChanged += 
      new Class.ClassNameChangedEventHandler(class_ClassNameChanged);

    #region Collections

    // Find names of all sub elements
    IEnumerable<string> distinctSubElementNames = 
      element.FindAllPossibleElements().Select(
      x => x.Name.LocalName).Distinct();
    foreach (string subElementName in distinctSubElementNames)
    {
        // Find first element with the specified name
        //XElement subElement = element.Elements(subElementName).First();
        XElement subElement = 
          element.FindAllPossibleElements().Where(
          x => x.Name.LocalName == subElementName).First();

        // If this is a property, then continue to next child element
        if (subElement.IsPropertyElement())
        {
            continue;
        }
        // If this element is an element in a collection
        if (subElement.IsCollectionElement())
        {
            newClass.IsCollection = true;
            newClass.CollectionType = subElementName.ToPascal();
            newClass.XmlName = subElementName;
            continue;
            //This is a collection already (only one type of child element)
        }
        else if (subElement.HasEqualSiblings())
        {
            string subClassName = subElement.Name.LocalName.ToPascal();
            newClass.CollectionProperties.Add(new CollectionProperty
            {
                CollectionName = subClassName + "Collection",
                //PropertyName = subClassName + 
                // (subElement.Name.LocalName.EndsWith("s") ? "es" : "s"),
                PropertyName = subClassName.Pluralize(),
                ClassName = subClassName,
                XmlName = subClassName == 
                  subElement.Name.LocalName ? string.Empty : subElement.Name.LocalName
            });
        }
    }

    #endregion

    #region Normal Properties

    foreach (string attributeName in element.FindAllPossibleAttributes())
    {
        newClass.Properties.Add(new Property
        {
            Name = attributeName.ToPascal(),
            ReturnType = PropertyType.String,
            IsXmlAttribute = true,
            XmlName = attributeName
        });
    }

    #endregion

    #region Sub Classes

    var subEls = element.FindAllPossibleElements();
    foreach (XElement subElement in subEls)
    {
        string subElementName = subElement.Name.LocalName.ToPascal();
        int count = element.Elements(subElement.Name).Count();

        //Determine if element is a property of parent or is a child class
        if (subElement.IsPropertyElement())
        {
            Property existingProperty = 
              newClass.Properties.SingleOrDefault(x => x.Name == subElementName);

            if (existingProperty == null)
            {
                newClass.Properties.Add(new Property
                {
                    Name = subElementName,
                    ReturnType = PropertyType.String,
                    IsXmlAttribute = false,
                    XmlName = subElement.Name.LocalName
                });
            }
        }
        else
        {
            Class subClass = 
              newClass.SubClasses.SingleOrDefault(x => x.Name == subElementName);

            if (subClass == null)
            {
                newClass.SubClasses.Add(CreateClass(subElement, false));

                if (newClass.IsCollection && newClass.CollectionType == subElementName)
                {
                    continue;
                }
                else
                {
                    subClass = newClass.SubClasses.SingleOrDefault(
                                  x => x.Name == subElementName);
                    if (subClass.IsCollection)
                    {
                        newClass.Properties.Add(new Property
                        {
                            //Name = subClass.CollectionType +
                            // (subClass.CollectionType.EndsWith("s") ? "es" : "s"),
                            Name = subClass.CollectionType.Pluralize(),
                            IsXmlAttribute = false,
                            ReturnType = PropertyType.Custom,
                            CustomType = subClass.CollectionType + "Collection",
                            XmlName = subElement.Name.LocalName,
                            XmlItemName = subClass.XmlName
                        });
                    }
                    else
                    {
                        //If Is A Collection Property Already
                        CollectionProperty cp = 
                          newClass.CollectionProperties.SingleOrDefault(
                          x => x.ClassName == subElementName);
                        if (cp != default(CollectionProperty))
                        { continue; }

                        newClass.Properties.Add(new Property
                        {
                            Name = subElementName,
                            IsXmlAttribute = false,
                            ReturnType = PropertyType.Custom,
                            CustomType = subElementName,
                            XmlName = subElement.Name.LocalName
                        });
                    }
                }
            }
        }
    }

    #endregion

    return newClass;
}

private TreeNode CreateNode(Class mainClass)
{
    TreeNode node = new TreeNode
    {
        Text = mainClass.Name,
        Tag = mainClass
    };

    foreach (Class subClass in mainClass.SubClasses)
    {
        node.Nodes.Add(CreateNode(subClass));
    }

    return node;
}

在这里,我们基本上只是扫描文档。 当我们遇到一个元素时,我们会创建另一个类(大多数时候),并为每个属性创建一个属性。 这听起来很简单,但如果真的这么简单,代码就不会这么冗长了。 事实上,它开始变得如此复杂,以至于如果我离开太久,我经常会忘记里面发生了什么。 因此,如果你能很快地理解所有内容,那就太好了! 你是超人。 如果没有,只要高兴它能工作就好! 该代码并非万无一失,但大多数时候应该做得很好,并且只会让你纠正几行(如果需要)。 我会进一步改进它,但我想,见鬼,我是免费做这件事的。 =) 嘿嘿。

无论如何,回到正题... 现在我们已经解析了 XML 文件,我们可以使用 UI 更改一些内容,然后点击闪亮的绿色按钮... 是的,这意味着“开始”。 对于每个属性,你会找到一个名为“返回类型”的属性。 正如你可能已经猜到的,这与属性的返回类型有关。 因为,默认情况下,我们只能检测字符串; 如果你希望特定属性具有不同的数据类型,则需要显式指定。 或者你可以保留它并在代码生成完成后更改它。 现在你可以点击按钮了... ;-)

代码生成

public static void Generate(Class mainClass, string fileName)
{
    StringBuilder sbClasses = new StringBuilder(2000);
    sbClasses.Append(Constants.USINGS);
    sbClasses.Append(string.Format(
      Constants.NAMESPACE_START_FORMAT, "MyNamespace"));

    sbClasses.Append(GenerateClass(mainClass, ref sbClasses));

    sbClasses.Append("}"); // End Namespace

    sbClasses.ToString().ToFile(fileName);
}

private static string GenerateClass(Class newClass, ref StringBuilder sbClasses)
{
    if (newClass.IsMain && newClass.IsCollection)
    {
        throw new ArgumentException("The root class cannot be a collection!");
    }

    string classFormat = Constants.ROOT_CLASS_FORMAT;
    if (!newClass.IsMain)
    {
        classFormat = newClass.IsCollection ? 
          Constants.COLLECTION_CLASS_FORMAT : Constants.CLASS_FORMAT;
    }

    StringBuilder sbProperties = new StringBuilder(2000);
    StringBuilder sbConstructor = new StringBuilder(50);

    #region Properties

    foreach (Property property in newClass.Properties.OrderBy(p => p.Name))
    {
        string xmlAttribute = string.Empty;//Empty for elements only (not attributes)
        if (property.IsXmlAttribute)
        {
            if (!string.IsNullOrEmpty(property.XmlName) &&
                property.XmlName != property.Name)
            {
                xmlAttribute = 
                  string.Format(Constants.XML_ATTRIBUTE_FORMAT, property.XmlName);
            }
            else { xmlAttribute = Constants.XML_ATTRIBUTE; }
        }
        else
        {
            if (!string.IsNullOrEmpty(property.XmlName) &&
                property.XmlName != property.Name)
            {
                if (!string.IsNullOrEmpty(property.XmlItemName))
                {
                    if (!string.IsNullOrEmpty(property.XmlName))
                    {
                        // Apply XmlArray / XmlArrayItem attributes
                        xmlAttribute = string.Format(Constants.XML_COLLECTION_FORMAT, 
                                       property.XmlName, property.XmlItemName);
                    }
                    else
                    {
                        // Use XmlItemName in XmlElement name
                        xmlAttribute = string.Format(Constants.XML_ELEMENT_FORMAT, 
                                                     property.XmlItemName);
                    }
                }
                else
                {
                    // Apply XmlElement attribute
                    xmlAttribute = 
                      string.Format(Constants.XML_ELEMENT_FORMAT, property.XmlName);
                }
            }
        }

        sbProperties.Append(string.Format(
            Constants.PROPERTY_FORMAT,
            property.ReturnType == PropertyType.Custom
                ? property.CustomType
                : property.ReturnType.ToString(),
            property.Name,
            xmlAttribute));

        sbProperties.Append(Environment.NewLine);

        if (property.ReturnType == PropertyType.Custom)
        {
            sbConstructor.Append(string.Format(
                "{0}            {1} = new {2}();",
                Environment.NewLine,
                property.Name,
                property.CustomType));
        }
    }

    #endregion

    foreach (CollectionProperty collectionProperty in newClass.CollectionProperties)
    {
        sbProperties.Append(string.Format(
                Constants.PROPERTY_FORMAT,
                collectionProperty.CollectionName,
                        collectionProperty.PropertyName,
                collectionProperty.RepresentsXmlNode
                    ? string.Empty
                    : string.Format(
                        Constants.XML_ELEMENT_FORMAT,
                        string.IsNullOrEmpty(collectionProperty.XmlName)
                            ? collectionProperty.ClassName
                            : collectionProperty.XmlName)));

        sbConstructor.Append(string.Format(
            "{0}            {1} = new {2}();",
            Environment.NewLine,
            collectionProperty.PropertyName,
            collectionProperty.CollectionName));
    }

    foreach (Class subClass in newClass.SubClasses)
    {
        sbClasses.Append(GenerateClass(subClass, ref sbClasses));
    }

    if (!newClass.CollectionProperties.IsNullOrEmpty())
    {
        StringBuilder sbCollectionClasses = new StringBuilder();
        foreach (CollectionProperty collectionProperty in newClass.CollectionProperties)
        {
            sbCollectionClasses.Append(string.Format(
              Constants.COLLECTION_CLASS_FORMAT, collectionProperty.ClassName));
            sbCollectionClasses.Append(Environment.NewLine);
        }

        string itemClass = string.Format(classFormat,
            newClass.IsCollection ? newClass.CollectionType : newClass.Name,
            sbProperties.ToString(),
            sbConstructor.ToString());
        return string.Concat(sbCollectionClasses.ToString(), 
                             Environment.NewLine, itemClass);
    }
    else
    {
        return string.Format(
                classFormat,
                newClass.IsCollection ? newClass.CollectionType : newClass.Name,
                sbProperties.ToString(),
                sbConstructor.ToString());
    }
}

在这里,我们正在遍历每个类实体,生成类、属性等,并将类输出到文件中。

使用工具

  1. 单击工具栏上的“加载”按钮,选择你的 XML 文件,然后单击“确定”。
  2. 更改你需要更改的任何内容(类名、返回类型等)。
  3. 单击“生成”;那是大绿色按钮! :-D

注释

此应用程序使用 MBG 扩展库,因此在输出类中包含有用的 Load / Save 方法,用于序列化/反序列化。 所以,是的... 你甚至不必编写任何序列化代码; 你不是很幸运吗? ;-) 要构建源代码,你需要引用 MBG 扩展库。 你可以从二进制文件下载中获取它,也可以从这里获取:https://codeproject.org.cn/KB/dotnet/MBGExtensionsLibrary.aspx

尽情享受吧!!!

© . All rights reserved.