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