[update-ecma-xml] Generate the //Type/@FullNameSP attribute if not present.
[mono.git] / mcs / tools / mdoc / Mono.Documentation / ecmadoc.cs
1 //
2 // ecmadoc.cs
3 //
4 // Author:
5 //   Jonathan Pryor  <jpryor@novell.com>
6 //
7 // Copyright (c) 2010 Novell, Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Linq;
33 using System.Text;
34 using System.Xml;
35 using System.Xml.Linq;
36
37 using Monodoc;
38 using Mono.Documentation;
39
40 using Mono.Options;
41 using Mono.Rocks;
42
43 namespace Mono.Documentation
44 {
45         public class MDocUpdateEcmaXml : MDocCommand
46         {
47                 string file = "CLILibraryTypes.xml";
48                 List<string> directories;
49                 Dictionary<string, HashSet<string>> libraries = new Dictionary<string, HashSet<string>>();
50
51                 public override void Run (IEnumerable<string> args)
52                 {
53                         string current_library = "";
54
55                         var options = new OptionSet () {
56                                 { "o|out=", 
57                                         "{FILE} to generate/update documentation within.\n" + 
58                                         "If not specified, will process " + file + ".\n" +
59                                         "Set to '-' to skip updates and write to standard output.",
60                                         v => file = v },
61                                 { "library=",
62                                         "The {LIBRARY} that the following --type=TYPE types should be a part of.",
63                                         v => current_library = v },
64                                 { "type=",
65                                         "The full {TYPE} name of a type to copy into the output file.",
66                                         v => AddTypeToLibrary (current_library, v) },
67                         };
68                         directories = Parse (options, args, "export-ecma-xml", 
69                                         "[OPTIONS]+ DIRECTORIES",
70                                         "Export mdoc documentation within DIRECTORIES into ECMA-format XML.\n\n" +
71                                         "DIRECTORIES are mdoc(5) directories as produced by 'mdoc update'.");
72                         if (directories == null || directories.Count == 0)
73                                 return;
74
75                         Update ();
76                 }
77
78                 void AddTypeToLibrary (string library, string type)
79                 {
80                         HashSet<string> types;
81                         if (!libraries.TryGetValue (library, out types))
82                                 libraries.Add (library, types = new HashSet<string> ());
83                         types.Add (type.Replace ('/', '.').Replace ('+', '.'));
84                 }
85
86                 void Update ()
87                 {
88                         XDocument input = LoadFile (this.file);
89
90                         var seenLibraries = new HashSet<string> ();
91
92                         Action<string> creator = file => {
93                                 using (var output = CreateWriter (file)) {
94                                         // spit out header comments, DTD, etc.
95                                         foreach (var node in input.Nodes ()) {
96                                                 if (node.NodeType == XmlNodeType.Element || node.NodeType == XmlNodeType.Text)
97                                                         continue;
98                                                 node.WriteTo (output);
99                                         }
100
101                                         using (var librariesElement = new Element (output, o => o.WriteStartElement ("Libraries"))) {
102                                                 UpdateExistingLibraries (input, output, seenLibraries);
103                                                 GenerateMissingLibraries (input, output, seenLibraries);
104                                         }
105                                         output.WriteWhitespace ("\r\n");
106                                 }
107                         };
108                         MdocFile.UpdateFile (this.file, creator);
109                 }
110
111                 static XDocument LoadFile (string file)
112                 {
113                         if (file == "-" || !File.Exists (file))
114                                 return CreateDefaultDocument ();
115
116                         var settings = new XmlReaderSettings {
117                                 ProhibitDtd = false,
118                         };
119                         using (var reader = XmlReader.Create (file, settings))
120                                 return XDocument.Load (reader, LoadOptions.PreserveWhitespace);
121                 }
122
123                 static XDocument CreateDefaultDocument ()
124                 {
125                         return new XDocument (
126                                         new XComment (" ====================================================================== "),
127                                         new XComment (" This XML is a description of the Common Language Infrastructure (CLI) library. "),
128                                         new XComment (" This file is a normative part of Partition IV of the following standards: ISO/IEC 23271 and ECMA 335 "),
129                                         new XComment (" ====================================================================== "),
130                                         new XDocumentType ("Libraries", null, "CLILibraryTypes.dtd", null),
131                                         new XElement ("Libraries"));
132                 }
133
134                 static XmlWriter CreateWriter (string file)
135                 {
136                         var settings = new XmlWriterSettings {
137                                 Indent              = true,
138                                 IndentChars         = "\t",
139                                 NewLineChars        = "\r\n",
140                                 OmitXmlDeclaration  = true,
141                         };
142
143                         if (file == "-")
144                                 return XmlWriter.Create (Console.Out, settings);
145
146                         settings.Encoding = new UTF8Encoding (false);
147                         return XmlWriter.Create (file, settings);
148                 }
149
150                 struct Element : IDisposable {
151                         XmlWriter output;
152
153                         public Element (XmlWriter output, Action<XmlWriter> action)
154                         {
155                                 this.output = output;
156                                 action (output);
157                         }
158
159                         public void Dispose ()
160                         {
161                                 output.WriteEndElement ();
162                         }
163                 }
164
165                 void UpdateExistingLibraries (XDocument input, XmlWriter output, HashSet<string> seenLibraries)
166                 {
167                         foreach (XElement types in input.Root.Elements ()) {
168                                 XAttribute library = types.Attribute ("Library");
169                                 HashSet<string> libraryTypes;
170                                 if (library == null || !libraries.TryGetValue (library.Value, out libraryTypes)) {
171                                         types.WriteTo (output);
172                                         continue;
173                                 }
174                                 seenLibraries.Add (library.Value);
175                                 var seenTypes = new HashSet<string> ();
176                                 using (Element typesElement = CreateTypesElement (output, library.Value)) {
177                                         foreach (XElement type in types.Elements ()) {
178                                                 XAttribute fullName = type.Attribute ("FullName");
179                                                 string typeName = fullName == null
180                                                         ? null
181                                                         : XmlDocUtils.ToEscapedTypeName (fullName.Value);
182                                                 if (typeName == null || !libraryTypes.Contains (typeName)) {
183                                                         type.WriteTo (output);
184                                                         continue;
185                                                 }
186                                                 seenTypes.Add (typeName);
187                                                 WriteType (output, typeName);
188                                         }
189                                         foreach (string type in libraryTypes.Except (seenTypes))
190                                                 WriteType (output, type);
191                                 }
192                         }
193                 }
194
195                 static Element CreateTypesElement (XmlWriter output, string library)
196                 {
197                         return new Element (
198                                         output, 
199                                         o => 
200                                                 {o.WriteStartElement ("Types"); 
201                                                 o.WriteAttributeString ("Library", library);});
202                 }
203
204                 void WriteType (XmlWriter output, string typeName)
205                 {
206                         XElement type = LoadType (typeName);
207
208                         XAttribute fullName   = type.Attribute ("FullName");
209                         XAttribute fullNameSp = type.Attribute ("FullNameSP");
210                         if (fullNameSp == null && fullName != null) {
211                                 type.Add (new XAttribute ("FullNameSP", fullName.Value.Replace ('.', '_')));
212                         }
213
214                         type.WriteTo (output);
215                 }
216
217                 void GenerateMissingLibraries (XDocument input, XmlWriter output, HashSet<string> seenLibraries)
218                 {
219                         foreach (KeyValuePair<string, HashSet<string>> lib in libraries) {
220                                 if (seenLibraries.Contains (lib.Key))
221                                         continue;
222                                 seenLibraries.Add (lib.Key);
223                                 using (var typesElement = CreateTypesElement (output, lib.Key)) {
224                                         foreach (string type in lib.Value) {
225                                                 LoadType (type).WriteTo (output);
226                                         }
227                                 }
228                         }
229                 }
230
231                 XElement LoadType (string type)
232                 {
233                         foreach (KeyValuePair<string, string> permutation in GetTypeDirectoryFilePermutations (type)) {
234                                 foreach (string root in directories) {
235                                         string path = Path.Combine (root, Path.Combine (permutation.Key, permutation.Value + ".xml"));
236                                         if (File.Exists (path))
237                                                 return XElement.Load (path);
238                                 }
239                         }
240                         throw new FileNotFoundException ("Unable to find documentation file for type: " + type + ".");
241                 }
242
243                 // type has been "normalized", which (alas) means we have ~no clue which
244                 // part is the namespace and which is the type name, particularly
245                 // problematic as types may be nested to any level.
246                 // Try ~all permutations. :-)
247                 static IEnumerable<KeyValuePair<string, string>> GetTypeDirectoryFilePermutations (string type)
248                 {
249                         int end = type.Length;
250                         int dot;
251                         while ((dot = type.LastIndexOf ('.', end-1)) >= 0) {
252                                 yield return new KeyValuePair<string, string> (
253                                                 type.Substring (0, dot), 
254                                                 type.Substring (dot+1).Replace ('.', '+'));
255                                 end = dot;
256                         }
257                         yield return new KeyValuePair<string, string> ("", type.Replace ('.', '+'));
258                 }
259         }
260 }