aboutsummaryrefslogtreecommitdiff
path: root/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixBuildTools.XsdGen/StronglyTypedClasses.cs')
-rw-r--r--src/WixBuildTools.XsdGen/StronglyTypedClasses.cs1498
1 files changed, 1498 insertions, 0 deletions
diff --git a/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs b/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs
new file mode 100644
index 00000000..4a41f8a9
--- /dev/null
+++ b/src/WixBuildTools.XsdGen/StronglyTypedClasses.cs
@@ -0,0 +1,1498 @@
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
3namespace 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}