MBG XML 到类生成器
代码生成器,用于通过扫描 XML 文件本身来创建 XML 可序列化类。
引言
MBG XML 到类生成器是一个工具,用于通过扫描 XML 文件来创建 .NET (C#) 类文件。 它生成所有必需的类,以及用于序列化目的的 XML 属性。
背景
有时,我会遇到来自客户或其他来源的 XML 文件,并且必须手动浏览该文件以便在我的代码中创建实体。 现在,熟悉相关 XML 文件的结构绝对是一个好主意,但不得不键入许多类和属性却不是一件有趣的事情。 因此,XML 到类生成器诞生了。
从哪里开始?
好吧,我们需要一些实体来保存数据,以便在扫描 XML 文件和创建 .cs 文件之间使用。 因此,让我们定义其中的一些。 我们需要
- 类
Entity
(用于保存与将成为类的 XML 元素相关的数据) - 类
Property
(与 XML 属性和一些元素相关) - 集合属性(我没有仅仅在
Property
类上包含“IsCollection
”属性是有原因的,我现在不会详细介绍)
类实体是主要实体,包含子类、属性和集合属性的集合。
解析 XML
对于解析 XML,我决定将此代码放入自定义 TreeView 控件中:XmlTreeView
。 我这样做是为了显示数据,并且在我需要生成它时有一个方便的地方可以获取它(我将数据存储在 TreeNode
的 Tag
属性中)。 当用户选择要加载的文件时,代码会将文件名传递给 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());
}
}
在这里,我们正在遍历每个类实体,生成类、属性等,并将类输出到文件中。
使用工具
- 单击工具栏上的“加载”按钮,选择你的 XML 文件,然后单击“确定”。
- 更改你需要更改的任何内容(类名、返回类型等)。
- 单击“生成”;那是大绿色按钮! :-D
注释
此应用程序使用 MBG 扩展库,因此在输出类中包含有用的 Load
/ Save
方法,用于序列化/反序列化。 所以,是的... 你甚至不必编写任何序列化代码; 你不是很幸运吗? ;-) 要构建源代码,你需要引用 MBG 扩展库。 你可以从二进制文件下载中获取它,也可以从这里获取:https://codeproject.org.cn/KB/dotnet/MBGExtensionsLibrary.aspx。
尽情享受吧!!!