| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | |
| 3 | namespace WixToolset.Tools |
| 4 | { |
| 5 | using System; |
| 6 | using System.CodeDom; |
| 7 | using System.Collections; |
| 8 | using System.Collections.Generic; |
| 9 | using System.Collections.Specialized; |
| 10 | using System.IO; |
| 11 | using System.Reflection; |
| 12 | using System.Text; |
| 13 | using System.Text.RegularExpressions; |
| 14 | using System.Xml; |
| 15 | using System.Xml.Schema; |
| 16 | |
| 17 | /// <summary> |
| 18 | /// Type containing static Generate method, which fills in a compile unit from a |
| 19 | /// given schema. |
| 20 | /// </summary> |
| 21 | internal class StronglyTypedClasses |
| 22 | { |
| 23 | private static string outputXmlComment = "Processes this element and all child elements into an XmlWriter."; |
| 24 | private static Hashtable simpleTypeNamesToClrTypeNames; |
| 25 | private static Dictionary<string, EnumDeclaration> typeNamesToEnumDeclarations; |
| 26 | private static Dictionary<EnumDeclaration, CodeTypeDeclaration> enumsToParseMethodClasses; |
| 27 | private static Regex multiUppercaseNameRegex = new Regex("[A-Z][A-Z][A-Z]", RegexOptions.Compiled); |
| 28 | private static Dictionary<string, XmlSchemaAttributeGroup> refToAttributeGroups; |
| 29 | private static CodeTypeDeclaration enumHelperClass; |
| 30 | |
| 31 | /// <summary> |
| 32 | /// Private constructor for static class. |
| 33 | /// </summary> |
| 34 | private StronglyTypedClasses() |
| 35 | { |
| 36 | } |
| 37 | |
| 38 | /// <summary> |
| 39 | /// Generates strongly typed serialization classes for the given schema document |
| 40 | /// under the given namespace and generates a code compile unit. |
| 41 | /// </summary> |
| 42 | /// <param name="xmlSchema">Schema document to generate classes for.</param> |
| 43 | /// <param name="generateNamespace">Namespace to be used for the generated code.</param> |
| 44 | /// <param name="commonNamespace">Namespace in which to find common classes and interfaces, |
| 45 | /// like ISchemaElement.</param> |
| 46 | /// <returns>A fully populated CodeCompileUnit, which can be serialized in the language of choice.</returns> |
| 47 | public static CodeCompileUnit Generate(XmlSchema xmlSchema, string generateNamespace, string commonNamespace) |
| 48 | { |
| 49 | if (xmlSchema == null) |
| 50 | { |
| 51 | throw new ArgumentNullException("xmlSchema"); |
| 52 | } |
| 53 | if (generateNamespace == null) |
| 54 | { |
| 55 | throw new ArgumentNullException("generateNamespace"); |
| 56 | } |
| 57 | |
| 58 | simpleTypeNamesToClrTypeNames = new Hashtable(); |
| 59 | typeNamesToEnumDeclarations = new Dictionary<string, EnumDeclaration>(); |
| 60 | refToAttributeGroups = new Dictionary<string, XmlSchemaAttributeGroup>(); |
| 61 | enumsToParseMethodClasses = new Dictionary<EnumDeclaration, CodeTypeDeclaration>(); |
| 62 | |
| 63 | CodeCompileUnit codeCompileUnit = new CodeCompileUnit(); |
| 64 | CodeNamespace codeNamespace = new CodeNamespace(generateNamespace); |
| 65 | codeCompileUnit.Namespaces.Add(codeNamespace); |
| 66 | codeNamespace.Imports.Add(new CodeNamespaceImport("System")); |
| 67 | codeNamespace.Imports.Add(new CodeNamespaceImport("System.CodeDom.Compiler")); // for GeneratedCodeAttribute |
| 68 | codeNamespace.Imports.Add(new CodeNamespaceImport("System.Collections")); |
| 69 | codeNamespace.Imports.Add(new CodeNamespaceImport("System.Diagnostics.CodeAnalysis")); |
| 70 | codeNamespace.Imports.Add(new CodeNamespaceImport("System.Globalization")); |
| 71 | codeNamespace.Imports.Add(new CodeNamespaceImport("System.Xml")); |
| 72 | if (commonNamespace != null) |
| 73 | { |
| 74 | codeNamespace.Imports.Add(new CodeNamespaceImport(commonNamespace)); |
| 75 | } |
| 76 | |
| 77 | // NOTE: This hash table serves double duty so be sure to have the XSD |
| 78 | // type name mapped to the CLR type name *and* the CLR type name |
| 79 | // mapped to the same CLR type name. Look at long and bool for |
| 80 | // examples below (and before you ask, no I don't know why DateTime |
| 81 | // just works). |
| 82 | simpleTypeNamesToClrTypeNames.Add("dateTime", "DateTime"); |
| 83 | simpleTypeNamesToClrTypeNames.Add("integer", "int"); |
| 84 | simpleTypeNamesToClrTypeNames.Add("int", "int"); |
| 85 | simpleTypeNamesToClrTypeNames.Add("NMTOKEN", "string"); |
| 86 | simpleTypeNamesToClrTypeNames.Add("string", "string"); |
| 87 | simpleTypeNamesToClrTypeNames.Add("nonNegativeInteger", "long"); |
| 88 | simpleTypeNamesToClrTypeNames.Add("long", "long"); |
| 89 | simpleTypeNamesToClrTypeNames.Add("boolean", "bool"); |
| 90 | simpleTypeNamesToClrTypeNames.Add("bool", "bool"); |
| 91 | |
| 92 | xmlSchema.Compile(null); |
| 93 | |
| 94 | foreach (XmlSchemaAttributeGroup schemaAttributeGroup in xmlSchema.AttributeGroups.Values) |
| 95 | { |
| 96 | refToAttributeGroups.Add(schemaAttributeGroup.Name, schemaAttributeGroup); |
| 97 | } |
| 98 | |
| 99 | foreach (XmlSchemaObject schemaObject in xmlSchema.SchemaTypes.Values) |
| 100 | { |
| 101 | XmlSchemaSimpleType schemaSimpleType = schemaObject as XmlSchemaSimpleType; |
| 102 | if (schemaSimpleType != null) |
| 103 | { |
| 104 | ProcessSimpleType(schemaSimpleType, codeNamespace); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | foreach (XmlSchemaObject schemaObject in xmlSchema.SchemaTypes.Values) |
| 109 | { |
| 110 | XmlSchemaComplexType schemaComplexType = schemaObject as XmlSchemaComplexType; |
| 111 | if (schemaComplexType != null) |
| 112 | { |
| 113 | ProcessComplexType(schemaComplexType, codeNamespace); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | foreach (XmlSchemaObject schemaObject in xmlSchema.Elements.Values) |
| 118 | { |
| 119 | XmlSchemaElement schemaElement = schemaObject as XmlSchemaElement; |
| 120 | if (schemaElement != null) |
| 121 | { |
| 122 | ProcessElement(schemaElement, codeNamespace); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | return codeCompileUnit; |
| 127 | } |
| 128 | |
| 129 | /// <summary> |
| 130 | /// Processes an XmlSchemaElement into corresponding types. |
| 131 | /// </summary> |
| 132 | /// <param name="schemaElement">XmlSchemaElement to be processed.</param> |
| 133 | /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param> |
| 134 | private static void ProcessElement(XmlSchemaElement schemaElement, CodeNamespace codeNamespace) |
| 135 | { |
| 136 | string elementType = schemaElement.SchemaTypeName.Name; |
| 137 | string elementNamespace = schemaElement.QualifiedName.Namespace; |
| 138 | string elementDocumentation = GetDocumentation(schemaElement.Annotation); |
| 139 | |
| 140 | if ((elementType == null || elementType.Length == 0) && schemaElement.SchemaType != null) |
| 141 | { |
| 142 | ProcessComplexType(schemaElement.Name, elementNamespace, (XmlSchemaComplexType)schemaElement.SchemaType, elementDocumentation, codeNamespace); |
| 143 | } |
| 144 | else |
| 145 | { |
| 146 | if (elementType == null || elementType.Length == 0) |
| 147 | { |
| 148 | elementType = "string"; |
| 149 | } |
| 150 | |
| 151 | CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(schemaElement.Name); |
| 152 | typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); |
| 153 | typeDeclaration.Attributes = MemberAttributes.Public; |
| 154 | typeDeclaration.IsClass = true; |
| 155 | |
| 156 | if (elementDocumentation != null) |
| 157 | { |
| 158 | GenerateSummaryComment(typeDeclaration.Comments, elementDocumentation); |
| 159 | } |
| 160 | |
| 161 | CodeMemberMethod outputXmlMethod = new CodeMemberMethod(); |
| 162 | outputXmlMethod.Attributes = MemberAttributes.Public; |
| 163 | outputXmlMethod.ImplementationTypes.Add("ISchemaElement"); |
| 164 | outputXmlMethod.Name = "OutputXml"; |
| 165 | outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer")); |
| 166 | outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false)); |
| 167 | outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteStartElement", new CodeSnippetExpression(String.Concat("\"", schemaElement.Name, "\"")), new CodeSnippetExpression(String.Concat("\"", elementNamespace, "\"")))); |
| 168 | GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment); |
| 169 | |
| 170 | if (simpleTypeNamesToClrTypeNames.ContainsKey(elementType)) |
| 171 | { |
| 172 | CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement"); |
| 173 | typeDeclaration.Members.Add(parentField); |
| 174 | |
| 175 | CodeMemberProperty parentProperty = new CodeMemberProperty(); |
| 176 | parentProperty.Attributes = MemberAttributes.Public; |
| 177 | parentProperty.ImplementationTypes.Add("ISchemaElement"); |
| 178 | parentProperty.Name = "ParentElement"; |
| 179 | parentProperty.Type = new CodeTypeReference("ISchemaElement"); |
| 180 | parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"))); |
| 181 | parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value"))); |
| 182 | typeDeclaration.Members.Add(parentProperty); |
| 183 | |
| 184 | CodeMemberMethod setAttributeMethod = new CodeMemberMethod(); |
| 185 | setAttributeMethod.Attributes = MemberAttributes.Public; |
| 186 | setAttributeMethod.ImplementationTypes.Add("ISetAttributes"); |
| 187 | setAttributeMethod.Name = "SetAttribute"; |
| 188 | setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name")); |
| 189 | setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value")); |
| 190 | setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes"); |
| 191 | setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); |
| 192 | setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true)); |
| 193 | |
| 194 | GenerateFieldAndProperty("Content", (string)simpleTypeNamesToClrTypeNames[elementType], typeDeclaration, outputXmlMethod, setAttributeMethod, null, elementDocumentation, true, false); |
| 195 | |
| 196 | typeDeclaration.Members.Add(setAttributeMethod); |
| 197 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes")); |
| 198 | } |
| 199 | else |
| 200 | { |
| 201 | typeDeclaration.BaseTypes.Add(elementType); |
| 202 | outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeBaseReferenceExpression(), "OutputXml", new CodeVariableReferenceExpression("writer"))); |
| 203 | outputXmlMethod.Attributes |= MemberAttributes.Override; |
| 204 | } |
| 205 | |
| 206 | outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteEndElement")); |
| 207 | |
| 208 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement")); |
| 209 | typeDeclaration.Members.Add(outputXmlMethod); |
| 210 | codeNamespace.Types.Add(typeDeclaration); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | /// <summary> |
| 215 | /// Processes an XmlSchemaComplexType into corresponding types. |
| 216 | /// </summary> |
| 217 | /// <param name="complexType">XmlSchemaComplexType to be processed.</param> |
| 218 | /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param> |
| 219 | private static void ProcessComplexType(XmlSchemaComplexType complexType, CodeNamespace codeNamespace) |
| 220 | { |
| 221 | CodeMemberMethod outputXmlMethod = new CodeMemberMethod(); |
| 222 | outputXmlMethod.Attributes = MemberAttributes.Public; |
| 223 | outputXmlMethod.ImplementationTypes.Add("ISchemaElement"); |
| 224 | outputXmlMethod.Name = "OutputXml"; |
| 225 | outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer")); |
| 226 | outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false)); |
| 227 | GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment); |
| 228 | |
| 229 | CodeMemberMethod setAttributeMethod = new CodeMemberMethod(); |
| 230 | setAttributeMethod.Attributes = MemberAttributes.Public; |
| 231 | setAttributeMethod.ImplementationTypes.Add("ISetAttributes"); |
| 232 | setAttributeMethod.Name = "SetAttribute"; |
| 233 | setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name")); |
| 234 | setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value")); |
| 235 | setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes"); |
| 236 | setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); |
| 237 | setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true)); |
| 238 | |
| 239 | string documentation = GetDocumentation(complexType.Annotation); |
| 240 | |
| 241 | ProcessSimpleContent(complexType.Name, (XmlSchemaSimpleContentExtension)complexType.ContentModel.Content, documentation, codeNamespace, outputXmlMethod, setAttributeMethod, true); |
| 242 | } |
| 243 | |
| 244 | /// <summary> |
| 245 | /// Processes an XmlSchemaComplexType into corresponding types. |
| 246 | /// </summary> |
| 247 | /// <param name="typeName">Name to use for the type being output.</param> |
| 248 | /// <param name="elementNamespace">Namespace of the xml element.</param> |
| 249 | /// <param name="complexType">XmlSchemaComplexType to be processed.</param> |
| 250 | /// <param name="documentation">Documentation for the element.</param> |
| 251 | /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param> |
| 252 | private static void ProcessComplexType(string typeName, string elementNamespace, XmlSchemaComplexType complexType, string documentation, CodeNamespace codeNamespace) |
| 253 | { |
| 254 | CodeMemberMethod outputXmlMethod = new CodeMemberMethod(); |
| 255 | outputXmlMethod.Attributes = MemberAttributes.Public; |
| 256 | outputXmlMethod.ImplementationTypes.Add("ISchemaElement"); |
| 257 | outputXmlMethod.Name = "OutputXml"; |
| 258 | outputXmlMethod.Parameters.Add(new CodeParameterDeclarationExpression("XmlWriter", "writer")); |
| 259 | outputXmlMethod.Statements.Add(GetArgumentNullCheckStatement("writer", false)); |
| 260 | outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteStartElement", new CodeSnippetExpression(String.Concat("\"", typeName, "\"")), new CodeSnippetExpression(String.Concat("\"", elementNamespace, "\"")))); |
| 261 | GenerateSummaryComment(outputXmlMethod.Comments, outputXmlComment); |
| 262 | |
| 263 | CodeMemberMethod setAttributeMethod = new CodeMemberMethod(); |
| 264 | setAttributeMethod.Attributes = MemberAttributes.Public; |
| 265 | setAttributeMethod.ImplementationTypes.Add("ISetAttributes"); |
| 266 | setAttributeMethod.Name = "SetAttribute"; |
| 267 | setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name")); |
| 268 | setAttributeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "value")); |
| 269 | setAttributeMethod.PrivateImplementationType = new CodeTypeReference("ISetAttributes"); |
| 270 | setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); |
| 271 | setAttributeMethod.Statements.Add(GetArgumentNullCheckStatement("name", true)); |
| 272 | |
| 273 | if (complexType.ContentModel == null) |
| 274 | { |
| 275 | CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(typeName); |
| 276 | typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); |
| 277 | typeDeclaration.Attributes = MemberAttributes.Public; |
| 278 | typeDeclaration.IsClass = true; |
| 279 | CodeIterationStatement childEnumStatement = null; |
| 280 | |
| 281 | if (documentation != null) |
| 282 | { |
| 283 | GenerateSummaryComment(typeDeclaration.Comments, documentation); |
| 284 | } |
| 285 | |
| 286 | if (complexType.Particle != null) |
| 287 | { |
| 288 | CodeMemberField childrenField = new CodeMemberField("ElementCollection", "children"); |
| 289 | typeDeclaration.Members.Add(childrenField); |
| 290 | |
| 291 | CodeMemberProperty childrenProperty = new CodeMemberProperty(); |
| 292 | childrenProperty.Attributes = MemberAttributes.Public; |
| 293 | childrenProperty.ImplementationTypes.Add("IParentElement"); |
| 294 | childrenProperty.Name = "Children"; |
| 295 | childrenProperty.Type = new CodeTypeReference("IEnumerable"); |
| 296 | childrenProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"))); |
| 297 | typeDeclaration.Members.Add(childrenProperty); |
| 298 | |
| 299 | CodeMemberProperty filterChildrenProperty = new CodeMemberProperty(); |
| 300 | filterChildrenProperty.Attributes = MemberAttributes.Public; |
| 301 | filterChildrenProperty.ImplementationTypes.Add("IParentElement"); |
| 302 | filterChildrenProperty.Name = "Item"; |
| 303 | filterChildrenProperty.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Type), "childType")); |
| 304 | filterChildrenProperty.Type = new CodeTypeReference("IEnumerable"); |
| 305 | filterChildrenProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "Filter", new CodeVariableReferenceExpression("childType")))); |
| 306 | filterChildrenProperty.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")); |
| 307 | typeDeclaration.Members.Add(filterChildrenProperty); |
| 308 | |
| 309 | CodeMemberMethod addChildMethod = new CodeMemberMethod(); |
| 310 | addChildMethod.Attributes = MemberAttributes.Public; |
| 311 | addChildMethod.ImplementationTypes.Add("IParentElement"); |
| 312 | addChildMethod.Name = "AddChild"; |
| 313 | addChildMethod.Parameters.Add(new CodeParameterDeclarationExpression("ISchemaElement", "child")); |
| 314 | addChildMethod.Statements.Add(GetArgumentNullCheckStatement("child", false)); |
| 315 | CodeExpressionStatement addChildStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "AddElement", new CodeVariableReferenceExpression("child"))); |
| 316 | addChildMethod.Statements.Add(addChildStatement); |
| 317 | CodeAssignStatement setParentStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("child"), "ParentElement"), new CodeThisReferenceExpression()); |
| 318 | addChildMethod.Statements.Add(setParentStatement); |
| 319 | typeDeclaration.Members.Add(addChildMethod); |
| 320 | |
| 321 | CodeMemberMethod removeChildMethod = new CodeMemberMethod(); |
| 322 | removeChildMethod.Attributes = MemberAttributes.Public; |
| 323 | removeChildMethod.ImplementationTypes.Add("IParentElement"); |
| 324 | removeChildMethod.Name = "RemoveChild"; |
| 325 | removeChildMethod.Parameters.Add(new CodeParameterDeclarationExpression("ISchemaElement", "child")); |
| 326 | removeChildMethod.Statements.Add(GetArgumentNullCheckStatement("child", false)); |
| 327 | CodeExpressionStatement removeChildStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "RemoveElement", new CodeVariableReferenceExpression("child"))); |
| 328 | removeChildMethod.Statements.Add(removeChildStatement); |
| 329 | CodeAssignStatement nullParentStatement = new CodeAssignStatement(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("child"), "ParentElement"), new CodePrimitiveExpression(null)); |
| 330 | removeChildMethod.Statements.Add(nullParentStatement); |
| 331 | typeDeclaration.Members.Add(removeChildMethod); |
| 332 | |
| 333 | CodeMemberMethod createChildMethod = new CodeMemberMethod(); |
| 334 | createChildMethod.Attributes = MemberAttributes.Public; |
| 335 | createChildMethod.ImplementationTypes.Add("ICreateChildren"); |
| 336 | createChildMethod.Name = "CreateChild"; |
| 337 | createChildMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "childName")); |
| 338 | createChildMethod.PrivateImplementationType = new CodeTypeReference("ICreateChildren"); |
| 339 | createChildMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")); |
| 340 | createChildMethod.ReturnType = new CodeTypeReference("ISchemaElement"); |
| 341 | createChildMethod.Statements.Add(GetArgumentNullCheckStatement("childName", true)); |
| 342 | createChildMethod.Statements.Add(new CodeVariableDeclarationStatement("ISchemaElement", "childValue", new CodePrimitiveExpression(null))); |
| 343 | |
| 344 | CodeConstructor typeConstructor = new CodeConstructor(); |
| 345 | typeConstructor.Attributes = MemberAttributes.Public; |
| 346 | |
| 347 | CodeVariableReferenceExpression collectionVariable = null; |
| 348 | |
| 349 | XmlSchemaChoice schemaChoice = complexType.Particle as XmlSchemaChoice; |
| 350 | if (schemaChoice != null) |
| 351 | { |
| 352 | collectionVariable = ProcessSchemaGroup(schemaChoice, typeConstructor, createChildMethod); |
| 353 | } |
| 354 | else |
| 355 | { |
| 356 | XmlSchemaSequence schemaSequence = complexType.Particle as XmlSchemaSequence; |
| 357 | if (schemaSequence != null) |
| 358 | { |
| 359 | collectionVariable = ProcessSchemaGroup(schemaSequence, typeConstructor, createChildMethod); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | typeConstructor.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), collectionVariable)); |
| 364 | typeDeclaration.Members.Add(typeConstructor); |
| 365 | |
| 366 | CodeConditionStatement childNameNotFound = new CodeConditionStatement(); |
| 367 | childNameNotFound.Condition = new CodeBinaryOperatorExpression(new CodePrimitiveExpression(null), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression("childValue")); |
| 368 | childNameNotFound.TrueStatements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("InvalidOperationException", new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("String"), "Concat", new CodeVariableReferenceExpression("childName"), new CodeSnippetExpression("\" is not a valid child name.\""))))); |
| 369 | createChildMethod.Statements.Add(childNameNotFound); |
| 370 | |
| 371 | if (createChildMethod.Statements.Count > 8) |
| 372 | { |
| 373 | createChildMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); |
| 374 | } |
| 375 | |
| 376 | createChildMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("childValue"))); |
| 377 | typeDeclaration.Members.Add(createChildMethod); |
| 378 | |
| 379 | childEnumStatement = new CodeIterationStatement(); |
| 380 | childEnumStatement.InitStatement = new CodeVariableDeclarationStatement("IEnumerator", "enumerator", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "children"), "GetEnumerator")); |
| 381 | childEnumStatement.TestExpression = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("enumerator"), "MoveNext"); |
| 382 | childEnumStatement.Statements.Add(new CodeVariableDeclarationStatement("ISchemaElement", "childElement", new CodeCastExpression("ISchemaElement", new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("enumerator"), "Current")))); |
| 383 | childEnumStatement.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("childElement"), "OutputXml", new CodeVariableReferenceExpression("writer"))); |
| 384 | childEnumStatement.IncrementStatement = new CodeExpressionStatement(new CodeSnippetExpression("")); |
| 385 | |
| 386 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("IParentElement")); |
| 387 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ICreateChildren")); |
| 388 | } |
| 389 | |
| 390 | // TODO: Handle xs:anyAttribute. |
| 391 | ProcessAttributes(complexType.Attributes, typeDeclaration, outputXmlMethod, setAttributeMethod); |
| 392 | |
| 393 | if (childEnumStatement != null) |
| 394 | { |
| 395 | outputXmlMethod.Statements.Add(childEnumStatement); |
| 396 | } |
| 397 | |
| 398 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement")); |
| 399 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes")); |
| 400 | |
| 401 | CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement"); |
| 402 | typeDeclaration.Members.Add(parentField); |
| 403 | |
| 404 | CodeMemberProperty parentProperty = new CodeMemberProperty(); |
| 405 | parentProperty.Attributes = MemberAttributes.Public; |
| 406 | parentProperty.ImplementationTypes.Add("ISchemaElement"); |
| 407 | parentProperty.Name = "ParentElement"; |
| 408 | parentProperty.Type = new CodeTypeReference("ISchemaElement"); |
| 409 | parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"))); |
| 410 | parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value"))); |
| 411 | typeDeclaration.Members.Add(parentProperty); |
| 412 | |
| 413 | if (outputXmlMethod.Statements.Count > 8) |
| 414 | { |
| 415 | outputXmlMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); |
| 416 | } |
| 417 | |
| 418 | if (setAttributeMethod.Statements.Count > 8) |
| 419 | { |
| 420 | setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); |
| 421 | } |
| 422 | |
| 423 | typeDeclaration.Members.Add(outputXmlMethod); |
| 424 | typeDeclaration.Members.Add(setAttributeMethod); |
| 425 | codeNamespace.Types.Add(typeDeclaration); |
| 426 | } |
| 427 | else |
| 428 | { |
| 429 | ProcessSimpleContent(typeName, (XmlSchemaSimpleContentExtension)complexType.ContentModel.Content, documentation, codeNamespace, outputXmlMethod, setAttributeMethod, false); |
| 430 | } |
| 431 | |
| 432 | outputXmlMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteEndElement")); |
| 433 | } |
| 434 | |
| 435 | /// <summary> |
| 436 | /// Processes a collection of attributes, generating the required fields and properties. |
| 437 | /// </summary> |
| 438 | /// <param name="attributes">List of attribute or attributeGroupRef elements being processed.</param> |
| 439 | /// <param name="typeDeclaration">CodeTypeDeclaration to be used when outputting code.</param> |
| 440 | /// <param name="outputXmlMethod">Member method for the OutputXml method.</param> |
| 441 | /// <param name="setAttributeMethod">Member method for the SetAttribute method.</param> |
| 442 | private static void ProcessAttributes(XmlSchemaObjectCollection attributes, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod) |
| 443 | { |
| 444 | foreach (XmlSchemaObject schemaObject in attributes) |
| 445 | { |
| 446 | XmlSchemaAttribute schemaAttribute = schemaObject as XmlSchemaAttribute; |
| 447 | if (schemaAttribute != null) |
| 448 | { |
| 449 | ProcessAttribute(schemaAttribute, typeDeclaration, outputXmlMethod, setAttributeMethod); |
| 450 | } |
| 451 | else |
| 452 | { |
| 453 | XmlSchemaAttributeGroupRef schemaAttributeGroupRef = schemaObject as XmlSchemaAttributeGroupRef; |
| 454 | if (schemaAttributeGroupRef != null) |
| 455 | { |
| 456 | XmlSchemaAttributeGroup schemaAttributeGroup = refToAttributeGroups[schemaAttributeGroupRef.RefName.Name]; |
| 457 | // recurse! |
| 458 | ProcessAttributes(schemaAttributeGroup.Attributes, typeDeclaration, outputXmlMethod, setAttributeMethod); |
| 459 | } |
| 460 | } |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | /// <summary> |
| 465 | /// Processes an XmlSchemaGroupBase element. |
| 466 | /// </summary> |
| 467 | /// <param name="schemaGroup">Element group to process.</param> |
| 468 | /// <param name="constructor">Constructor to which statements should be added.</param> |
| 469 | /// <param name="createChildMethod">Method used for creating children on read-in.</param> |
| 470 | /// <returns>A reference to the local variable containing the collection.</returns> |
| 471 | private static CodeVariableReferenceExpression ProcessSchemaGroup(XmlSchemaGroupBase schemaGroup, CodeConstructor constructor, CodeMemberMethod createChildMethod) |
| 472 | { |
| 473 | return ProcessSchemaGroup(schemaGroup, constructor, createChildMethod, 0); |
| 474 | } |
| 475 | |
| 476 | /// <summary> |
| 477 | /// Processes an XmlSchemaGroupBase element. |
| 478 | /// </summary> |
| 479 | /// <param name="schemaGroup">Element group to process.</param> |
| 480 | /// <param name="constructor">Constructor to which statements should be added.</param> |
| 481 | /// <param name="createChildMethod">Method used for creating children on read-in.</param> |
| 482 | /// <param name="depth">Depth to which this collection is nested.</param> |
| 483 | /// <returns>A reference to the local variable containing the collection.</returns> |
| 484 | private static CodeVariableReferenceExpression ProcessSchemaGroup(XmlSchemaGroupBase schemaGroup, CodeConstructor constructor, CodeMemberMethod createChildMethod, int depth) |
| 485 | { |
| 486 | string collectionName = String.Format("childCollection{0}", depth); |
| 487 | CodeVariableReferenceExpression collectionVariableReference = new CodeVariableReferenceExpression(collectionName); |
| 488 | CodeVariableDeclarationStatement collectionStatement = new CodeVariableDeclarationStatement("ElementCollection", collectionName); |
| 489 | if (schemaGroup is XmlSchemaChoice) |
| 490 | { |
| 491 | collectionStatement.InitExpression = new CodeObjectCreateExpression("ElementCollection", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ElementCollection.CollectionType"), "Choice")); |
| 492 | } |
| 493 | else |
| 494 | { |
| 495 | collectionStatement.InitExpression = new CodeObjectCreateExpression("ElementCollection", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ElementCollection.CollectionType"), "Sequence")); |
| 496 | } |
| 497 | constructor.Statements.Add(collectionStatement); |
| 498 | |
| 499 | foreach (XmlSchemaObject obj in schemaGroup.Items) |
| 500 | { |
| 501 | XmlSchemaElement schemaElement = obj as XmlSchemaElement; |
| 502 | if (schemaElement != null) |
| 503 | { |
| 504 | if (schemaGroup is XmlSchemaChoice) |
| 505 | { |
| 506 | CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.ChoiceItem", new CodeTypeOfExpression(schemaElement.RefName.Name))); |
| 507 | constructor.Statements.Add(addItemInvoke); |
| 508 | } |
| 509 | else |
| 510 | { |
| 511 | CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.SequenceItem", new CodeTypeOfExpression(schemaElement.RefName.Name))); |
| 512 | constructor.Statements.Add(addItemInvoke); |
| 513 | } |
| 514 | |
| 515 | CodeConditionStatement createChildIf = new CodeConditionStatement(); |
| 516 | createChildIf.Condition = new CodeBinaryOperatorExpression(new CodeSnippetExpression(String.Concat("\"", schemaElement.RefName.Name, "\"")), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression("childName")); |
| 517 | createChildIf.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("childValue"), new CodeObjectCreateExpression(schemaElement.RefName.Name))); |
| 518 | createChildMethod.Statements.Add(createChildIf); |
| 519 | |
| 520 | continue; |
| 521 | } |
| 522 | |
| 523 | XmlSchemaAny schemaAny = obj as XmlSchemaAny; |
| 524 | if (schemaAny != null) |
| 525 | { |
| 526 | if (schemaGroup is XmlSchemaChoice) |
| 527 | { |
| 528 | CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.ChoiceItem", new CodeTypeOfExpression("ISchemaElement"))); |
| 529 | constructor.Statements.Add(addItemInvoke); |
| 530 | } |
| 531 | else |
| 532 | { |
| 533 | CodeMethodInvokeExpression addItemInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddItem", new CodeObjectCreateExpression("ElementCollection.SequenceItem", new CodeTypeOfExpression("ISchemaElement"), new CodeSnippetExpression("0"), new CodeSnippetExpression("-1"))); |
| 534 | constructor.Statements.Add(addItemInvoke); |
| 535 | } |
| 536 | |
| 537 | continue; |
| 538 | } |
| 539 | |
| 540 | XmlSchemaGroupBase schemaGroupBase = obj as XmlSchemaGroupBase; |
| 541 | if (schemaGroupBase != null) |
| 542 | { |
| 543 | CodeVariableReferenceExpression nestedCollectionReference = ProcessSchemaGroup(schemaGroupBase, constructor, createChildMethod, depth + 1); |
| 544 | CodeMethodInvokeExpression addCollectionInvoke = new CodeMethodInvokeExpression(collectionVariableReference, "AddCollection", nestedCollectionReference); |
| 545 | constructor.Statements.Add(addCollectionInvoke); |
| 546 | |
| 547 | continue; |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | return collectionVariableReference; |
| 552 | } |
| 553 | |
| 554 | /// <summary> |
| 555 | /// Processes an XmlSchemaSimpleContentExtension into corresponding types. |
| 556 | /// </summary> |
| 557 | /// <param name="typeName">Name of the type being generated.</param> |
| 558 | /// <param name="simpleContent">XmlSchemaSimpleContentExtension being processed.</param> |
| 559 | /// <param name="documentation">Documentation for the simple content.</param> |
| 560 | /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param> |
| 561 | /// <param name="outputXmlMethod">Method to use when outputting Xml.</param> |
| 562 | /// <param name="setAttributeMethod">Method to use when setting an attribute.</param> |
| 563 | /// <param name="abstractClass">If true, generate an abstract class.</param> |
| 564 | private static void ProcessSimpleContent(string typeName, XmlSchemaSimpleContentExtension simpleContent, string documentation, CodeNamespace codeNamespace, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod, bool abstractClass) |
| 565 | { |
| 566 | CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(typeName); |
| 567 | typeDeclaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); |
| 568 | typeDeclaration.Attributes = MemberAttributes.Public; |
| 569 | typeDeclaration.IsClass = true; |
| 570 | |
| 571 | if (documentation != null) |
| 572 | { |
| 573 | GenerateSummaryComment(typeDeclaration.Comments, documentation); |
| 574 | } |
| 575 | |
| 576 | if (abstractClass) |
| 577 | { |
| 578 | typeDeclaration.TypeAttributes = System.Reflection.TypeAttributes.Abstract | System.Reflection.TypeAttributes.Public; |
| 579 | } |
| 580 | |
| 581 | // TODO: Handle xs:anyAttribute here. |
| 582 | foreach (XmlSchemaAttribute schemaAttribute in simpleContent.Attributes) |
| 583 | { |
| 584 | ProcessAttribute(schemaAttribute, typeDeclaration, outputXmlMethod, setAttributeMethod); |
| 585 | } |
| 586 | |
| 587 | // This needs to come last, so that the generation code generates the inner content after the attributes. |
| 588 | string contentDocumentation = GetDocumentation(simpleContent.Annotation); |
| 589 | GenerateFieldAndProperty("Content", (string)simpleTypeNamesToClrTypeNames[simpleContent.BaseTypeName.Name], typeDeclaration, outputXmlMethod, setAttributeMethod, null, contentDocumentation, true, false); |
| 590 | |
| 591 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISchemaElement")); |
| 592 | typeDeclaration.BaseTypes.Add(new CodeTypeReference("ISetAttributes")); |
| 593 | |
| 594 | CodeMemberField parentField = new CodeMemberField("ISchemaElement", "parentElement"); |
| 595 | typeDeclaration.Members.Add(parentField); |
| 596 | |
| 597 | CodeMemberProperty parentProperty = new CodeMemberProperty(); |
| 598 | parentProperty.Attributes = MemberAttributes.Public; |
| 599 | parentProperty.ImplementationTypes.Add("ISchemaElement"); |
| 600 | parentProperty.Name = "ParentElement"; |
| 601 | parentProperty.Type = new CodeTypeReference("ISchemaElement"); |
| 602 | parentProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"))); |
| 603 | parentProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "parentElement"), new CodeVariableReferenceExpression("value"))); |
| 604 | typeDeclaration.Members.Add(parentProperty); |
| 605 | |
| 606 | if (outputXmlMethod.Statements.Count > 8) |
| 607 | { |
| 608 | outputXmlMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); |
| 609 | } |
| 610 | |
| 611 | if (setAttributeMethod.Statements.Count > 8) |
| 612 | { |
| 613 | setAttributeMethod.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")); |
| 614 | } |
| 615 | |
| 616 | typeDeclaration.Members.Add(outputXmlMethod); |
| 617 | typeDeclaration.Members.Add(setAttributeMethod); |
| 618 | codeNamespace.Types.Add(typeDeclaration); |
| 619 | } |
| 620 | |
| 621 | /// <summary> |
| 622 | /// Processes an attribute, generating the required field and property. Potentially generates |
| 623 | /// an enum for an attribute restriction. |
| 624 | /// </summary> |
| 625 | /// <param name="attribute">Attribute element being processed.</param> |
| 626 | /// <param name="typeDeclaration">CodeTypeDeclaration to be used when outputting code.</param> |
| 627 | /// <param name="outputXmlMethod">Member method for the OutputXml method.</param> |
| 628 | /// <param name="setAttributeMethod">Member method for the SetAttribute method.</param> |
| 629 | private static void ProcessAttribute(XmlSchemaAttribute attribute, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod) |
| 630 | { |
| 631 | string attributeName = attribute.QualifiedName.Name; |
| 632 | string rawAttributeType = attribute.AttributeSchemaType.QualifiedName.Name; |
| 633 | string attributeType = null; |
| 634 | EnumDeclaration enumDeclaration = null; |
| 635 | if (rawAttributeType == null || rawAttributeType.Length == 0) |
| 636 | { |
| 637 | ProcessSimpleType(attributeName, attribute.AttributeSchemaType, true, out enumDeclaration, out attributeType); |
| 638 | |
| 639 | if (enumDeclaration != null) |
| 640 | { |
| 641 | typeDeclaration.Members.Add(enumDeclaration.TypeDeclaration); |
| 642 | AddEnumHelperMethods(enumDeclaration, typeDeclaration); |
| 643 | } |
| 644 | } |
| 645 | else |
| 646 | { |
| 647 | attributeType = (string)simpleTypeNamesToClrTypeNames[rawAttributeType]; |
| 648 | } |
| 649 | |
| 650 | string documentation = GetDocumentation(attribute.Annotation); |
| 651 | |
| 652 | // TODO: Handle required fields. |
| 653 | GenerateFieldAndProperty(attributeName, attributeType, typeDeclaration, outputXmlMethod, setAttributeMethod, enumDeclaration, documentation, false, false); |
| 654 | } |
| 655 | |
| 656 | /// <summary> |
| 657 | /// Gets the first sentence of a documentation element and returns it as a string. |
| 658 | /// </summary> |
| 659 | /// <param name="annotation">The annotation in which to look for a documentation element.</param> |
| 660 | /// <returns>The string representing the first sentence, or null if none found.</returns> |
| 661 | private static string GetDocumentation(XmlSchemaAnnotation annotation) |
| 662 | { |
| 663 | string documentation = null; |
| 664 | |
| 665 | if (annotation != null && annotation.Items != null) |
| 666 | { |
| 667 | foreach (XmlSchemaObject obj in annotation.Items) |
| 668 | { |
| 669 | XmlSchemaDocumentation schemaDocumentation = obj as XmlSchemaDocumentation; |
| 670 | if (schemaDocumentation != null) |
| 671 | { |
| 672 | if (schemaDocumentation.Markup.Length > 0) |
| 673 | { |
| 674 | XmlText text = schemaDocumentation.Markup[0] as XmlText; |
| 675 | if (text != null) |
| 676 | { |
| 677 | documentation = text.Value; |
| 678 | } |
| 679 | } |
| 680 | break; |
| 681 | } |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | if (documentation != null) |
| 686 | { |
| 687 | documentation = documentation.Trim(); |
| 688 | } |
| 689 | return documentation; |
| 690 | } |
| 691 | |
| 692 | /// <summary> |
| 693 | /// Makes a valid enum value out of the passed in value. May remove spaces, add 'Item' to the |
| 694 | /// start if it begins with an integer, or strip out punctuation. |
| 695 | /// </summary> |
| 696 | /// <param name="enumValue">Enum value to be processed.</param> |
| 697 | /// <returns>Enum value with invalid characters removed.</returns> |
| 698 | private static string MakeEnumValue(string enumValue) |
| 699 | { |
| 700 | if (Char.IsDigit(enumValue[0])) |
| 701 | { |
| 702 | enumValue = String.Concat("Item", enumValue); |
| 703 | } |
| 704 | |
| 705 | StringBuilder newValue = new StringBuilder(); |
| 706 | for (int i = 0; i < enumValue.Length; ++i) |
| 707 | { |
| 708 | if (!Char.IsPunctuation(enumValue[i]) && !Char.IsSymbol(enumValue[i]) && !Char.IsWhiteSpace(enumValue[i])) |
| 709 | { |
| 710 | newValue.Append(enumValue[i]); |
| 711 | } |
| 712 | } |
| 713 | |
| 714 | return newValue.ToString(); |
| 715 | } |
| 716 | |
| 717 | /// <summary> |
| 718 | /// Generates the private field and public property for a piece of data. |
| 719 | /// </summary> |
| 720 | /// <param name="propertyName">Name of the property being generated.</param> |
| 721 | /// <param name="typeName">Name of the type for the property.</param> |
| 722 | /// <param name="typeDeclaration">Type declaration into which the field and property should be placed.</param> |
| 723 | /// <param name="outputXmlMethod">Member method for the OutputXml method.</param> |
| 724 | /// <param name="setAttributeMethod">Member method for the SetAttribute method.</param> |
| 725 | /// <param name="enumDeclaration">EnumDeclaration, which is null unless called from a locally defined enum attribute.</param> |
| 726 | /// <param name="documentation">Comment string to be placed on the property.</param> |
| 727 | /// <param name="nestedContent">If true, the field will be placed in nested content when outputting to XML.</param> |
| 728 | /// <param name="requiredField">If true, the generated serialization code will throw if the field is not set.</param> |
| 729 | private static void GenerateFieldAndProperty(string propertyName, string typeName, CodeTypeDeclaration typeDeclaration, CodeMemberMethod outputXmlMethod, CodeMemberMethod setAttributeMethod, EnumDeclaration enumDeclaration, string documentation, bool nestedContent, bool requiredField) |
| 730 | { |
| 731 | string fieldName = String.Concat(propertyName.Substring(0, 1).ToLower(), propertyName.Substring(1), "Field"); |
| 732 | string fieldNameSet = String.Concat(fieldName, "Set"); |
| 733 | Type type = GetClrTypeByXmlName(typeName); |
| 734 | CodeMemberField fieldMember; |
| 735 | if (type == null) |
| 736 | { |
| 737 | fieldMember = new CodeMemberField(typeName, fieldName); |
| 738 | } |
| 739 | else |
| 740 | { |
| 741 | fieldMember = new CodeMemberField(type, fieldName); |
| 742 | } |
| 743 | fieldMember.Attributes = MemberAttributes.Private; |
| 744 | typeDeclaration.Members.Add(fieldMember); |
| 745 | typeDeclaration.Members.Add(new CodeMemberField(typeof(bool), fieldNameSet)); |
| 746 | |
| 747 | CodeMemberProperty propertyMember = new CodeMemberProperty(); |
| 748 | propertyMember.Attributes = MemberAttributes.Public | MemberAttributes.Final; |
| 749 | if (documentation != null) |
| 750 | { |
| 751 | GenerateSummaryComment(propertyMember.Comments, documentation); |
| 752 | } |
| 753 | propertyMember.Name = propertyName; |
| 754 | if (type == null) |
| 755 | { |
| 756 | propertyMember.Type = new CodeTypeReference(typeName); |
| 757 | } |
| 758 | else |
| 759 | { |
| 760 | propertyMember.Type = new CodeTypeReference(type); |
| 761 | } |
| 762 | |
| 763 | if (propertyMember.Name.StartsWith("src")) |
| 764 | { |
| 765 | propertyMember.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly")); |
| 766 | } |
| 767 | else if (StronglyTypedClasses.multiUppercaseNameRegex.Match(propertyMember.Name).Success) |
| 768 | { |
| 769 | propertyMember.CustomAttributes.Add(GetCodeAnalysisSuppressionAttribute("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased")); |
| 770 | } |
| 771 | |
| 772 | CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(); |
| 773 | returnStatement.Expression = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName); |
| 774 | propertyMember.GetStatements.Add(returnStatement); |
| 775 | |
| 776 | CodeAssignStatement assignmentStatement = new CodeAssignStatement(); |
| 777 | propertyMember.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet), new CodePrimitiveExpression(true))); |
| 778 | assignmentStatement.Left = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName); |
| 779 | assignmentStatement.Right = new CodePropertySetValueReferenceExpression(); |
| 780 | propertyMember.SetStatements.Add(assignmentStatement); |
| 781 | |
| 782 | CodeConditionStatement fieldSetStatement = new CodeConditionStatement(); |
| 783 | fieldSetStatement.Condition = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet); |
| 784 | |
| 785 | CodeAssignStatement fieldSetAttrStatement = new CodeAssignStatement(); |
| 786 | fieldSetAttrStatement.Left = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldNameSet); |
| 787 | fieldSetAttrStatement.Right = new CodePrimitiveExpression(true); |
| 788 | |
| 789 | CodeConditionStatement attributeNameMatchStatement = new CodeConditionStatement(); |
| 790 | attributeNameMatchStatement.Condition = new CodeBinaryOperatorExpression(new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), CodeBinaryOperatorType.IdentityEquality, new CodeVariableReferenceExpression("name")); |
| 791 | |
| 792 | string clrTypeName = (string)simpleTypeNamesToClrTypeNames[typeName]; |
| 793 | switch (clrTypeName) |
| 794 | { |
| 795 | case "string": |
| 796 | if (nestedContent) |
| 797 | { |
| 798 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName))); |
| 799 | attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeVariableReferenceExpression("value"))); |
| 800 | } |
| 801 | else |
| 802 | { |
| 803 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName))); |
| 804 | attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeVariableReferenceExpression("value"))); |
| 805 | } |
| 806 | break; |
| 807 | case "bool": |
| 808 | if (nestedContent) |
| 809 | { |
| 810 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 811 | } |
| 812 | else |
| 813 | { |
| 814 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 815 | } |
| 816 | attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToBoolean", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 817 | break; |
| 818 | case "int": |
| 819 | case "long": |
| 820 | if (nestedContent) |
| 821 | { |
| 822 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 823 | } |
| 824 | else |
| 825 | { |
| 826 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 827 | } |
| 828 | attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToInt32", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 829 | break; |
| 830 | default: |
| 831 | if (typeName == "DateTime") |
| 832 | { |
| 833 | if (nestedContent) |
| 834 | { |
| 835 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteString", new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 836 | } |
| 837 | else |
| 838 | { |
| 839 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeMethodInvokeExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), "ToString", new CodePrimitiveExpression("yyyy-MM-ddTHH:mm:ss"), new CodePropertyReferenceExpression(new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture"), "DateTimeFormat")))); |
| 840 | } |
| 841 | attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Convert"), "ToDateTime", new CodeVariableReferenceExpression("value"), new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("CultureInfo"), "InvariantCulture")))); |
| 842 | break; |
| 843 | } |
| 844 | |
| 845 | if (enumDeclaration == null) |
| 846 | { |
| 847 | GenerateOutputForEnum(fieldSetStatement, attributeNameMatchStatement, typeNamesToEnumDeclarations[typeName], fieldName, propertyName); |
| 848 | } |
| 849 | else |
| 850 | { |
| 851 | GenerateOutputForEnum(fieldSetStatement, attributeNameMatchStatement, enumDeclaration, fieldName, propertyName); |
| 852 | } |
| 853 | break; |
| 854 | } |
| 855 | |
| 856 | attributeNameMatchStatement.TrueStatements.Add(fieldSetAttrStatement); |
| 857 | |
| 858 | // TODO: Add throw to falseStatements if required field not set. |
| 859 | outputXmlMethod.Statements.Add(fieldSetStatement); |
| 860 | setAttributeMethod.Statements.Add(attributeNameMatchStatement); |
| 861 | |
| 862 | typeDeclaration.Members.Add(propertyMember); |
| 863 | } |
| 864 | |
| 865 | /// <summary> |
| 866 | /// Generates output for an enum type. Will generate a switch statement for normal enums, and if statements |
| 867 | /// for a flags enum. |
| 868 | /// </summary> |
| 869 | /// <param name="fieldSetStatement">If statement to add statements to.</param> |
| 870 | /// <param name="attributeNameMatchStatement">If statement to add statements to.</param> |
| 871 | /// <param name="enumDeclaration">Enum declaration for this field. Could be locally defined enum or global.</param> |
| 872 | /// <param name="fieldName">Name of the private field.</param> |
| 873 | /// <param name="propertyName">Name of the property (and XML attribute).</param> |
| 874 | private static void GenerateOutputForEnum(CodeConditionStatement fieldSetStatement, CodeConditionStatement attributeNameMatchStatement, EnumDeclaration enumDeclaration, string fieldName, string propertyName) |
| 875 | { |
| 876 | CodeTypeDeclaration enumParent = enumsToParseMethodClasses[enumDeclaration]; |
| 877 | |
| 878 | if (enumDeclaration.Flags) |
| 879 | { |
| 880 | CodeVariableDeclarationStatement outputValueVariable = new CodeVariableDeclarationStatement(typeof(string), "outputValue", new CodeSnippetExpression("\"\"")); |
| 881 | fieldSetStatement.TrueStatements.Add(outputValueVariable); |
| 882 | |
| 883 | foreach (string key in enumDeclaration.Values) |
| 884 | { |
| 885 | CodeConditionStatement enumIfStatement = new CodeConditionStatement(); |
| 886 | enumIfStatement.Condition = new CodeBinaryOperatorExpression(new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), CodeBinaryOperatorType.BitwiseAnd, new CodePropertyReferenceExpression(new CodeSnippetExpression(enumDeclaration.Name), MakeEnumValue(key))), CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("0")); |
| 887 | CodeConditionStatement lengthIfStatement = new CodeConditionStatement(); |
| 888 | lengthIfStatement.Condition = new CodeBinaryOperatorExpression(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("outputValue"), "Length"), CodeBinaryOperatorType.IdentityInequality, new CodeSnippetExpression("0")); |
| 889 | lengthIfStatement.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("outputValue"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("outputValue"), CodeBinaryOperatorType.Add, new CodeSnippetExpression("\" \"")))); |
| 890 | enumIfStatement.TrueStatements.Add(lengthIfStatement); |
| 891 | enumIfStatement.TrueStatements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression("outputValue"), new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("outputValue"), CodeBinaryOperatorType.Add, new CodeSnippetExpression(String.Concat("\"", key, "\""))))); |
| 892 | fieldSetStatement.TrueStatements.Add(enumIfStatement); |
| 893 | } |
| 894 | |
| 895 | attributeNameMatchStatement.TrueStatements.Add(new CodeMethodInvokeExpression( |
| 896 | new CodeTypeReferenceExpression(enumParent.Name), |
| 897 | String.Concat("TryParse", enumDeclaration.Name), |
| 898 | new CodeVariableReferenceExpression("value"), |
| 899 | new CodeDirectionExpression(FieldDirection.Out, new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)))); |
| 900 | |
| 901 | fieldSetStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeSnippetExpression(String.Concat("outputValue")))); |
| 902 | } |
| 903 | else |
| 904 | { |
| 905 | foreach (string key in enumDeclaration.Values) |
| 906 | { |
| 907 | CodeConditionStatement enumOutStatement = new CodeConditionStatement(); |
| 908 | enumOutStatement.Condition = new CodeBinaryOperatorExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), CodeBinaryOperatorType.ValueEquality, new CodePropertyReferenceExpression(new CodeSnippetExpression(enumDeclaration.Name), MakeEnumValue(key))); |
| 909 | enumOutStatement.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("writer"), "WriteAttributeString", new CodeSnippetExpression(String.Concat("\"", propertyName, "\"")), new CodeSnippetExpression(String.Concat("\"", key, "\"")))); |
| 910 | fieldSetStatement.TrueStatements.Add(enumOutStatement); |
| 911 | } |
| 912 | |
| 913 | attributeNameMatchStatement.TrueStatements.Add(new CodeAssignStatement( |
| 914 | new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), |
| 915 | new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(enumParent.Name), |
| 916 | String.Concat("Parse", enumDeclaration.Name), |
| 917 | new CodeVariableReferenceExpression("value")))); |
| 918 | } |
| 919 | } |
| 920 | |
| 921 | /// <summary> |
| 922 | /// Generates a summary comment. |
| 923 | /// </summary> |
| 924 | /// <param name="comments">Comments collection to add the comments to.</param> |
| 925 | /// <param name="content">Content of the comment.</param> |
| 926 | private static void GenerateSummaryComment(CodeCommentStatementCollection comments, string content) |
| 927 | { |
| 928 | using (StringWriter sw = new StringWriter()) |
| 929 | { |
| 930 | XmlTextWriter writer = null; |
| 931 | |
| 932 | // create the comment as xml to ensure proper escaping of special xml characters |
| 933 | try |
| 934 | { |
| 935 | writer = new XmlTextWriter(sw); |
| 936 | writer.Indentation = 0; |
| 937 | |
| 938 | writer.WriteStartElement("summary"); |
| 939 | writer.WriteString(Environment.NewLine); |
| 940 | |
| 941 | string nextComment; |
| 942 | int newlineIndex = content.IndexOf(Environment.NewLine); |
| 943 | int offset = 0; |
| 944 | while (newlineIndex != -1) |
| 945 | { |
| 946 | nextComment = content.Substring(offset, newlineIndex - offset).Trim(); |
| 947 | writer.WriteString(nextComment); |
| 948 | writer.WriteString(Environment.NewLine); |
| 949 | offset = newlineIndex + Environment.NewLine.Length; |
| 950 | newlineIndex = content.IndexOf(Environment.NewLine, offset); |
| 951 | } |
| 952 | nextComment = content.Substring(offset).Trim(); |
| 953 | writer.WriteString(nextComment); |
| 954 | writer.WriteString(Environment.NewLine); |
| 955 | |
| 956 | writer.WriteEndElement(); |
| 957 | } |
| 958 | finally |
| 959 | { |
| 960 | if (null != writer) |
| 961 | { |
| 962 | writer.Close(); |
| 963 | } |
| 964 | } |
| 965 | |
| 966 | // create the comment statements (one per line of xml) |
| 967 | using (StringReader sr = new StringReader(sw.ToString())) |
| 968 | { |
| 969 | string line; |
| 970 | |
| 971 | while (null != (line = sr.ReadLine())) |
| 972 | { |
| 973 | comments.Add(new CodeCommentStatement(line, true)); |
| 974 | } |
| 975 | } |
| 976 | } |
| 977 | } |
| 978 | |
| 979 | /// <summary> |
| 980 | /// Gets the CLR type for simple XML type. |
| 981 | /// </summary> |
| 982 | /// <param name="typeName">Plain text name of type.</param> |
| 983 | /// <returns>Type corresponding to parameter.</returns> |
| 984 | private static Type GetClrTypeByXmlName(string typeName) |
| 985 | { |
| 986 | switch (typeName) |
| 987 | { |
| 988 | case "bool": |
| 989 | return typeof(bool); |
| 990 | case "int": |
| 991 | return typeof(int); |
| 992 | case "long": |
| 993 | return typeof(long); |
| 994 | case "string": |
| 995 | return typeof(string); |
| 996 | default: |
| 997 | return null; |
| 998 | } |
| 999 | } |
| 1000 | |
| 1001 | /// <summary> |
| 1002 | /// Processes an XmlSchemaSimpleType into corresponding types. |
| 1003 | /// </summary> |
| 1004 | /// <param name="simpleType">XmlSchemaSimpleType to be processed.</param> |
| 1005 | /// <param name="codeNamespace">CodeNamespace to be used when outputting code.</param> |
| 1006 | private static void ProcessSimpleType(XmlSchemaSimpleType simpleType, CodeNamespace codeNamespace) |
| 1007 | { |
| 1008 | EnumDeclaration enumDeclaration; |
| 1009 | string simpleTypeName = simpleType.Name; |
| 1010 | string baseTypeName; |
| 1011 | |
| 1012 | ProcessSimpleType(simpleTypeName, simpleType, false, out enumDeclaration, out baseTypeName); |
| 1013 | |
| 1014 | simpleTypeNamesToClrTypeNames.Add(simpleTypeName, baseTypeName); |
| 1015 | |
| 1016 | if (enumDeclaration != null) |
| 1017 | { |
| 1018 | codeNamespace.Types.Add(enumDeclaration.TypeDeclaration); |
| 1019 | typeNamesToEnumDeclarations.Add(simpleTypeName, enumDeclaration); |
| 1020 | AddEnumHelperMethods(enumDeclaration, codeNamespace); |
| 1021 | } |
| 1022 | } |
| 1023 | |
| 1024 | /// <summary> |
| 1025 | /// Processes an XmlSchemaSimpleType into corresponding code. |
| 1026 | /// </summary> |
| 1027 | /// <param name="simpleTypeName">Name for the type.</param> |
| 1028 | /// <param name="simpleType">XmlSchemaSimpleType to be processed.</param> |
| 1029 | /// <param name="codeNamespace">CodeNamespace to be used when outputting code for global types.</param> |
| 1030 | /// <param name="parentTypeDeclaration">CodeTypeDeclaration to be used when outputting code for nested types.</param> |
| 1031 | /// <param name="outputXmlMethod">Member method for the OutputXml method for nested types.</param> |
| 1032 | /// <param name="setAttributeMethod">Member method for the SetAttribute method for nested types.</param> |
| 1033 | private static void ProcessSimpleType(string simpleTypeName, XmlSchemaSimpleType simpleType, bool nestedType, out EnumDeclaration enumDeclaration, out string baseTypeName) |
| 1034 | { |
| 1035 | enumDeclaration = null; |
| 1036 | baseTypeName = null; |
| 1037 | |
| 1038 | // XSD supports simpleTypes derived by union, list, or restriction; restrictions can have any |
| 1039 | // combination of pattern, enumeration, length, and more; lists can contain any other simpleType. |
| 1040 | // XsdGen, in contrast, only supports a limited set of values... |
| 1041 | // Unions are weakly supported by just using the first member type |
| 1042 | // restrictions must either be all enumeration or a single pattern, a list must be of a |
| 1043 | // single simpleType which itself is only a restriction of enumeration. |
| 1044 | if (simpleType.Content is XmlSchemaSimpleTypeUnion) |
| 1045 | { |
| 1046 | XmlSchemaSimpleTypeUnion union = simpleType.Content as XmlSchemaSimpleTypeUnion; |
| 1047 | if (union.MemberTypes.Length > 0) |
| 1048 | { |
| 1049 | baseTypeName = union.MemberTypes[0].Name; |
| 1050 | return; |
| 1051 | } |
| 1052 | else |
| 1053 | { |
| 1054 | baseTypeName = "string"; |
| 1055 | return; |
| 1056 | } |
| 1057 | } |
| 1058 | |
| 1059 | bool listType = false; // XSD lists become [Flag]enums in C#... |
| 1060 | XmlSchemaSimpleTypeList simpleTypeList = simpleType.Content as XmlSchemaSimpleTypeList; |
| 1061 | XmlSchemaSimpleTypeRestriction simpleTypeRestriction = simpleType.Content as XmlSchemaSimpleTypeRestriction; |
| 1062 | |
| 1063 | if (simpleTypeList != null) |
| 1064 | { |
| 1065 | baseTypeName = simpleTypeList.ItemTypeName.Name; |
| 1066 | |
| 1067 | if (String.IsNullOrEmpty(baseTypeName)) |
| 1068 | { |
| 1069 | simpleTypeRestriction = simpleTypeList.ItemType.Content as XmlSchemaSimpleTypeRestriction; |
| 1070 | if (simpleTypeRestriction == null) |
| 1071 | { |
| 1072 | string appName = typeof(XsdGen).Assembly.GetName().Name; |
| 1073 | throw new NotImplementedException(string.Format("{0} does not support a <list> that does not contain a <simpleType>/<restriction>.", appName)); |
| 1074 | } |
| 1075 | |
| 1076 | listType = true; |
| 1077 | } |
| 1078 | else |
| 1079 | { |
| 1080 | // We expect to find an existing enum already declared! |
| 1081 | EnumDeclaration existingEnumDeclaration = typeNamesToEnumDeclarations[baseTypeName]; |
| 1082 | // TODO: do we need to further alter the Flags setter code because of the helper stuff? |
| 1083 | // As far as I can tell, this code is never exercised by our existing XSDs! |
| 1084 | existingEnumDeclaration.SetFlags(); |
| 1085 | } |
| 1086 | } |
| 1087 | |
| 1088 | if (simpleTypeRestriction == null) |
| 1089 | { |
| 1090 | string appName = typeof(XsdGen).Assembly.GetName().Name; |
| 1091 | throw new NotImplementedException(string.Format("{0} does not understand this simpleType!", appName)); |
| 1092 | } |
| 1093 | |
| 1094 | bool foundPattern = false; |
| 1095 | foreach (XmlSchemaFacet facet in simpleTypeRestriction.Facets) |
| 1096 | { |
| 1097 | XmlSchemaEnumerationFacet enumFacet = facet as XmlSchemaEnumerationFacet; |
| 1098 | XmlSchemaPatternFacet patternFacet = facet as XmlSchemaPatternFacet; |
| 1099 | |
| 1100 | if (enumFacet != null) |
| 1101 | { |
| 1102 | if (foundPattern) |
| 1103 | { |
| 1104 | string appName = typeof(XsdGen).Assembly.GetName().Name; |
| 1105 | throw new NotImplementedException(string.Format("{0} does not support restrictions containing both <pattern> and <enumeration>.", appName)); |
| 1106 | } |
| 1107 | |
| 1108 | if (enumDeclaration == null) |
| 1109 | { |
| 1110 | // For nested types, the simple name comes from the attribute name, with "Type" appended |
| 1111 | // to prevent name collision with the attribute member itself. |
| 1112 | if (nestedType) |
| 1113 | { |
| 1114 | simpleTypeName = String.Concat(simpleTypeName, "Type"); |
| 1115 | } |
| 1116 | baseTypeName = simpleTypeName; |
| 1117 | |
| 1118 | string typeDocumentation = GetDocumentation(simpleType.Annotation); |
| 1119 | enumDeclaration = new EnumDeclaration(simpleTypeName, typeDocumentation); |
| 1120 | } |
| 1121 | |
| 1122 | string documentation = GetDocumentation(enumFacet.Annotation); |
| 1123 | enumDeclaration.AddValue(enumFacet.Value, documentation); |
| 1124 | } |
| 1125 | |
| 1126 | if (patternFacet != null) |
| 1127 | { |
| 1128 | if (enumDeclaration != null) |
| 1129 | { |
| 1130 | string appName = typeof(XsdGen).Assembly.GetName().Name; |
| 1131 | throw new NotImplementedException(string.Format("{0} does not support restrictions containing both <pattern> and <enumeration>.", appName)); |
| 1132 | } |
| 1133 | |
| 1134 | if (foundPattern) |
| 1135 | { |
| 1136 | string appName = typeof(XsdGen).Assembly.GetName().Name; |
| 1137 | throw new NotImplementedException(string.Format("{0} does not support restrictions multiple <pattern> elements.", appName)); |
| 1138 | } |
| 1139 | |
| 1140 | foundPattern = true; |
| 1141 | } |
| 1142 | } |
| 1143 | |
| 1144 | if (enumDeclaration != null && listType) |
| 1145 | { |
| 1146 | enumDeclaration.SetFlags(); |
| 1147 | } |
| 1148 | |
| 1149 | if (String.IsNullOrEmpty(baseTypeName)) |
| 1150 | { |
| 1151 | baseTypeName = (string)simpleTypeNamesToClrTypeNames[simpleTypeRestriction.BaseTypeName.Name]; |
| 1152 | } |
| 1153 | } |
| 1154 | |
| 1155 | /// <summary> |
| 1156 | /// Creates an attribute declaration indicating generated code including the tool name and version. |
| 1157 | /// </summary> |
| 1158 | /// <returns>GeneratedCodeAttribute declearation.</returns> |
| 1159 | private static CodeAttributeDeclaration GetGeneratedCodeAttribute() |
| 1160 | { |
| 1161 | AssemblyName generatorAssemblyName = typeof(XsdGen).Assembly.GetName(); |
| 1162 | return new CodeAttributeDeclaration("GeneratedCode", |
| 1163 | new CodeAttributeArgument(new CodePrimitiveExpression(generatorAssemblyName.Name)), |
| 1164 | new CodeAttributeArgument(new CodePrimitiveExpression(generatorAssemblyName.Version.ToString()))); |
| 1165 | } |
| 1166 | |
| 1167 | /// <summary> |
| 1168 | /// Creates a code statement to throw an exception if an argument is null. |
| 1169 | /// </summary> |
| 1170 | /// <param name="argumentName">Name of the argument to check.</param> |
| 1171 | /// <param name="nullOrEmpty">True to check for null-or-empty instead of just null</param> |
| 1172 | /// <returns>Code condition statement.</returns> |
| 1173 | private static CodeConditionStatement GetArgumentNullCheckStatement(string argumentName, bool nullOrEmpty) |
| 1174 | { |
| 1175 | CodeConditionStatement conditionStatement = new CodeConditionStatement(); |
| 1176 | if (nullOrEmpty) |
| 1177 | { |
| 1178 | conditionStatement.Condition = new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("String"), "IsNullOrEmpty"), new CodeVariableReferenceExpression(argumentName)); |
| 1179 | } |
| 1180 | else |
| 1181 | { |
| 1182 | conditionStatement.Condition = new CodeBinaryOperatorExpression(new CodePrimitiveExpression(null), CodeBinaryOperatorType.ValueEquality, new CodeVariableReferenceExpression(argumentName)); |
| 1183 | } |
| 1184 | |
| 1185 | conditionStatement.TrueStatements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("ArgumentNullException", new CodeSnippetExpression(String.Concat("\"", argumentName, "\""))))); |
| 1186 | return conditionStatement; |
| 1187 | } |
| 1188 | |
| 1189 | /// <summary> |
| 1190 | /// Creates an attribute declaration to suppress a particular code-analysis message. |
| 1191 | /// </summary> |
| 1192 | /// <param name="category">Code analysis category, such as "Microsoft.Design"</param> |
| 1193 | /// <param name="checkId">Code analysis ID number.</param> |
| 1194 | /// <returns>SuppressMessageAttribute declaration.</returns> |
| 1195 | private static CodeAttributeDeclaration GetCodeAnalysisSuppressionAttribute(string category, string checkId) |
| 1196 | { |
| 1197 | return new CodeAttributeDeclaration("SuppressMessage", |
| 1198 | new CodeAttributeArgument(new CodePrimitiveExpression(category)), |
| 1199 | new CodeAttributeArgument(new CodePrimitiveExpression(checkId))); |
| 1200 | } |
| 1201 | |
| 1202 | /// <summary> |
| 1203 | /// Class representing an enum declaration. |
| 1204 | /// </summary> |
| 1205 | internal class EnumDeclaration |
| 1206 | { |
| 1207 | private string enumTypeName; |
| 1208 | private CodeTypeDeclaration declaration; |
| 1209 | private bool flags; |
| 1210 | private StringCollection enumValues; |
| 1211 | |
| 1212 | /// <summary> |
| 1213 | /// Creates a new enum declaration with the given name. |
| 1214 | /// </summary> |
| 1215 | /// <param name="enumTypeName">Name of the type for the enum.</param> |
| 1216 | /// <param name="documentation">Documentation for the enum type.</param> |
| 1217 | public EnumDeclaration(string enumTypeName, string documentation) |
| 1218 | { |
| 1219 | this.enumTypeName = enumTypeName; |
| 1220 | |
| 1221 | this.declaration = new CodeTypeDeclaration(enumTypeName); |
| 1222 | this.declaration.CustomAttributes.Add(GetGeneratedCodeAttribute()); |
| 1223 | this.declaration.Attributes = MemberAttributes.Public; |
| 1224 | this.declaration.IsEnum = true; |
| 1225 | |
| 1226 | if (documentation != null) |
| 1227 | { |
| 1228 | GenerateSummaryComment(this.declaration.Comments, documentation); |
| 1229 | } |
| 1230 | |
| 1231 | this.enumValues = new StringCollection(); |
| 1232 | } |
| 1233 | |
| 1234 | public CodeTypeDeclaration TypeDeclaration |
| 1235 | { |
| 1236 | get { return this.declaration; } |
| 1237 | } |
| 1238 | |
| 1239 | /// <summary> |
| 1240 | /// Gets the enumeration values. |
| 1241 | /// </summary> |
| 1242 | /// <value>The enumeration values.</value> |
| 1243 | public ICollection Values |
| 1244 | { |
| 1245 | get { return this.enumValues; } |
| 1246 | } |
| 1247 | |
| 1248 | /// <summary> |
| 1249 | /// Gets the enumeration name. |
| 1250 | /// </summary> |
| 1251 | /// <value>The enumeration name.</value> |
| 1252 | public string Name |
| 1253 | { |
| 1254 | get { return this.enumTypeName; } |
| 1255 | } |
| 1256 | |
| 1257 | /// <summary> |
| 1258 | /// Gets the enumeration flags property. |
| 1259 | /// </summary> |
| 1260 | /// <value>Whether the enumeration is a [Flags] type.</value> |
| 1261 | public bool Flags |
| 1262 | { |
| 1263 | get { return this.flags; } |
| 1264 | } |
| 1265 | |
| 1266 | /// <summary> |
| 1267 | /// Sets the [Flags] property on the enumeration. Once set, this cannot be undone. |
| 1268 | /// </summary> |
| 1269 | public void SetFlags() |
| 1270 | { |
| 1271 | if (this.flags) |
| 1272 | { |
| 1273 | return; |
| 1274 | } |
| 1275 | |
| 1276 | this.flags = true; |
| 1277 | |
| 1278 | this.declaration.CustomAttributes.Add(new CodeAttributeDeclaration("Flags")); |
| 1279 | SwitchToNoneValue(); |
| 1280 | |
| 1281 | int enumValue = 0; |
| 1282 | foreach (CodeMemberField enumField in this.declaration.Members) |
| 1283 | { |
| 1284 | enumField.InitExpression = new CodeSnippetExpression(enumValue.ToString()); |
| 1285 | if (enumValue == 0) |
| 1286 | { |
| 1287 | enumValue = 1; |
| 1288 | } |
| 1289 | else |
| 1290 | { |
| 1291 | enumValue *= 2; |
| 1292 | } |
| 1293 | } |
| 1294 | } |
| 1295 | |
| 1296 | private void InjectIllegalAndNotSetValues() |
| 1297 | { |
| 1298 | CodeMemberField memberIllegal = new CodeMemberField(typeof(int), "IllegalValue"); |
| 1299 | CodeMemberField memberNotSet = new CodeMemberField(typeof(int), "NotSet"); |
| 1300 | |
| 1301 | memberIllegal.InitExpression = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(int)), "MaxValue"); |
| 1302 | // Using "-1" for "NotSet" ensure that the next value is zero, which is consistent |
| 1303 | // with older (3.0) behavior. |
| 1304 | memberNotSet.InitExpression = new CodePrimitiveExpression(-1); |
| 1305 | |
| 1306 | this.declaration.Members.Insert(0, memberIllegal); |
| 1307 | this.declaration.Members.Insert(1, memberNotSet); |
| 1308 | } |
| 1309 | |
| 1310 | private void SwitchToNoneValue() |
| 1311 | { |
| 1312 | if (this.enumValues.Count > 0) |
| 1313 | { |
| 1314 | // Remove the "IllegalValue" and "NotSet" values first. |
| 1315 | this.declaration.Members.RemoveAt(0); |
| 1316 | this.declaration.Members.RemoveAt(0); |
| 1317 | |
| 1318 | CodeMemberField memberNone = new CodeMemberField(typeof(int), "None"); |
| 1319 | memberNone.InitExpression = new CodePrimitiveExpression(0); |
| 1320 | |
| 1321 | this.declaration.Members.Insert(0, memberNone); |
| 1322 | } |
| 1323 | } |
| 1324 | |
| 1325 | /// <summary> |
| 1326 | /// Add a value to the enumeration. |
| 1327 | /// </summary> |
| 1328 | /// <param name="enumValue">The value to add.</param> |
| 1329 | public void AddValue(string enumValue, string documentation) |
| 1330 | { |
| 1331 | if (this.enumValues.Count == 0) |
| 1332 | { |
| 1333 | InjectIllegalAndNotSetValues(); |
| 1334 | } |
| 1335 | |
| 1336 | this.enumValues.Add(enumValue); |
| 1337 | CodeMemberField memberField = new CodeMemberField(typeof(int), MakeEnumValue(enumValue)); |
| 1338 | //memberField.Attributes |
| 1339 | this.declaration.Members.Add(memberField); |
| 1340 | if (documentation != null) |
| 1341 | { |
| 1342 | GenerateSummaryComment(memberField.Comments, documentation); |
| 1343 | } |
| 1344 | } |
| 1345 | } |
| 1346 | |
| 1347 | private static void AddEnumHelperMethods(EnumDeclaration enumDeclaration, CodeNamespace codeNamespace) |
| 1348 | { |
| 1349 | if (enumHelperClass == null) |
| 1350 | { |
| 1351 | enumHelperClass = new CodeTypeDeclaration("Enums"); |
| 1352 | enumHelperClass.CustomAttributes.Add(GetGeneratedCodeAttribute()); |
| 1353 | // The static and final attributes don't seem to get applied, but we'd prefer if they were. |
| 1354 | enumHelperClass.Attributes = MemberAttributes.Public | MemberAttributes.Static | MemberAttributes.Final; |
| 1355 | codeNamespace.Types.Add(enumHelperClass); |
| 1356 | } |
| 1357 | |
| 1358 | AddEnumHelperMethods(enumDeclaration, enumHelperClass); |
| 1359 | } |
| 1360 | |
| 1361 | private static void AddEnumHelperMethods(EnumDeclaration enumDeclaration, CodeTypeDeclaration parentType) |
| 1362 | { |
| 1363 | CodeTypeReference stringType = new CodeTypeReference(typeof(string)); |
| 1364 | CodeTypeReference boolType = new CodeTypeReference(typeof(bool)); |
| 1365 | CodeTypeReference enumType = new CodeTypeReference(typeof(Enum)); |
| 1366 | CodeTypeReference newEnumType = new CodeTypeReference(enumDeclaration.Name); |
| 1367 | |
| 1368 | CodePrimitiveExpression falseValue = new CodePrimitiveExpression(false); |
| 1369 | CodePrimitiveExpression trueValue = new CodePrimitiveExpression(true); |
| 1370 | CodeMethodReturnStatement returnFalse = new CodeMethodReturnStatement(falseValue); |
| 1371 | CodeMethodReturnStatement returnTrue = new CodeMethodReturnStatement(trueValue); |
| 1372 | |
| 1373 | string parseMethodName = String.Concat("Parse", enumDeclaration.Name); |
| 1374 | string tryParseMethodName = String.Concat("TryParse", enumDeclaration.Name); |
| 1375 | |
| 1376 | CodeFieldReferenceExpression defaultEnumValue = null; |
| 1377 | CodeFieldReferenceExpression illegalEnumValue = null; |
| 1378 | bool addParse = true; |
| 1379 | if (enumDeclaration.Flags) |
| 1380 | { |
| 1381 | defaultEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "None"); |
| 1382 | illegalEnumValue = defaultEnumValue; |
| 1383 | // Because there's no "IllegalValue" for [Flags] enums, we can't create the Parse() |
| 1384 | // method. We can still create the TryParse() method, though! |
| 1385 | addParse = false; |
| 1386 | } |
| 1387 | else |
| 1388 | { |
| 1389 | defaultEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "NotSet"); |
| 1390 | illegalEnumValue = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), "IllegalValue"); |
| 1391 | } |
| 1392 | |
| 1393 | if (addParse) |
| 1394 | { |
| 1395 | CodeMemberMethod parseNewEnum = new CodeMemberMethod(); |
| 1396 | GenerateSummaryComment(parseNewEnum.Comments, String.Format("Parses a {0} from a string.", enumDeclaration.Name)); |
| 1397 | parseNewEnum.Attributes = MemberAttributes.Public | MemberAttributes.Static; |
| 1398 | parseNewEnum.Name = parseMethodName; |
| 1399 | parseNewEnum.ReturnType = newEnumType; |
| 1400 | parseNewEnum.Parameters.Add(new CodeParameterDeclarationExpression(stringType, "value")); |
| 1401 | |
| 1402 | parseNewEnum.Statements.Add(new CodeVariableDeclarationStatement(newEnumType, "parsedValue")); |
| 1403 | |
| 1404 | // Just delegate to the TryParse version... |
| 1405 | parseNewEnum.Statements.Add(new CodeMethodInvokeExpression( |
| 1406 | new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(parentType.Name), tryParseMethodName), |
| 1407 | new CodeArgumentReferenceExpression("value"), |
| 1408 | new CodeDirectionExpression(FieldDirection.Out, new CodeVariableReferenceExpression("parsedValue")))); |
| 1409 | |
| 1410 | parseNewEnum.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("parsedValue"))); |
| 1411 | parentType.Members.Add(parseNewEnum); |
| 1412 | } |
| 1413 | |
| 1414 | CodeMemberMethod tryParseNewEnum = new CodeMemberMethod(); |
| 1415 | GenerateSummaryComment(tryParseNewEnum.Comments, String.Format("Tries to parse a {0} from a string.", enumDeclaration.Name)); |
| 1416 | tryParseNewEnum.Attributes = MemberAttributes.Public | MemberAttributes.Static; |
| 1417 | tryParseNewEnum.Name = tryParseMethodName; |
| 1418 | tryParseNewEnum.ReturnType = boolType; |
| 1419 | CodeParameterDeclarationExpression valueDeclaration = new CodeParameterDeclarationExpression(stringType, "value"); |
| 1420 | CodeParameterDeclarationExpression parsedValueDeclaration = new CodeParameterDeclarationExpression(newEnumType, "parsedValue"); |
| 1421 | parsedValueDeclaration.Direction = FieldDirection.Out; |
| 1422 | tryParseNewEnum.Parameters.Add(valueDeclaration); |
| 1423 | tryParseNewEnum.Parameters.Add(parsedValueDeclaration); |
| 1424 | |
| 1425 | CodeArgumentReferenceExpression value = new CodeArgumentReferenceExpression(valueDeclaration.Name); |
| 1426 | CodeArgumentReferenceExpression parsedValue = new CodeArgumentReferenceExpression(parsedValueDeclaration.Name); |
| 1427 | |
| 1428 | tryParseNewEnum.Statements.Add(new CodeAssignStatement(parsedValue, defaultEnumValue)); |
| 1429 | |
| 1430 | tryParseNewEnum.Statements.Add(new CodeConditionStatement( |
| 1431 | new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeTypeReferenceExpression(stringType), "IsNullOrEmpty"), value), |
| 1432 | returnFalse)); |
| 1433 | |
| 1434 | // The structure is similar, but distinct, for regular and flag-style enums. In particular, |
| 1435 | // for a flags-style enum we have to be able to parse multiple values, separated by |
| 1436 | // spaces, and each value is bitwise-OR'd together. |
| 1437 | CodeStatementCollection nestedIfParent = tryParseNewEnum.Statements; |
| 1438 | CodeExpression valueToTest = value; |
| 1439 | |
| 1440 | // For Flags-style enums, we need to loop over the space-separated values... |
| 1441 | if (enumDeclaration.Flags) |
| 1442 | { |
| 1443 | CodeVariableDeclarationStatement split = new CodeVariableDeclarationStatement(typeof(string[]), "splitValue", |
| 1444 | new CodeMethodInvokeExpression(value, "Split", |
| 1445 | new CodeMethodInvokeExpression(new CodePrimitiveExpression(" \t\r\n"), "ToCharArray"), |
| 1446 | new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(StringSplitOptions)), "RemoveEmptyEntries"))); |
| 1447 | tryParseNewEnum.Statements.Add(split); |
| 1448 | |
| 1449 | CodeIterationStatement flagLoop = new CodeIterationStatement( |
| 1450 | new CodeVariableDeclarationStatement(typeof(IEnumerator), "enumerator", |
| 1451 | new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(split.Name), "GetEnumerator")), |
| 1452 | new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("enumerator"), "MoveNext"), |
| 1453 | new CodeSnippetStatement("")); |
| 1454 | tryParseNewEnum.Statements.Add(flagLoop); |
| 1455 | |
| 1456 | CodeVariableDeclarationStatement currentValue = new CodeVariableDeclarationStatement(typeof(string), "currentValue", |
| 1457 | new CodeCastExpression(stringType, |
| 1458 | new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("enumerator"), "Current"))); |
| 1459 | flagLoop.Statements.Add(currentValue); |
| 1460 | valueToTest = new CodeVariableReferenceExpression(currentValue.Name); |
| 1461 | |
| 1462 | nestedIfParent = flagLoop.Statements; |
| 1463 | } |
| 1464 | |
| 1465 | // We can't just Enum.Parse, because some values are also keywords (like 'string', 'int', 'default'), |
| 1466 | // and these get generated as '@'-prefixed values. Instead, we 'switch' on the value and do it manually. |
| 1467 | // Actually, we if/else, because CodeDom doesn't support 'switch'! Also, we nest the successive 'if's |
| 1468 | // in order to short-circuit the parsing as soon as there's a match. |
| 1469 | foreach (string enumValue in enumDeclaration.Values) |
| 1470 | { |
| 1471 | CodeFieldReferenceExpression enumValueReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(newEnumType), MakeEnumValue(enumValue)); |
| 1472 | CodeConditionStatement ifStatement = new CodeConditionStatement( |
| 1473 | new CodeBinaryOperatorExpression(new CodePrimitiveExpression(enumValue), CodeBinaryOperatorType.ValueEquality, valueToTest)); |
| 1474 | if (enumDeclaration.Flags) |
| 1475 | { |
| 1476 | ifStatement.TrueStatements.Add(new CodeAssignStatement(parsedValue, |
| 1477 | new CodeBinaryOperatorExpression(parsedValue, CodeBinaryOperatorType.BitwiseOr, enumValueReference))); |
| 1478 | } |
| 1479 | else |
| 1480 | { |
| 1481 | ifStatement.TrueStatements.Add(new CodeAssignStatement(parsedValue, enumValueReference)); |
| 1482 | } |
| 1483 | nestedIfParent.Add(ifStatement); |
| 1484 | nestedIfParent = ifStatement.FalseStatements; |
| 1485 | } |
| 1486 | |
| 1487 | // Finally, if we didn't find a match, it's illegal (or none, for flags)! |
| 1488 | nestedIfParent.Add(new CodeAssignStatement(parsedValue, illegalEnumValue)); |
| 1489 | nestedIfParent.Add(returnFalse); |
| 1490 | |
| 1491 | tryParseNewEnum.Statements.Add(returnTrue); |
| 1492 | |
| 1493 | parentType.Members.Add(tryParseNewEnum); |
| 1494 | |
| 1495 | enumsToParseMethodClasses.Add(enumDeclaration, parentType); |
| 1496 | } |
| 1497 | } |
| 1498 | } |