2004-08-16 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / tools / mono-xsd / NewMonoXSD.cs
1 ///\r
2 /// MonoXSD.cs -- A reflection-based tool for dealing with XML Schema.\r
3 ///\r
4 /// Authors: Duncan Mak (duncan@ximian.com)\r
5 ///          Lluis Sanchez Gual (lluis@ximian.com)\r
6 ///          Atsushi Enomoto (atsushi@ximian.com)\r
7 ///\r
8 /// Copyright (C) 2003, Duncan Mak,\r
9 ///                     Ximian, Inc.\r
10 ///\r
11 \r
12 using System;\r
13 using System.Collections;\r
14 using System.Data;\r
15 using System.IO;\r
16 using System.Reflection;\r
17 using System.Xml;\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
24 \r
25 namespace Mono.Util {\r
26 \r
27         public class Driver\r
28         {\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
35 \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
54 \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
69 \r
70                 static void Main (string [] args)\r
71                 {\r
72                         if (args.Length < 1) {\r
73                                 Console.WriteLine (helpString);\r
74                                 Environment.Exit (0);\r
75                         }\r
76 \r
77                         try\r
78                         {\r
79                                 new Driver().Run (args);\r
80                         }\r
81                         catch (Exception ex)\r
82                         {\r
83                                 Console.WriteLine (ex.Message);\r
84                                 Console.WriteLine (ex);\r
85                         }\r
86                 }\r
87 \r
88                 string outputDir = null;\r
89 \r
90 \r
91                 ArrayList lookupTypes = new ArrayList();\r
92                 ArrayList assemblies = new ArrayList();\r
93 \r
94                 ArrayList schemaNames = new ArrayList();\r
95                 ArrayList inferenceNames = new ArrayList();\r
96                 ArrayList elements = new ArrayList();\r
97                 string language = null;\r
98                 string namesp = null;\r
99                 string uri = null;\r
100                 string providerOption = null;\r
101                 CodeDomProvider provider;\r
102 \r
103                 public void Run (string[] args)\r
104                 {\r
105                         ArrayList unknownFiles = new ArrayList();\r
106                         bool generateClasses = false;\r
107                         bool readingFiles = true;\r
108                         bool schemasOptions = false;\r
109                         bool assemblyOptions = false;\r
110                         bool generateDataset = false;\r
111                         bool inference = false;\r
112 \r
113                         foreach (string arg in args)\r
114                         {\r
115                                 if (!arg.StartsWith ("--") && !arg.StartsWith ("/") ||\r
116                                         (arg.StartsWith ("/") && arg.IndexOfAny (Path.InvalidPathChars) == -1)\r
117                                         ) \r
118                                 {\r
119                                         if ((arg.EndsWith (".dll") || arg.EndsWith (".exe")) && !arg.Substring (1).StartsWith ("generator:") && !arg.Substring (1).StartsWith ("g:"))\r
120                                         {\r
121                                                 if (!readingFiles) throw new Exception (incorrectOrder);\r
122                                                 assemblies.Add (arg);\r
123                                                 assemblyOptions = true;\r
124                                                 continue;\r
125                                         }\r
126                                         else if (arg.EndsWith (".xsd"))\r
127                                         {\r
128                                                 if (!readingFiles) Error (incorrectOrder);\r
129                                                 schemaNames.Add (arg);\r
130                                                 schemasOptions = true;\r
131                                                 continue;\r
132                                         }\r
133                                         else if (arg.EndsWith (".xml"))\r
134                                         {\r
135                                                 if (generateClasses || generateDataset) Error (duplicatedParam);\r
136                                                 inferenceNames.Add (arg);\r
137                                                 inference = true;\r
138                                                 continue;\r
139                                         }\r
140                                         else if (!arg.StartsWith ("/"))\r
141                                         {\r
142                                                 if (!readingFiles) Error (incorrectOrder);\r
143                                                 unknownFiles.Add (arg);\r
144                                                 continue;\r
145                                         }\r
146                                 }\r
147 \r
148                                 readingFiles = false;\r
149 \r
150                                 int i = arg.IndexOf (":");\r
151                                 if (i == -1) i = arg.Length;\r
152                                 string option = arg.Substring (1,i-1);\r
153                                 string param = (i<arg.Length-1) ? arg.Substring (i+1) : "";\r
154 \r
155                                 if (option == "classes" || option == "c")\r
156                                 {\r
157                                         if (generateClasses || generateDataset || inference) Error (duplicatedParam, option);\r
158                                         generateClasses = true;\r
159                                         schemasOptions = true;\r
160                                 }\r
161                                 else if (option == "dataset" || option == "d")\r
162                                 {\r
163                                         if (generateClasses || generateDataset || inference) Error (duplicatedParam, option);\r
164                                         generateDataset = true;\r
165                                         schemasOptions = true;\r
166                                 }\r
167                                 else if (option == "element" || option == "e")\r
168                                 {\r
169                                         elements.Add (param);\r
170                                         schemasOptions = true;\r
171                                 }\r
172                                 else if (option == "language" || option == "l")\r
173                                 {\r
174                                         if (provider != null) Error (duplicatedParam, option);\r
175                                         if (language != null) Error (duplicatedParam, option);\r
176                                         language = param;\r
177                                         schemasOptions = true;\r
178                                 }\r
179                                 else if (option == "namespace" || option == "n")\r
180                                 {\r
181                                         if (namesp != null) Error (duplicatedParam, option);\r
182                                         namesp = param;\r
183                                         schemasOptions = true;\r
184                                 }\r
185                                 else if (option == "outputdir" || option == "o")\r
186                                 {\r
187                                         if (outputDir != null) Error (duplicatedParam, option);\r
188                                         outputDir = param;\r
189                                 }\r
190                                 else if (option == "uri" || option == "u")\r
191                                 {\r
192                                         if (uri != null) Error (duplicatedParam, option);\r
193                                         uri = param;\r
194                                         schemasOptions = true;\r
195                                 }\r
196                                 else if (option == "type" || option == "t")\r
197                                 {\r
198                                         lookupTypes.Add (param);\r
199                                         assemblyOptions = true;\r
200                                 }\r
201                                 else if (option == "generator" || option == "g")\r
202                                 {\r
203                                         providerOption = param;\r
204                                 }\r
205                                 else if (option == "help" || option == "h")\r
206                                 {\r
207                                         Console.WriteLine (helpString);\r
208                                         return;\r
209                                 }\r
210                                 else\r
211                                         Error (unknownOption, option);\r
212                         }\r
213 \r
214                         if (!schemasOptions && !assemblyOptions && !inference)\r
215                                 Error (invalidParams);\r
216 \r
217                         if (schemasOptions && assemblyOptions)\r
218                                 Error (incompatibleArgs);\r
219 \r
220                         if (assemblies.Count > 1)\r
221                                 Error (tooManyAssem);\r
222 \r
223                         if (outputDir == null) outputDir = ".";\r
224 \r
225                         string typename = null;\r
226                         Type generatorType = null;\r
227 \r
228                         if (language != null) {\r
229                                 switch (language) {\r
230                                 case "CS":\r
231                                         provider = new CSharpCodeProvider ();\r
232                                         break;\r
233                                 case "VB":\r
234                                         provider = new VBCodeProvider ();\r
235                                         break;\r
236                                 default:\r
237                                         typename = StripQuot (language);\r
238 \r
239                                         generatorType = Type.GetType (typename);\r
240                                         if (generatorType == null)\r
241                                                 Error (generatorTypeNotFound, typename);\r
242                                         break;\r
243                                 }\r
244                         }\r
245 \r
246                         if (providerOption != null) {\r
247                                 string param = providerOption;\r
248                                 int comma = param.IndexOf (',');\r
249                                 if (comma < 0) {\r
250                                         typename = StripQuot (param);\r
251                                         generatorType = Type.GetType (param);\r
252                                 } else {\r
253                                         typename = param.Substring (0, comma);\r
254                                         string asmName = param.Substring (comma + 1);\r
255 #if NET_1_1\r
256                                         Assembly asm = Assembly.LoadFile (asmName);\r
257 #else\r
258                                         Assembly asm = Assembly.LoadFrom (asmName);\r
259 #endif\r
260                                         if (asm == null)\r
261                                                 Error (generatorAssemblyNotFound, asmName);\r
262                                         generatorType = asm.GetType (typename);\r
263                                 }\r
264                                 if (generatorType == null)\r
265                                         Error (generatorTypeNotFound, typename);\r
266                         }\r
267                         if (generatorType != null) {\r
268                                 if (!generatorType.IsSubclassOf (typeof (CodeDomProvider)))\r
269                                         Error (generatorTypeIsNotCodeGenerator, typename);\r
270                                 try {\r
271                                         provider = (CodeDomProvider) Activator.CreateInstance (generatorType, null);\r
272                                 } catch (Exception ex) {\r
273                                         Error (generatorThrewException, generatorType.AssemblyQualifiedName.ToString () + " --> " + ex.Message);\r
274                                 }\r
275                                 Console.WriteLine ("Loaded custom generator type " + generatorType + " .");\r
276                         }\r
277                         if (provider == null)\r
278                                 provider = new CSharpCodeProvider ();\r
279 \r
280                         if (schemasOptions)\r
281                         {\r
282                                 if (!generateClasses && !generateDataset)\r
283                                         Error (missingOutputForXsdInput);\r
284                                 schemaNames.AddRange (unknownFiles);\r
285                                 if (generateClasses)\r
286                                         GenerateClasses ();\r
287                                 else if (generateDataset)\r
288                                         GenerateDataset ();\r
289                         }\r
290                         else if (inference)\r
291                         {\r
292                                 foreach (string xmlfile in inferenceNames) {\r
293                                         string genFile = Path.Combine (outputDir, Path.GetFileNameWithoutExtension (xmlfile) + ".xsd");\r
294                                         DataSet ds = new DataSet ();\r
295                                         ds.InferXmlSchema (xmlfile, null);\r
296                                         ds.WriteXmlSchema (genFile);\r
297                                         Console.WriteLine ("Written file " + genFile);\r
298                                 }\r
299                         }\r
300                         else\r
301                         {\r
302                                 assemblies.AddRange (unknownFiles);\r
303                                 GenerateSchemas ();\r
304                         }\r
305                 }\r
306 \r
307                 public void GenerateSchemas ()\r
308                 {\r
309                         Assembly assembly = null;\r
310                         try\r
311                         {\r
312                                 assembly = Assembly.LoadFrom ((string) assemblies [0]);\r
313                         }\r
314                         catch (Exception ex)\r
315                         {\r
316                                 Error (errLoadAssembly, ex.Message);\r
317                         }\r
318                         \r
319                         Type[] types;\r
320                         \r
321                         if (lookupTypes.Count > 0)\r
322                         {\r
323                                 types = new Type [lookupTypes.Count];\r
324                                 for (int n=0; n<lookupTypes.Count; n++)\r
325                                 {\r
326                                         Type t = assembly.GetType ((string)lookupTypes[n]);\r
327                                         if (t == null) Error (typeNotFound, (string)lookupTypes[n]);\r
328                                         types[n] = t;\r
329                                 }\r
330                         }\r
331                         else\r
332                                 types = assembly.GetExportedTypes ();\r
333 \r
334                         XmlReflectionImporter ri = new XmlReflectionImporter ();\r
335                         XmlSchemas schemas = new XmlSchemas ();\r
336                         XmlSchemaExporter sx = new XmlSchemaExporter (schemas);\r
337 \r
338                         foreach (Type type in types)\r
339                         {\r
340                                 XmlTypeMapping tm = ri.ImportTypeMapping (type);\r
341                                 sx.ExportTypeMapping (tm);\r
342                         }\r
343 \r
344                         if (schemas.Count == 1)\r
345                         {\r
346                                 string fileName = Path.Combine (outputDir, "schema.xsd");\r
347                                 WriteSchema (fileName, schemas [0]);\r
348                         }\r
349                         else\r
350                         {\r
351                                 for (int n=0; n<schemas.Count; n++)\r
352                                 {\r
353                                         string fileName = Path.Combine (outputDir, "schema" + n + ".xsd");\r
354                                         WriteSchema (fileName, schemas [n]);\r
355                                 }\r
356                         }\r
357                 }\r
358 \r
359                 void WriteSchema (string fileName, XmlSchema schema)\r
360                 {\r
361                         StreamWriter sw = new StreamWriter (fileName);\r
362                         schema.Write (sw);\r
363                         sw.Close ();\r
364                         Console.WriteLine ("Written file " + fileName);\r
365                 }\r
366                 \r
367                 public void GenerateClasses ()\r
368                 {\r
369                         if (namesp == null) namesp = "Schemas";\r
370                         if (uri == null) uri = "";\r
371                         string targetFile = "";\r
372 \r
373                         XmlSchemas schemas = new XmlSchemas();\r
374                         foreach (string fileName in schemaNames)\r
375                         {\r
376                                 StreamReader sr = new StreamReader (fileName);\r
377                                 schemas.Add (XmlSchema.Read (sr, null));\r
378                                 sr.Close ();\r
379 \r
380                                 if (targetFile == "") targetFile = Path.GetFileNameWithoutExtension (fileName);\r
381                                 else targetFile += "_" + Path.GetFileNameWithoutExtension (fileName);\r
382                         }\r
383 \r
384                         targetFile += "." + provider.FileExtension;\r
385 \r
386                         CodeCompileUnit cunit = new CodeCompileUnit ();\r
387                         CodeNamespace codeNamespace = new CodeNamespace (namesp);\r
388                         cunit.Namespaces.Add (codeNamespace);\r
389                         codeNamespace.Comments.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));\r
390 \r
391                         // Locate elements to generate\r
392 \r
393                         ArrayList qnames = new ArrayList ();\r
394                         if (elements.Count > 0)\r
395                         {\r
396                                 foreach (string name in elements)\r
397                                         qnames.Add (new XmlQualifiedName (name, uri));\r
398                         }\r
399                         else\r
400                         {\r
401                                 foreach (XmlSchema schema in schemas) {\r
402                                         if (!schema.IsCompiled) schema.Compile (null);\r
403                                         foreach (XmlSchemaObject ob in schema.Items)\r
404                                                 if (ob is XmlSchemaElement)\r
405                                                         qnames.Add (((XmlSchemaElement)ob).QualifiedName);\r
406                                 }\r
407                         }\r
408 \r
409                         // Import schemas and generate the class model\r
410 \r
411                         XmlSchemaImporter importer = new XmlSchemaImporter (schemas);\r
412                         XmlCodeExporter sx = new XmlCodeExporter (codeNamespace, cunit);\r
413 \r
414                         ArrayList maps = new ArrayList();\r
415 \r
416                         foreach (XmlQualifiedName qname in qnames)\r
417                         {\r
418                                 XmlTypeMapping tm = importer.ImportTypeMapping (qname);\r
419                                 if (tm != null) maps.Add (tm);\r
420                         }\r
421                         \r
422                         foreach (XmlTypeMapping tm in maps)\r
423                         {\r
424                                 sx.ExportTypeMapping (tm);\r
425                         }\r
426 \r
427                         // Generate the code\r
428                         \r
429                         ICodeGenerator gen = provider.CreateGenerator();\r
430 \r
431                         string genFile = Path.Combine (outputDir, targetFile);\r
432                         StreamWriter sw = new StreamWriter(genFile, false);\r
433                         gen.GenerateCodeFromCompileUnit (cunit, sw, new CodeGeneratorOptions());\r
434                         sw.Close();\r
435 \r
436                         Console.WriteLine ("Written file " + genFile);\r
437                 }\r
438 \r
439                 public void GenerateDataset ()\r
440                 {\r
441                         if (namesp == null) namesp = "Schemas";\r
442                         if (uri == null) uri = "";\r
443                         string targetFile = "";\r
444 \r
445                         DataSet dataset = new DataSet ();\r
446                         foreach (string fileName in schemaNames)\r
447                         {\r
448                                 dataset.ReadXmlSchema (fileName);\r
449 \r
450                                 if (targetFile == "") targetFile = Path.GetFileNameWithoutExtension (fileName);\r
451                                 else targetFile += "_" + Path.GetFileNameWithoutExtension (fileName);\r
452                         }\r
453 \r
454                         targetFile += "." + provider.FileExtension;\r
455 \r
456                         CodeCompileUnit cunit = new CodeCompileUnit ();\r
457                         CodeNamespace codeNamespace = new CodeNamespace (namesp);\r
458                         cunit.Namespaces.Add (codeNamespace);\r
459                         codeNamespace.Comments.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));\r
460 \r
461                         // Generate the code\r
462                         \r
463                         ICodeGenerator gen = provider.CreateGenerator ();\r
464 \r
465                         TypedDataSetGenerator.Generate (dataset, codeNamespace, gen);\r
466 \r
467                         string genFile = Path.Combine (outputDir, targetFile);\r
468                         StreamWriter sw = new StreamWriter(genFile, false);\r
469                         gen.GenerateCodeFromCompileUnit (cunit, sw, new CodeGeneratorOptions());\r
470                         sw.Close();\r
471 \r
472                         Console.WriteLine ("Written file " + genFile);\r
473                 }\r
474 \r
475                 public void Error (string msg)\r
476                 {\r
477                         throw new Exception (msg);\r
478                 }\r
479 \r
480                 public void Error (string msg, string param)\r
481                 {\r
482                         throw new Exception (string.Format(msg,param));\r
483                 }\r
484 \r
485                 private string StripQuot (string input)\r
486                 {\r
487                         if (input.Length < 2)\r
488                                 return input;\r
489                         if (input [0] == '"' && input [input.Length -1] == '"' ||\r
490                                 input [0] == '\'' && input [input.Length - 1] == '\'')\r
491                                 return input.Substring (1, input.Length - 2);\r
492                         else\r
493                                 return language;\r
494                 }\r
495         }\r
496 }\r