2 /// MonoXSD.cs -- A reflection-based tool for dealing with XML Schema.
4 /// Authors: Duncan Mak (duncan@ximian.com)
5 /// Lluis Sanchez Gual (lluis@ximian.com)
6 /// Atsushi Enomoto (atsushi@ximian.com)
8 /// Copyright (C) 2003, Duncan Mak,
13 using System
.Collections
;
16 using System
.Reflection
;
18 using System
.Xml
.Schema
;
19 using System
.Xml
.Serialization
;
21 using System
.CodeDom
.Compiler
;
22 using Microsoft
.CSharp
;
23 using Microsoft
.VisualBasic
;
29 public static readonly string helpString
=
30 "xsd.exe - a utility for generating schema or class files\n\n" +
31 "xsd.exe <schema>.xsd /classes [/element:NAME] [/language:NAME]\n" +
32 " [/namespace:NAME] [/outputdir:PATH] [/uri:NAME]\n\n" +
33 "xsd.exe <schema>.xsd /dataset [/element:NAME] [/language:NAME]\n" +
34 " [/namespace:NAME] [/outputdir:PATH] [/uri:NAME]\n\n" +
36 "xsd.exe <assembly>.dll|<assembly>.exe [/outputdir:PATH] [/type:NAME]\n\n" +
37 "xsd.exe <instance>.xml [<instance>.xml ...] [/outputdir:PATH]\n\n" +
38 " /c /classes Generate classes for the specified schema.\n" +
39 " /d /dataset Generate typed dataset classes for the specified schema.\n" +
40 " /e /element:NAME Element from schema to generate code for.\n" +
41 " Multiple elements can be specified.\n" +
42 " /u /uri:NAME Namespace uri of the elements to generate code for.\n" +
43 " /l /language:NAME The language, or type name of custom CodeDomProvider\n" +
44 " to use for the generated code.\n" +
45 " Shorthand specifiers are: \"CS\" (C#) and \"VB\" (VB.NET).\n" +
46 " For type name, assembly qualified name is required.\n" +
47 " /g /generator:TYPE Code Generator type name, followed by ','\n" +
48 " and assembly file name.\n" +
49 " /o /outputdir:PATH The directory where to generate the code or schemas.\n" +
50 " /n /namespace:NAME Namespace for the generated code.\n" +
51 " /t /type:NAME Type for which to generate an xml schema.\n" +
52 " Multiple types can be specified.\n" +
53 " /h /help Output this help.\n";
55 static readonly string incorrectOrder
= "Options must be specified after Assembly or schema file names";
56 static readonly string duplicatedParam
= "The option {0} cannot be specified more that once";
57 static readonly string unknownOption
= "Unknown option {0}";
58 static readonly string incompatibleArgs
= "Cannot mix options for generating schemas and for generatic classes";
59 static readonly string invalidParams
= "Invalid parameters";
60 static readonly string tooManyAssem
= "Only one assembly name can be specified";
61 static readonly string errLoadAssembly
= "Could not load assembly: {0}";
62 static readonly string typeNotFound
= "Type {0} not found in the specified assembly";
63 static readonly string languageNotSupported
= "The language {0} is not supported";
64 static readonly string missingOutputForXsdInput
= "Can only generate one of classes or datasets.";
65 static readonly string generatorAssemblyNotFound
= "Could not load code provider assembly file: {0}";
66 static readonly string generatorTypeNotFound
= "Could not find specified code provider type: {0}";
67 static readonly string generatorTypeIsNotCodeGenerator
= "Specified code provider type was not CodeDomProvider: {0}";
68 static readonly string generatorThrewException
= "Specified CodeDomProvider raised an error while creating its instance: {0}";
70 static void Main (string [] args
)
72 if (args
.Length
< 1) {
73 Console
.WriteLine (helpString
);
79 new Driver().Run (args
);
81 catch (ApplicationException ex
)
83 Console
.WriteLine (ex
.Message
);
87 Console
.WriteLine (ex
);
91 string outputDir
= null;
94 ArrayList lookupTypes
= new ArrayList();
95 ArrayList assemblies
= new ArrayList();
97 ArrayList schemaNames
= new ArrayList();
98 ArrayList inferenceNames
= new ArrayList();
99 ArrayList elements
= new ArrayList();
100 string language
= null;
101 string namesp
= null;
103 string providerOption
= null;
104 CodeDomProvider provider
;
106 public void Run (string[] args
)
108 ArrayList unknownFiles
= new ArrayList();
109 bool generateClasses
= false;
110 bool readingFiles
= true;
111 bool schemasOptions
= false;
112 bool assemblyOptions
= false;
113 bool generateDataset
= false;
114 bool inference
= false;
116 foreach (string arg
in args
)
118 if (!arg
.StartsWith ("--") && !arg
.StartsWith ("/") ||
119 (arg
.StartsWith ("/") && arg
.IndexOfAny (Path
.InvalidPathChars
) == -1)
122 if ((arg
.EndsWith (".dll") || arg
.EndsWith (".exe")) && !arg
.Substring (1).StartsWith ("generator:") && !arg
.Substring (1).StartsWith ("g:"))
124 if (!readingFiles
) throw new ApplicationException (incorrectOrder
);
125 assemblies
.Add (arg
);
126 assemblyOptions
= true;
129 else if (arg
.EndsWith (".xsd"))
131 if (!readingFiles
) Error (incorrectOrder
);
132 schemaNames
.Add (arg
);
133 schemasOptions
= true;
136 else if (arg
.EndsWith (".xml"))
138 if (generateClasses
|| generateDataset
) Error (duplicatedParam
);
139 inferenceNames
.Add (arg
);
143 else if (!arg
.StartsWith ("/"))
145 if (!readingFiles
) Error (incorrectOrder
);
146 unknownFiles
.Add (arg
);
151 readingFiles
= false;
153 int i
= arg
.IndexOf (":");
154 if (i
== -1) i
= arg
.Length
;
155 string option
= arg
.Substring (1,i
-1);
156 string param
= (i
<arg
.Length
-1) ? arg
.Substring (i
+1) : "";
158 if (option
== "classes" || option
== "c")
160 if (generateClasses
|| generateDataset
|| inference
) Error (duplicatedParam
, option
);
161 generateClasses
= true;
162 schemasOptions
= true;
164 else if (option
== "dataset" || option
== "d")
166 if (generateClasses
|| generateDataset
|| inference
) Error (duplicatedParam
, option
);
167 generateDataset
= true;
168 schemasOptions
= true;
170 else if (option
== "element" || option
== "e")
172 elements
.Add (param
);
173 schemasOptions
= true;
175 else if (option
== "language" || option
== "l")
177 if (provider
!= null) Error (duplicatedParam
, option
);
178 if (language
!= null) Error (duplicatedParam
, option
);
180 schemasOptions
= true;
182 else if (option
== "namespace" || option
== "n")
184 if (namesp
!= null) Error (duplicatedParam
, option
);
186 schemasOptions
= true;
188 else if (option
== "outputdir" || option
== "o")
190 if (outputDir
!= null) Error (duplicatedParam
, option
);
193 else if (option
== "uri" || option
== "u")
195 if (uri
!= null) Error (duplicatedParam
, option
);
197 schemasOptions
= true;
199 else if (option
== "type" || option
== "t")
201 lookupTypes
.Add (param
);
202 assemblyOptions
= true;
204 else if (option
== "generator" || option
== "g")
206 providerOption
= param
;
208 else if (option
== "help" || option
== "h")
210 Console
.WriteLine (helpString
);
213 else if (option
== "nologo")
215 // ignore, since we do not output a logo anyway
218 Error (unknownOption
, option
);
221 if (!schemasOptions
&& !assemblyOptions
&& !inference
)
222 Error (invalidParams
);
224 if (schemasOptions
&& assemblyOptions
)
225 Error (incompatibleArgs
);
227 if (assemblies
.Count
> 1)
228 Error (tooManyAssem
);
230 if (outputDir
== null) outputDir
= ".";
232 string typename
= null;
233 Type generatorType
= null;
235 if (language
!= null) {
238 provider
= new CSharpCodeProvider ();
241 provider
= new VBCodeProvider ();
244 typename
= StripQuot (language
);
246 generatorType
= Type
.GetType (typename
);
247 if (generatorType
== null)
248 Error (generatorTypeNotFound
, typename
);
253 if (providerOption
!= null) {
254 string param
= providerOption
;
255 int comma
= param
.IndexOf (',');
257 typename
= StripQuot (param
);
258 generatorType
= Type
.GetType (param
);
260 typename
= param
.Substring (0, comma
);
261 string asmName
= param
.Substring (comma
+ 1);
263 Assembly asm
= Assembly
.LoadFile (asmName
);
265 Assembly asm
= Assembly
.LoadFrom (asmName
);
268 Error (generatorAssemblyNotFound
, asmName
);
269 generatorType
= asm
.GetType (typename
);
271 if (generatorType
== null)
272 Error (generatorTypeNotFound
, typename
);
274 if (generatorType
!= null) {
275 if (!generatorType
.IsSubclassOf (typeof (CodeDomProvider
)))
276 Error (generatorTypeIsNotCodeGenerator
, typename
);
278 provider
= (CodeDomProvider
) Activator
.CreateInstance (generatorType
, null);
279 } catch (Exception ex
) {
280 Error (generatorThrewException
, generatorType
.AssemblyQualifiedName
.ToString () + " --> " + ex
.Message
);
282 Console
.WriteLine ("Loaded custom generator type " + generatorType
+ " .");
284 if (provider
== null)
285 provider
= new CSharpCodeProvider ();
289 if (!generateClasses
&& !generateDataset
)
290 Error (missingOutputForXsdInput
);
291 schemaNames
.AddRange (unknownFiles
);
294 else if (generateDataset
)
299 foreach (string xmlfile
in inferenceNames
) {
300 string genFile
= Path
.Combine (outputDir
, Path
.GetFileNameWithoutExtension (xmlfile
) + ".xsd");
301 DataSet ds
= new DataSet ();
302 ds
.InferXmlSchema (xmlfile
, null);
303 ds
.WriteXmlSchema (genFile
);
304 Console
.WriteLine ("Written file " + genFile
);
309 assemblies
.AddRange (unknownFiles
);
314 public void GenerateSchemas ()
316 Assembly assembly
= null;
319 assembly
= Assembly
.LoadFrom ((string) assemblies
[0]);
323 Error (errLoadAssembly
, ex
.Message
);
328 if (lookupTypes
.Count
> 0)
330 types
= new Type
[lookupTypes
.Count
];
331 for (int n
=0; n
<lookupTypes
.Count
; n
++)
333 Type t
= assembly
.GetType ((string)lookupTypes
[n
]);
334 if (t
== null) Error (typeNotFound
, (string)lookupTypes
[n
]);
339 types
= assembly
.GetExportedTypes ();
341 XmlReflectionImporter ri
= new XmlReflectionImporter ();
342 XmlSchemas schemas
= new XmlSchemas ();
343 XmlSchemaExporter sx
= new XmlSchemaExporter (schemas
);
345 foreach (Type type
in types
)
347 XmlTypeMapping tm
= ri
.ImportTypeMapping (type
);
348 sx
.ExportTypeMapping (tm
);
351 if (schemas
.Count
== 1)
353 string fileName
= Path
.Combine (outputDir
, "schema.xsd");
354 WriteSchema (fileName
, schemas
[0]);
358 for (int n
=0; n
<schemas
.Count
; n
++)
360 string fileName
= Path
.Combine (outputDir
, "schema" + n
+ ".xsd");
361 WriteSchema (fileName
, schemas
[n
]);
366 void WriteSchema (string fileName
, XmlSchema schema
)
368 StreamWriter sw
= new StreamWriter (fileName
);
371 Console
.WriteLine ("Written file " + fileName
);
374 public void GenerateClasses ()
376 if (namesp
== null) namesp
= "Schemas";
377 if (uri
== null) uri
= "";
378 string targetFile
= "";
380 XmlSchemas schemas
= new XmlSchemas();
381 foreach (string fileName
in schemaNames
)
383 StreamReader sr
= new StreamReader (fileName
);
384 schemas
.Add (XmlSchema
.Read (sr
, new ValidationEventHandler (HandleValidationError
)));
387 if (targetFile
== "") targetFile
= Path
.GetFileNameWithoutExtension (fileName
);
388 else targetFile
+= "_" + Path
.GetFileNameWithoutExtension (fileName
);
391 targetFile
+= "." + provider
.FileExtension
;
393 CodeCompileUnit cunit
= new CodeCompileUnit ();
394 CodeNamespace codeNamespace
= new CodeNamespace (namesp
);
395 cunit
.Namespaces
.Add (codeNamespace
);
396 codeNamespace
.Comments
.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));
398 // Locate elements to generate
400 ArrayList qnames
= new ArrayList ();
401 if (elements
.Count
> 0)
403 foreach (string name
in elements
)
404 qnames
.Add (new XmlQualifiedName (name
, uri
));
408 foreach (XmlSchema schema
in schemas
) {
409 if (!schema
.IsCompiled
) schema
.Compile (new ValidationEventHandler (HandleValidationError
));
410 foreach (XmlSchemaElement el
in schema
.Elements
.Values
)
411 if (!qnames
.Contains (el
.QualifiedName
))
412 qnames
.Add (el
.QualifiedName
);
416 // Import schemas and generate the class model
418 XmlSchemaImporter importer
= new XmlSchemaImporter (schemas
);
419 XmlCodeExporter sx
= new XmlCodeExporter (codeNamespace
, cunit
);
421 ArrayList maps
= new ArrayList();
423 foreach (XmlQualifiedName qname
in qnames
)
425 XmlTypeMapping tm
= importer
.ImportTypeMapping (qname
);
426 if (tm
!= null) maps
.Add (tm
);
429 foreach (XmlTypeMapping tm
in maps
)
431 sx
.ExportTypeMapping (tm
);
436 ICodeGenerator gen
= provider
.CreateGenerator();
438 string genFile
= Path
.Combine (outputDir
, targetFile
);
439 StreamWriter sw
= new StreamWriter(genFile
, false);
440 gen
.GenerateCodeFromCompileUnit (cunit
, sw
, new CodeGeneratorOptions());
443 Console
.WriteLine ("Written file " + genFile
);
446 public void GenerateDataset ()
448 if (namesp
== null) namesp
= "";
449 if (uri
== null) uri
= "";
450 string targetFile
= "";
452 DataSet dataset
= new DataSet ();
453 foreach (string fileName
in schemaNames
)
455 dataset
.ReadXmlSchema (fileName
);
457 if (targetFile
== "") targetFile
= Path
.GetFileNameWithoutExtension (fileName
);
458 else targetFile
+= "_" + Path
.GetFileNameWithoutExtension (fileName
);
461 targetFile
+= "." + provider
.FileExtension
;
463 CodeCompileUnit cunit
= new CodeCompileUnit ();
464 CodeNamespace codeNamespace
= new CodeNamespace (namesp
);
465 cunit
.Namespaces
.Add (codeNamespace
);
466 codeNamespace
.Comments
.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));
470 ICodeGenerator gen
= provider
.CreateGenerator ();
472 TypedDataSetGenerator
.Generate (dataset
, codeNamespace
, gen
);
474 string genFile
= Path
.Combine (outputDir
, targetFile
);
475 StreamWriter sw
= new StreamWriter(genFile
, false);
476 gen
.GenerateCodeFromCompileUnit (cunit
, sw
, new CodeGeneratorOptions());
479 Console
.WriteLine ("Written file " + genFile
);
482 void HandleValidationError (object o
, ValidationEventArgs e
)
484 Console
.WriteLine ("{0}: {1} {2}",
485 e
.Severity
== XmlSeverityType
.Error
? "Error" : "Warning",
487 e
.Exception
!= null ? e
.Exception
.Message
: null);
490 public void Error (string msg
)
492 throw new ApplicationException (msg
);
495 public void Error (string msg
, string param
)
497 throw new ApplicationException (string.Format(msg
,param
));
500 private string StripQuot (string input
)
502 if (input
.Length
< 2)
504 if (input
[0] == '"' && input
[input
.Length
-1] == '"' ||
505 input
[0] == '\'' && input
[input
.Length
- 1] == '\'')
506 return input
.Substring (1, input
.Length
- 2);