2004-08-07 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 to use for the generated code.\n" +\r
44                         "                      Currently, the only supported language is CS (C#).\n" +\r
45                         "   /g /generator:TYPE Code Generator type name, followed by ','\n" + \r
46                         "                      and assembly file name.\n" +\r
47                         "   /o /outputdir:PATH The directory where to generate the code or schemas.\n" +\r
48                         "   /n /namespace:NAME Namespace for the generated code.\n" +\r
49                         "   /t /type:NAME      Type for which to generate an xml schema.\n" +\r
50                         "                      Multiple types can be specified.\n" +\r
51                         "   /h /help           Output this help.\n";\r
52 \r
53                 static readonly string incorrectOrder = "Options must be specified after Assembly or schema file names";\r
54                 static readonly string duplicatedParam = "The option {0} cannot be specified more that once";\r
55                 static readonly string unknownOption = "Unknown option {0}";\r
56                 static readonly string incompatibleArgs = "Cannot mix options for generating schemas and for generatic classes";\r
57                 static readonly string invalidParams = "Invalid parameters";\r
58                 static readonly string tooManyAssem = "Only one assembly name can be specified";\r
59                 static readonly string errLoadAssembly = "Could not load assembly: {0}";\r
60                 static readonly string typeNotFound = "Type {0} not found in the specified assembly";\r
61                 static readonly string languageNotSupported = "The language {0} is not supported";\r
62                 static readonly string missingOutputForXsdInput = "Can only generate one of classes or datasets.";\r
63                 static readonly string generatorAssemblyNotFound = "Could not load code provider assembly file: {0}";\r
64                 static readonly string generatorTypeNotFound = "Could not find specified code provider type: {0}";\r
65                 static readonly string generatorTypeIsNotCodeGenerator = "Specified code provider type was not CodeDomProvider: {0}";\r
66                 static readonly string generatorThrewException = "Specified CodeDomProvider raised an error while creating its instance: {0}";\r
67 \r
68                 static void Main (string [] args)\r
69                 {\r
70                         if (args.Length < 1) {\r
71                                 Console.WriteLine (helpString);\r
72                                 Environment.Exit (0);\r
73                         }\r
74 \r
75                         try\r
76                         {\r
77                                 new Driver().Run (args);\r
78                         }\r
79                         catch (Exception ex)\r
80                         {\r
81                                 Console.WriteLine (ex.Message);\r
82                                 Console.WriteLine (ex);\r
83                         }\r
84                 }\r
85 \r
86                 string outputDir = null;\r
87 \r
88 \r
89                 ArrayList lookupTypes = new ArrayList();\r
90                 ArrayList assemblies = new ArrayList();\r
91 \r
92                 ArrayList schemaNames = new ArrayList();\r
93                 ArrayList inferenceNames = new ArrayList();\r
94                 ArrayList elements = new ArrayList();\r
95                 string language = null;\r
96                 string namesp = null;\r
97                 string uri = null;\r
98                 string providerOption = null;\r
99                 CodeDomProvider provider;\r
100 \r
101                 public void Run (string[] args)\r
102                 {\r
103                         ArrayList unknownFiles = new ArrayList();\r
104                         bool generateClasses = false;\r
105                         bool readingFiles = true;\r
106                         bool schemasOptions = false;\r
107                         bool assemblyOptions = false;\r
108                         bool generateDataset = false;\r
109                         bool inference = false;\r
110 \r
111                         foreach (string arg in args)\r
112                         {\r
113                                 if (!arg.StartsWith ("--") && !arg.StartsWith ("/") ||\r
114                                         (arg.StartsWith ("/") && arg.IndexOfAny (Path.InvalidPathChars) == -1)\r
115                                         ) \r
116                                 {\r
117                                         if ((arg.EndsWith (".dll") || arg.EndsWith (".exe")) && !arg.Substring (1).StartsWith ("generator:") && !arg.Substring (1).StartsWith ("g:"))\r
118                                         {\r
119                                                 if (!readingFiles) throw new Exception (incorrectOrder);\r
120                                                 assemblies.Add (arg);\r
121                                                 assemblyOptions = true;\r
122                                                 continue;\r
123                                         }\r
124                                         else if (arg.EndsWith (".xsd"))\r
125                                         {\r
126                                                 if (!readingFiles) Error (incorrectOrder);\r
127                                                 schemaNames.Add (arg);\r
128                                                 schemasOptions = true;\r
129                                                 continue;\r
130                                         }\r
131                                         else if (arg.EndsWith (".xml"))\r
132                                         {\r
133                                                 if (generateClasses || generateDataset) Error (duplicatedParam);\r
134                                                 inferenceNames.Add (arg);\r
135                                                 inference = true;\r
136                                                 continue;\r
137                                         }\r
138                                         else if (!arg.StartsWith ("/"))\r
139                                         {\r
140                                                 if (!readingFiles) Error (incorrectOrder);\r
141                                                 unknownFiles.Add (arg);\r
142                                                 continue;\r
143                                         }\r
144                                 }\r
145 \r
146                                 readingFiles = false;\r
147 \r
148                                 int i = arg.IndexOf (":");\r
149                                 if (i == -1) i = arg.Length;\r
150                                 string option = arg.Substring (1,i-1);\r
151                                 string param = (i<arg.Length-1) ? arg.Substring (i+1) : "";\r
152 \r
153                                 if (option == "classes" || option == "c")\r
154                                 {\r
155                                         if (generateClasses || generateDataset || inference) Error (duplicatedParam, option);\r
156                                         generateClasses = true;\r
157                                         schemasOptions = true;\r
158                                 }\r
159                                 else if (option == "dataset" || option == "d")\r
160                                 {\r
161                                         if (generateClasses || generateDataset || inference) Error (duplicatedParam, option);\r
162                                         generateDataset = true;\r
163                                         schemasOptions = true;\r
164                                 }\r
165                                 else if (option == "element" || option == "e")\r
166                                 {\r
167                                         elements.Add (param);\r
168                                         schemasOptions = true;\r
169                                 }\r
170                                 else if (option == "language" || option == "l")\r
171                                 {\r
172                                         if (provider != null) Error (duplicatedParam, option);\r
173                                         if (language != null) Error (duplicatedParam, option);\r
174                                         language = param;\r
175                                         schemasOptions = true;\r
176                                 }\r
177                                 else if (option == "namespace" || option == "n")\r
178                                 {\r
179                                         if (namesp != null) Error (duplicatedParam, option);\r
180                                         namesp = param;\r
181                                         schemasOptions = true;\r
182                                 }\r
183                                 else if (option == "outputdir" || option == "o")\r
184                                 {\r
185                                         if (outputDir != null) Error (duplicatedParam, option);\r
186                                         outputDir = param;\r
187                                 }\r
188                                 else if (option == "uri" || option == "u")\r
189                                 {\r
190                                         if (uri != null) Error (duplicatedParam, option);\r
191                                         uri = param;\r
192                                         schemasOptions = true;\r
193                                 }\r
194                                 else if (option == "type" || option == "t")\r
195                                 {\r
196                                         lookupTypes.Add (param);\r
197                                         assemblyOptions = true;\r
198                                 }\r
199                                 else if (option == "generator" || option == "g")\r
200                                 {\r
201                                         providerOption = param;\r
202                                 }\r
203                                 else if (option == "help" || option == "h")\r
204                                 {\r
205                                         Console.WriteLine (helpString);\r
206                                         return;\r
207                                 }\r
208                                 else\r
209                                         Error (unknownOption, option);\r
210                         }\r
211 \r
212                         if (!schemasOptions && !assemblyOptions && !inference)\r
213                                 Error (invalidParams);\r
214 \r
215                         if (schemasOptions && assemblyOptions)\r
216                                 Error (incompatibleArgs);\r
217 \r
218                         if (assemblies.Count > 1)\r
219                                 Error (tooManyAssem);\r
220 \r
221                         if (outputDir == null) outputDir = ".";\r
222 \r
223                         if (language != null) {\r
224                                 switch (language) {\r
225                                 case "CS":\r
226                                         provider = new CSharpCodeProvider ();\r
227                                         break;\r
228                                 case "VB":\r
229                                         provider = new VBCodeProvider ();\r
230                                         break;\r
231                                 default:\r
232                                         Error (languageNotSupported, language);\r
233                                         break;\r
234                                 }\r
235                         }\r
236 \r
237                         if (providerOption != null) {\r
238                                 string param = providerOption;\r
239                                 string typename;\r
240                                 Type generatorType;\r
241                                 int comma = param.IndexOf (',');\r
242                                 if (comma < 0) {\r
243                                         typename = param;\r
244                                         generatorType = Type.GetType (param);\r
245                                 } else {\r
246                                         typename = param.Substring (0, comma);\r
247                                         string asmName = param.Substring (comma + 1);\r
248 #if NET_1_1\r
249                                         Assembly asm = Assembly.LoadFile (asmName);\r
250 #else\r
251                                         Assembly asm = Assembly.LoadFrom (asmName);\r
252 #endif\r
253                                         if (asm == null)\r
254                                                 Error (generatorAssemblyNotFound, asmName);\r
255                                         generatorType = asm.GetType (typename);\r
256                                 }\r
257                                 if (generatorType == null)\r
258                                         Error (generatorTypeNotFound, typename);\r
259                                 if (!generatorType.IsSubclassOf (typeof (CodeDomProvider)))\r
260                                         Error (generatorTypeIsNotCodeGenerator, typename);\r
261                                 try {\r
262                                         provider = (CodeDomProvider) Activator.CreateInstance (generatorType, null);\r
263                                 } catch (Exception ex) {\r
264                                         Error (generatorThrewException, param);\r
265                                 }\r
266                                 Console.WriteLine ("Loaded custom generator type " + param + " .");\r
267                         }\r
268                         if (provider == null)\r
269                                 provider = new CSharpCodeProvider ();\r
270 \r
271                         if (schemasOptions)\r
272                         {\r
273                                 if (!generateClasses && !generateDataset)\r
274                                         Error (missingOutputForXsdInput);\r
275                                 schemaNames.AddRange (unknownFiles);\r
276                                 if (generateClasses)\r
277                                         GenerateClasses ();\r
278                                 else if (generateDataset)\r
279                                         GenerateDataset ();\r
280                         }\r
281                         else if (inference)\r
282                         {\r
283                                 foreach (string xmlfile in inferenceNames) {\r
284                                         string genFile = Path.Combine (outputDir, Path.GetFileNameWithoutExtension (xmlfile) + ".xsd");\r
285                                         DataSet ds = new DataSet ();\r
286                                         ds.InferXmlSchema (xmlfile, null);\r
287                                         ds.WriteXmlSchema (genFile);\r
288                                         Console.WriteLine ("Written file " + genFile);\r
289                                 }\r
290                         }\r
291                         else\r
292                         {\r
293                                 assemblies.AddRange (unknownFiles);\r
294                                 GenerateSchemas ();\r
295                         }\r
296                 }\r
297 \r
298                 public void GenerateSchemas ()\r
299                 {\r
300                         Assembly assembly = null;\r
301                         try\r
302                         {\r
303                                 assembly = Assembly.LoadFrom ((string) assemblies [0]);\r
304                         }\r
305                         catch (Exception ex)\r
306                         {\r
307                                 Error (errLoadAssembly, ex.Message);\r
308                         }\r
309                         \r
310                         Type[] types;\r
311                         \r
312                         if (lookupTypes.Count > 0)\r
313                         {\r
314                                 types = new Type [lookupTypes.Count];\r
315                                 for (int n=0; n<lookupTypes.Count; n++)\r
316                                 {\r
317                                         Type t = assembly.GetType ((string)lookupTypes[n]);\r
318                                         if (t == null) Error (typeNotFound, (string)lookupTypes[n]);\r
319                                         types[n] = t;\r
320                                 }\r
321                         }\r
322                         else\r
323                                 types = assembly.GetExportedTypes ();\r
324 \r
325                         XmlReflectionImporter ri = new XmlReflectionImporter ();\r
326                         XmlSchemas schemas = new XmlSchemas ();\r
327                         XmlSchemaExporter sx = new XmlSchemaExporter (schemas);\r
328 \r
329                         foreach (Type type in types)\r
330                         {\r
331                                 XmlTypeMapping tm = ri.ImportTypeMapping (type);\r
332                                 sx.ExportTypeMapping (tm);\r
333                         }\r
334 \r
335                         if (schemas.Count == 1)\r
336                         {\r
337                                 string fileName = Path.Combine (outputDir, "schema.xsd");\r
338                                 WriteSchema (fileName, schemas [0]);\r
339                         }\r
340                         else\r
341                         {\r
342                                 for (int n=0; n<schemas.Count; n++)\r
343                                 {\r
344                                         string fileName = Path.Combine (outputDir, "schema" + n + ".xsd");\r
345                                         WriteSchema (fileName, schemas [n]);\r
346                                 }\r
347                         }\r
348                 }\r
349 \r
350                 void WriteSchema (string fileName, XmlSchema schema)\r
351                 {\r
352                         StreamWriter sw = new StreamWriter (fileName);\r
353                         schema.Write (sw);\r
354                         sw.Close ();\r
355                         Console.WriteLine ("Written file " + fileName);\r
356                 }\r
357                 \r
358                 public void GenerateClasses ()\r
359                 {\r
360                         if (namesp == null) namesp = "Schemas";\r
361                         if (uri == null) uri = "";\r
362                         string targetFile = "";\r
363 \r
364                         XmlSchemas schemas = new XmlSchemas();\r
365                         foreach (string fileName in schemaNames)\r
366                         {\r
367                                 StreamReader sr = new StreamReader (fileName);\r
368                                 schemas.Add (XmlSchema.Read (sr, null));\r
369                                 sr.Close ();\r
370 \r
371                                 if (targetFile == "") targetFile = Path.GetFileNameWithoutExtension (fileName);\r
372                                 else targetFile += "_" + Path.GetFileNameWithoutExtension (fileName);\r
373                         }\r
374 \r
375                         targetFile += "." + provider.FileExtension;\r
376 \r
377                         CodeCompileUnit cunit = new CodeCompileUnit ();\r
378                         CodeNamespace codeNamespace = new CodeNamespace (namesp);\r
379                         cunit.Namespaces.Add (codeNamespace);\r
380                         codeNamespace.Comments.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));\r
381 \r
382                         // Locate elements to generate\r
383 \r
384                         ArrayList qnames = new ArrayList ();\r
385                         if (elements.Count > 0)\r
386                         {\r
387                                 foreach (string name in elements)\r
388                                         qnames.Add (new XmlQualifiedName (name, uri));\r
389                         }\r
390                         else\r
391                         {\r
392                                 foreach (XmlSchema schema in schemas) {\r
393                                         if (!schema.IsCompiled) schema.Compile (null);\r
394                                         foreach (XmlSchemaObject ob in schema.Items)\r
395                                                 if (ob is XmlSchemaElement)\r
396                                                         qnames.Add (((XmlSchemaElement)ob).QualifiedName);\r
397                                 }\r
398                         }\r
399 \r
400                         // Import schemas and generate the class model\r
401 \r
402                         XmlSchemaImporter importer = new XmlSchemaImporter (schemas);\r
403                         XmlCodeExporter sx = new XmlCodeExporter (codeNamespace, cunit);\r
404 \r
405                         ArrayList maps = new ArrayList();\r
406 \r
407                         foreach (XmlQualifiedName qname in qnames)\r
408                         {\r
409                                 XmlTypeMapping tm = importer.ImportTypeMapping (qname);\r
410                                 if (tm != null) maps.Add (tm);\r
411                         }\r
412                         \r
413                         foreach (XmlTypeMapping tm in maps)\r
414                         {\r
415                                 sx.ExportTypeMapping (tm);\r
416                         }\r
417 \r
418                         // Generate the code\r
419                         \r
420                         ICodeGenerator gen = provider.CreateGenerator();\r
421 \r
422                         string genFile = Path.Combine (outputDir, targetFile);\r
423                         StreamWriter sw = new StreamWriter(genFile, false);\r
424                         gen.GenerateCodeFromCompileUnit (cunit, sw, new CodeGeneratorOptions());\r
425                         sw.Close();\r
426 \r
427                         Console.WriteLine ("Written file " + genFile);\r
428                 }\r
429 \r
430                 public void GenerateDataset ()\r
431                 {\r
432                         if (namesp == null) namesp = "Schemas";\r
433                         if (uri == null) uri = "";\r
434                         string targetFile = "";\r
435 \r
436                         DataSet dataset = new DataSet ();\r
437                         foreach (string fileName in schemaNames)\r
438                         {\r
439                                 dataset.ReadXmlSchema (fileName);\r
440 \r
441                                 if (targetFile == "") targetFile = Path.GetFileNameWithoutExtension (fileName);\r
442                                 else targetFile += "_" + Path.GetFileNameWithoutExtension (fileName);\r
443                         }\r
444 \r
445                         targetFile += "." + provider.FileExtension;\r
446 \r
447                         CodeCompileUnit cunit = new CodeCompileUnit ();\r
448                         CodeNamespace codeNamespace = new CodeNamespace (namesp);\r
449                         cunit.Namespaces.Add (codeNamespace);\r
450                         codeNamespace.Comments.Add (new CodeCommentStatement ("\nThis source code was auto-generated by MonoXSD\n"));\r
451 \r
452                         // Generate the code\r
453                         \r
454                         ICodeGenerator gen = provider.CreateGenerator ();\r
455 \r
456                         TypedDataSetGenerator.Generate (dataset, codeNamespace, gen);\r
457 \r
458                         string genFile = Path.Combine (outputDir, targetFile);\r
459                         StreamWriter sw = new StreamWriter(genFile, false);\r
460                         gen.GenerateCodeFromCompileUnit (cunit, sw, new CodeGeneratorOptions());\r
461                         sw.Close();\r
462 \r
463                         Console.WriteLine ("Written file " + genFile);\r
464                 }\r
465 \r
466                 public void Error (string msg)\r
467                 {\r
468                         throw new Exception (msg);\r
469                 }\r
470 \r
471                 public void Error (string msg, string param)\r
472                 {\r
473                         throw new Exception (string.Format(msg,param));\r
474                 }\r
475         }\r
476 }\r