2 /// MonoXSD.cs -- A reflection-based tool for dealing with XML Schema.
\r
4 /// Authors: Duncan Mak (duncan@ximian.com)
\r
5 /// Lluis Sanchez Gual (lluis@ximian.com)
\r
6 /// Atsushi Enomoto (atsushi@ximian.com)
\r
8 /// Copyright (C) 2003, Duncan Mak,
\r
13 using System.Collections;
\r
16 using System.Reflection;
\r
18 using System.Xml.Schema;
\r
19 using System.Xml.Serialization;
\r
20 using System.CodeDom;
\r
21 using System.CodeDom.Compiler;
\r
22 using Microsoft.CSharp;
\r
23 using Microsoft.VisualBasic;
\r
25 namespace Mono.Util {
\r
29 public static readonly string helpString =
\r
30 "xsd.exe - a utility for generating schema or class files\n\n" +
\r
31 "xsd.exe <schema>.xsd /classes [/element:NAME] [/language:NAME]\n" +
\r
32 " [/namespace:NAME] [/outputdir:PATH] [/uri:NAME]\n\n" +
\r
33 "xsd.exe <schema>.xsd /dataset [/element:NAME] [/language:NAME]\n" +
\r
34 " [/namespace:NAME] [/outputdir:PATH] [/uri:NAME]\n\n" +
\r
36 "xsd.exe <assembly>.dll|<assembly>.exe [/outputdir:PATH] [/type:NAME]\n\n" +
\r
37 "xsd.exe <instance>.xml [<instance>.xml ...] [/outputdir:PATH]\n\n" +
\r
38 " /c /classes Generate classes for the specified schema.\n" +
\r
39 " /d /dataset Generate typed dataset classes for the specified schema.\n" +
\r
40 " /e /element:NAME Element from schema to generate code for.\n" +
\r
41 " Multiple elements can be specified.\n" +
\r
42 " /u /uri:NAME Namespace uri of the elements to generate code for.\n" +
\r
43 " /l /language:NAME The language, or type name of custom CodeDomProvider\n" +
\r
44 " to use for the generated code.\n" +
\r
45 " Shorthand specifiers are: \"CS\" (C#) and \"VB\" (VB.NET).\n" +
\r
46 " For type name, assembly qualified name is required.\n" +
\r
47 " /g /generator:TYPE Code Generator type name, followed by ','\n" +
\r
48 " and assembly file name.\n" +
\r
49 " /o /outputdir:PATH The directory where to generate the code or schemas.\n" +
\r
50 " /n /namespace:NAME Namespace for the generated code.\n" +
\r
51 " /t /type:NAME Type for which to generate an xml schema.\n" +
\r
52 " Multiple types can be specified.\n" +
\r
53 " /h /help Output this help.\n";
\r
55 static readonly string incorrectOrder = "Options must be specified after Assembly or schema file names";
\r
56 static readonly string duplicatedParam = "The option {0} cannot be specified more that once";
\r
57 static readonly string unknownOption = "Unknown option {0}";
\r
58 static readonly string incompatibleArgs = "Cannot mix options for generating schemas and for generatic classes";
\r
59 static readonly string invalidParams = "Invalid parameters";
\r
60 static readonly string tooManyAssem = "Only one assembly name can be specified";
\r
61 static readonly string errLoadAssembly = "Could not load assembly: {0}";
\r
62 static readonly string typeNotFound = "Type {0} not found in the specified assembly";
\r
63 static readonly string languageNotSupported = "The language {0} is not supported";
\r
64 static readonly string missingOutputForXsdInput = "Can only generate one of classes or datasets.";
\r
65 static readonly string generatorAssemblyNotFound = "Could not load code provider assembly file: {0}";
\r
66 static readonly string generatorTypeNotFound = "Could not find specified code provider type: {0}";
\r
67 static readonly string generatorTypeIsNotCodeGenerator = "Specified code provider type was not CodeDomProvider: {0}";
\r
68 static readonly string generatorThrewException = "Specified CodeDomProvider raised an error while creating its instance: {0}";
\r
70 static void Main (string [] args)
\r
72 if (args.Length < 1) {
\r
73 Console.WriteLine (helpString);
\r
74 Environment.Exit (0);
\r
79 new Driver().Run (args);
\r
81 catch (ApplicationException ex)
\r
83 Console.WriteLine (ex.Message);
\r
85 catch (Exception ex)
\r
87 Console.WriteLine (ex);
\r
91 string outputDir = null;
\r
94 ArrayList lookupTypes = new ArrayList();
\r
95 ArrayList assemblies = new ArrayList();
\r
97 ArrayList schemaNames = new ArrayList();
\r
98 ArrayList inferenceNames = new ArrayList();
\r
99 ArrayList elements = new ArrayList();
\r
100 string language = null;
\r
101 string namesp = null;
\r
103 string providerOption = null;
\r
104 CodeDomProvider provider;
\r
106 public void Run (string[] args)
\r
108 ArrayList unknownFiles = new ArrayList();
\r
109 bool generateClasses = false;
\r
110 bool readingFiles = true;
\r
111 bool schemasOptions = false;
\r
112 bool assemblyOptions = false;
\r
113 bool generateDataset = false;
\r
114 bool inference = false;
\r
116 foreach (string arg in args)
\r
118 if (!arg.StartsWith ("--") && !arg.StartsWith ("/") ||
\r
119 (arg.StartsWith ("/") && arg.IndexOfAny (Path.InvalidPathChars) == -1)
\r
122 if ((arg.EndsWith (".dll") || arg.EndsWith (".exe")) && !arg.Substring (1).StartsWith ("generator:") && !arg.Substring (1).StartsWith ("g:"))
\r
124 if (!readingFiles) throw new ApplicationException (incorrectOrder);
\r
125 assemblies.Add (arg);
\r
126 assemblyOptions = true;
\r
129 else if (arg.EndsWith (".xsd"))
\r
131 if (!readingFiles) Error (incorrectOrder);
\r
132 schemaNames.Add (arg);
\r
133 schemasOptions = true;
\r
136 else if (arg.EndsWith (".xml"))
\r
138 if (generateClasses || generateDataset) Error (duplicatedParam);
\r
139 inferenceNames.Add (arg);
\r
143 else if (!arg.StartsWith ("/"))
\r
145 if (!readingFiles) Error (incorrectOrder);
\r
146 unknownFiles.Add (arg);
\r
151 readingFiles = false;
\r
153 int i = arg.IndexOf (":");
\r
154 if (i == -1) i = arg.Length;
\r
155 string option = arg.Substring (1,i-1);
\r
156 string param = (i<arg.Length-1) ? arg.Substring (i+1) : "";
\r
158 if (option == "classes" || option == "c")
\r
160 if (generateClasses || generateDataset || inference) Error (duplicatedParam, option);
\r
161 generateClasses = true;
\r
162 schemasOptions = true;
\r
164 else if (option == "dataset" || option == "d")
\r
166 if (generateClasses || generateDataset || inference) Error (duplicatedParam, option);
\r
167 generateDataset = true;
\r
168 schemasOptions = true;
\r
170 else if (option == "element" || option == "e")
\r
172 elements.Add (param);
\r
173 schemasOptions = true;
\r
175 else if (option == "language" || option == "l")
\r
177 if (provider != null) Error (duplicatedParam, option);
\r
178 if (language != null) Error (duplicatedParam, option);
\r
180 schemasOptions = true;
\r
182 else if (option == "namespace" || option == "n")
\r
184 if (namesp != null) Error (duplicatedParam, option);
\r
186 schemasOptions = true;
\r
188 else if (option == "outputdir" || option == "o")
\r
190 if (outputDir != null) Error (duplicatedParam, option);
\r
193 else if (option == "uri" || option == "u")
\r
195 if (uri != null) Error (duplicatedParam, option);
\r
197 schemasOptions = true;
\r
199 else if (option == "type" || option == "t")
\r
201 lookupTypes.Add (param);
\r
202 assemblyOptions = true;
\r
204 else if (option == "generator" || option == "g")
\r
206 providerOption = param;
\r
208 else if (option == "help" || option == "h")
\r
210 Console.WriteLine (helpString);
\r
214 Error (unknownOption, option);
\r
217 if (!schemasOptions && !assemblyOptions && !inference)
\r
218 Error (invalidParams);
\r
220 if (schemasOptions && assemblyOptions)
\r
221 Error (incompatibleArgs);
\r
223 if (assemblies.Count > 1)
\r
224 Error (tooManyAssem);
\r
226 if (outputDir == null) outputDir = ".";
\r
228 string typename = null;
\r
229 Type generatorType = null;
\r
231 if (language != null) {
\r
232 switch (language) {
\r
234 provider = new CSharpCodeProvider ();
\r
237 provider = new VBCodeProvider ();
\r
240 typename = StripQuot (language);
\r
242 generatorType = Type.GetType (typename);
\r
243 if (generatorType == null)
\r
244 Error (generatorTypeNotFound, typename);
\r
249 if (providerOption != null) {
\r
250 string param = providerOption;
\r
251 int comma = param.IndexOf (',');
\r
253 typename = StripQuot (param);
\r
254 generatorType = Type.GetType (param);
\r
256 typename = param.Substring (0, comma);
\r
257 string asmName = param.Substring (comma + 1);
\r
259 Assembly asm = Assembly.LoadFile (asmName);
\r
261 Assembly asm = Assembly.LoadFrom (asmName);
\r
264 Error (generatorAssemblyNotFound, asmName);
\r
265 generatorType = asm.GetType (typename);
\r
267 if (generatorType == null)
\r
268 Error (generatorTypeNotFound, typename);
\r
270 if (generatorType != null) {
\r
271 if (!generatorType.IsSubclassOf (typeof (CodeDomProvider)))
\r
272 Error (generatorTypeIsNotCodeGenerator, typename);
\r
274 provider = (CodeDomProvider) Activator.CreateInstance (generatorType, null);
\r
275 } catch (Exception ex) {
\r
276 Error (generatorThrewException, generatorType.AssemblyQualifiedName.ToString () + " --> " + ex.Message);
\r
278 Console.WriteLine ("Loaded custom generator type " + generatorType + " .");
\r
280 if (provider == null)
\r
281 provider = new CSharpCodeProvider ();
\r
283 if (schemasOptions)
\r
285 if (!generateClasses && !generateDataset)
\r
286 Error (missingOutputForXsdInput);
\r
287 schemaNames.AddRange (unknownFiles);
\r
288 if (generateClasses)
\r
289 GenerateClasses ();
\r
290 else if (generateDataset)
\r
291 GenerateDataset ();
\r
293 else if (inference)
\r
295 foreach (string xmlfile in inferenceNames) {
\r
296 string genFile = Path.Combine (outputDir, Path.GetFileNameWithoutExtension (xmlfile) + ".xsd");
\r
297 DataSet ds = new DataSet ();
\r
298 ds.InferXmlSchema (xmlfile, null);
\r
299 ds.WriteXmlSchema (genFile);
\r
300 Console.WriteLine ("Written file " + genFile);
\r
305 assemblies.AddRange (unknownFiles);
\r
306 GenerateSchemas ();
\r
310 public void GenerateSchemas ()
\r
312 Assembly assembly = null;
\r
315 assembly = Assembly.LoadFrom ((string) assemblies [0]);
\r
317 catch (Exception ex)
\r
319 Error (errLoadAssembly, ex.Message);
\r
324 if (lookupTypes.Count > 0)
\r
326 types = new Type [lookupTypes.Count];
\r
327 for (int n=0; n<lookupTypes.Count; n++)
\r
329 Type t = assembly.GetType ((string)lookupTypes[n]);
\r
330 if (t == null) Error (typeNotFound, (string)lookupTypes[n]);
\r
335 types = assembly.GetExportedTypes ();
\r
337 XmlReflectionImporter ri = new XmlReflectionImporter ();
\r
338 XmlSchemas schemas = new XmlSchemas ();
\r
339 XmlSchemaExporter sx = new XmlSchemaExporter (schemas);
\r
341 foreach (Type type in types)
\r
343 XmlTypeMapping tm = ri.ImportTypeMapping (type);
\r
344 sx.ExportTypeMapping (tm);
\r
347 if (schemas.Count == 1)
\r
349 string fileName = Path.Combine (outputDir, "schema.xsd");
\r
350 WriteSchema (fileName, schemas [0]);
\r
354 for (int n=0; n<schemas.Count; n++)
\r
356 string fileName = Path.Combine (outputDir, "schema" + n + ".xsd");
\r
357 WriteSchema (fileName, schemas [n]);
\r
362 void WriteSchema (string fileName, XmlSchema schema)
\r
364 StreamWriter sw = new StreamWriter (fileName);
\r
367 Console.WriteLine ("Written file " + fileName);
\r
370 public void GenerateClasses ()
\r
372 if (namesp == null) namesp = "Schemas";
\r
373 if (uri == null) uri = "";
\r
374 string targetFile = "";
\r
376 XmlSchemas schemas = new XmlSchemas();
\r
377 foreach (string fileName in schemaNames)
\r
379 StreamReader sr = new StreamReader (fileName);
\r
380 schemas.Add (XmlSchema.Read (sr, null));
\r
383 if (targetFile == "") targetFile = Path.GetFileNameWithoutExtension (fileName);
\r
384 else targetFile += "_" + Path.GetFileNameWithoutExtension (fileName);
\r
387 targetFile += "." + provider.FileExtension;
\r
389 CodeCompileUnit cunit = new CodeCompileUnit ();
\r
390 CodeNamespace codeNamespace = new CodeNamespace (namesp);
\r
391 cunit.Namespaces.Add (codeNamespace);
\r
392 codeNamespace.Comments.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));
\r
394 // Locate elements to generate
\r
396 ArrayList qnames = new ArrayList ();
\r
397 if (elements.Count > 0)
\r
399 foreach (string name in elements)
\r
400 qnames.Add (new XmlQualifiedName (name, uri));
\r
404 foreach (XmlSchema schema in schemas) {
\r
405 if (!schema.IsCompiled) schema.Compile (null);
\r
406 foreach (XmlSchemaObject ob in schema.Items)
\r
407 if (ob is XmlSchemaElement)
\r
408 qnames.Add (((XmlSchemaElement)ob).QualifiedName);
\r
412 // Import schemas and generate the class model
\r
414 XmlSchemaImporter importer = new XmlSchemaImporter (schemas);
\r
415 XmlCodeExporter sx = new XmlCodeExporter (codeNamespace, cunit);
\r
417 ArrayList maps = new ArrayList();
\r
419 foreach (XmlQualifiedName qname in qnames)
\r
421 XmlTypeMapping tm = importer.ImportTypeMapping (qname);
\r
422 if (tm != null) maps.Add (tm);
\r
425 foreach (XmlTypeMapping tm in maps)
\r
427 sx.ExportTypeMapping (tm);
\r
430 // Generate the code
\r
432 ICodeGenerator gen = provider.CreateGenerator();
\r
434 string genFile = Path.Combine (outputDir, targetFile);
\r
435 StreamWriter sw = new StreamWriter(genFile, false);
\r
436 gen.GenerateCodeFromCompileUnit (cunit, sw, new CodeGeneratorOptions());
\r
439 Console.WriteLine ("Written file " + genFile);
\r
442 public void GenerateDataset ()
\r
444 if (namesp == null) namesp = "";
\r
445 if (uri == null) uri = "";
\r
446 string targetFile = "";
\r
448 DataSet dataset = new DataSet ();
\r
449 foreach (string fileName in schemaNames)
\r
451 dataset.ReadXmlSchema (fileName);
\r
453 if (targetFile == "") targetFile = Path.GetFileNameWithoutExtension (fileName);
\r
454 else targetFile += "_" + Path.GetFileNameWithoutExtension (fileName);
\r
457 targetFile += "." + provider.FileExtension;
\r
459 CodeCompileUnit cunit = new CodeCompileUnit ();
\r
460 CodeNamespace codeNamespace = new CodeNamespace (namesp);
\r
461 cunit.Namespaces.Add (codeNamespace);
\r
462 codeNamespace.Comments.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));
\r
464 // Generate the code
\r
466 ICodeGenerator gen = provider.CreateGenerator ();
\r
468 TypedDataSetGenerator.Generate (dataset, codeNamespace, gen);
\r
470 string genFile = Path.Combine (outputDir, targetFile);
\r
471 StreamWriter sw = new StreamWriter(genFile, false);
\r
472 gen.GenerateCodeFromCompileUnit (cunit, sw, new CodeGeneratorOptions());
\r
475 Console.WriteLine ("Written file " + genFile);
\r
478 public void Error (string msg)
\r
480 throw new ApplicationException (msg);
\r
483 public void Error (string msg, string param)
\r
485 throw new ApplicationException (string.Format(msg,param));
\r
488 private string StripQuot (string input)
\r
490 if (input.Length < 2)
\r
492 if (input [0] == '"' && input [input.Length -1] == '"' ||
\r
493 input [0] == '\'' && input [input.Length - 1] == '\'')
\r
494 return input.Substring (1, input.Length - 2);
\r