Copied remotely
[mono.git] / mcs / class / Mono.Xml.Ext / Mono.Xml / XmlNodeWriter.cs
1 //
2 // Mono.Xml.XmlNodeWriter
3 //
4 // Author:
5 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 //      (C)2003 Atsushi Enomoto
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Xml;
32
33 namespace Mono.Xml
34 {
35         public class XmlNodeWriter : XmlWriter
36         {
37                 public XmlNodeWriter () : this (true)
38                 {
39                 }
40
41                 // It should be public after some tests are done :-)
42                 public XmlNodeWriter (bool isDocumentEntity)
43                 {
44                         doc = new XmlDocument ();
45                         state = XmlNodeType.None;
46                         this.isDocumentEntity = isDocumentEntity;
47                         if (!isDocumentEntity)
48                                 current = fragment = doc.CreateDocumentFragment ();
49                 }
50
51                 XmlDocument doc;
52                 bool isClosed;
53                 // If it is not null, then we are now inside the element.
54                 XmlNode current;
55                 // If it is not null, then we are now inside the attribute.
56                 XmlAttribute attribute;
57                 // If it is false, then allow to contain multiple document elements.
58                 bool isDocumentEntity;
59                 XmlDocumentFragment fragment;
60
61                 // None: started or closed.
62                 // XmlDeclaration: after xmldecl. Never allow xmldecl.
63                 // DocumentType: after doctype. Never allow xmldecl and doctype.
64                 // Element: inside document element.
65                 // 
66                 XmlNodeType state;
67
68                 // Properties
69                 public XmlNode Document {
70                         get { return isDocumentEntity ? (XmlNode)doc : (XmlNode)fragment; }
71                 }
72
73                 public override WriteState WriteState {
74                         get {
75                                 if (isClosed)
76                                         return WriteState.Closed;
77                                 if (attribute != null)
78                                         return WriteState.Attribute;
79
80                                 switch (state) {
81                                 case XmlNodeType.None:
82                                         return WriteState.Start;
83                                 case XmlNodeType.XmlDeclaration:
84                                         return WriteState.Prolog;
85                                 case XmlNodeType.DocumentType:
86                                         return WriteState.Element;
87                                 default:
88                                         return WriteState.Content;
89                                 }
90                         }
91                 }
92
93                 public override string XmlLang {
94                         get {
95                                 for (XmlElement n = current as XmlElement; n != null; n = n.ParentNode as XmlElement)
96                                         if (n.HasAttribute ("xml:lang"))
97                                                 return n.GetAttribute ("xml:lang");
98                                 return String.Empty;
99                         }
100                 }
101
102                 public override XmlSpace XmlSpace {
103                         get {
104                                 for (XmlElement n = current as XmlElement; n != null; n = n.ParentNode as XmlElement) {
105                                         string xs = n.GetAttribute ("xml:space");
106                                         switch (xs) {
107                                         case "preserve":
108                                                 return XmlSpace.Preserve;
109                                         case "default":
110                                                 return XmlSpace.Default;
111                                         case "":
112                                                 continue;
113                                         default:
114                                                 throw new InvalidOperationException (String.Format ("Invalid xml:space {0}.", xs));
115                                         }
116                                 }
117                                 return XmlSpace.None;
118                         }
119                 }
120
121                 // Private Methods
122
123                 private void CheckState ()
124                 {
125                         if (isClosed)
126                                 throw new InvalidOperationException ();
127
128                 }
129
130                 private void WritePossiblyTopLevelNode (XmlNode n, bool possiblyAttribute)
131                 {
132                         CheckState ();
133                         if (!possiblyAttribute && attribute != null)
134                                 throw new InvalidOperationException (String.Format ("Current state is not acceptable for {0}.", n.NodeType));
135
136                         if (state != XmlNodeType.Element)
137                                 Document.AppendChild (n);
138                         else if (attribute != null)
139                                 attribute.AppendChild (n);
140                         else
141                                 current.AppendChild (n);
142                         if (state == XmlNodeType.None)
143                                 state = XmlNodeType.XmlDeclaration;
144                 }
145
146                 // Public Methods
147
148                 public override void Close ()
149                 {
150                         CheckState ();
151                         isClosed = true;
152                 }
153
154                 public override void Flush ()
155                 {
156                 }
157
158                 public override string LookupPrefix (string ns)
159                 {
160                         CheckState ();
161                         if (current == null)
162                                 throw new InvalidOperationException ();
163                         return current.GetPrefixOfNamespace (ns);
164                 }
165
166                 // StartDocument
167
168                 public override void WriteStartDocument ()
169                 {
170                         WriteStartDocument (null);
171                 }
172
173                 public override void WriteStartDocument (bool standalone)
174                 {
175                         WriteStartDocument (standalone ? "yes" : "no");
176                 }
177
178                 private void WriteStartDocument (string sddecl)
179                 {
180                         CheckState ();
181                         if (state != XmlNodeType.None)
182                                 throw new InvalidOperationException ("Current state is not acceptable for xmldecl.");
183
184                         doc.AppendChild (doc.CreateXmlDeclaration ("1.0", null, sddecl));
185                         state = XmlNodeType.XmlDeclaration;
186                 }
187                 
188                 // EndDocument
189                 
190                 public override void WriteEndDocument ()
191                 {
192                         CheckState ();
193
194                         isClosed = true;
195                 }
196
197                 // DocumentType
198                 public override void WriteDocType (string name, string publicId, string systemId, string internalSubset)
199                 {
200                         CheckState ();
201                         switch (state) {
202                         case XmlNodeType.None:
203                         case XmlNodeType.XmlDeclaration:
204                                 doc.AppendChild (doc.CreateDocumentType (name, publicId, systemId, internalSubset));
205                                 state = XmlNodeType.DocumentType;
206                                 break;
207                         default:
208                                 throw new InvalidOperationException ("Current state is not acceptable for doctype.");
209                         }
210                 }
211
212                 // StartElement
213
214                 public override void WriteStartElement (string prefix, string name, string ns)
215                 {
216                         CheckState ();
217                         if (isDocumentEntity && state == XmlNodeType.EndElement && doc.DocumentElement != null)
218                                 throw new InvalidOperationException ("Current state is not acceptable for startElement.");
219
220                         XmlElement el = doc.CreateElement (prefix, name, ns);
221                         if (current == null) {
222                                 Document.AppendChild (el);
223                                 state = XmlNodeType.Element;
224                         } else {
225                                 current.AppendChild (el);
226                                 state = XmlNodeType.Element;
227                         }
228
229                         current = el;
230                 }
231
232                 // EndElement
233
234                 public override void WriteEndElement ()
235                 {
236                         WriteEndElementInternal (false);
237                 }
238                 
239                 public override void WriteFullEndElement ()
240                 {
241                         WriteEndElementInternal (true);
242                 }
243
244                 private void WriteEndElementInternal (bool forceFull)
245                 {
246                         CheckState ();
247                         if (current == null)
248                                 throw new InvalidOperationException ("Current state is not acceptable for endElement.");
249
250                         if (!forceFull && current.FirstChild == null)
251                                 ((XmlElement) current).IsEmpty = true;
252
253                         if (isDocumentEntity && current.ParentNode == doc)
254                                 state = XmlNodeType.EndElement;
255                         else
256                                 current = current.ParentNode;
257                 }
258
259                 // StartAttribute
260
261                 public override void WriteStartAttribute (string prefix, string name, string ns)
262                 {
263                         CheckState ();
264                         if (attribute != null)
265                                 throw new InvalidOperationException ("There is an open attribute.");
266                         if (!(current is XmlElement))
267                                 throw new InvalidOperationException ("Current state is not acceptable for startAttribute.");
268
269                         attribute = doc.CreateAttribute (prefix, name, ns);
270                         ((XmlElement)current).SetAttributeNode (attribute);
271                 }
272
273                 public override void WriteEndAttribute ()
274                 {
275                         CheckState ();
276                         if (attribute == null)
277                                 throw new InvalidOperationException ("Current state is not acceptable for startAttribute.");
278
279                         attribute = null;
280                 }
281
282                 public override void WriteCData (string data)
283                 {
284                         CheckState ();
285                         if (current == null)
286                                 throw new InvalidOperationException ("Current state is not acceptable for CDATAsection.");
287
288                         current.AppendChild (doc.CreateCDataSection (data));
289                 }
290
291                 public override void WriteComment (string comment)
292                 {
293                         WritePossiblyTopLevelNode (doc.CreateComment (comment), false);
294                 }
295
296                 public override void WriteProcessingInstruction (string name, string value)
297                 {
298                         WritePossiblyTopLevelNode (
299                                 doc.CreateProcessingInstruction (name, value), false);
300                 }
301
302                 public override void WriteEntityRef (string name)
303                 {
304                         WritePossiblyTopLevelNode (doc.CreateEntityReference (name), true);
305                 }
306
307                 public override void WriteCharEntity (char c)
308                 {
309                         WritePossiblyTopLevelNode (doc.CreateTextNode (new string (new char [] {c}, 0, 1)), true);
310                 }
311
312                 public override void WriteWhitespace (string ws)
313                 {
314                         WritePossiblyTopLevelNode (doc.CreateWhitespace (ws), true);
315                 }
316
317                 public override void WriteString (string data)
318                 {
319                         CheckState ();
320                         if (current == null)
321                                 throw new InvalidOperationException ("Current state is not acceptable for Text.");
322
323                         if (attribute != null)
324                                 attribute.AppendChild (doc.CreateTextNode (data));
325                         else {
326                                 XmlText last = current.LastChild as XmlText;
327                                 if (last == null)
328                                         current.AppendChild(doc.CreateTextNode(data));
329                                 else 
330                                         last.AppendData(data);
331                         }
332                 }
333
334                 public override void WriteName (string name)
335                 {
336                         WriteString (name);
337                 }
338
339                 public override void WriteNmToken (string nmtoken)
340                 {
341                         WriteString (nmtoken);
342                 }
343
344                 public override void WriteQualifiedName (string name, string ns)
345                 {
346                         string prefix = LookupPrefix (ns);
347                         if (prefix == null)
348                                 throw new ArgumentException (String.Format ("Invalid namespace {0}", ns));
349                         if (prefix != String.Empty)
350                                 WriteString (name);
351                         else
352                                 WriteString (prefix + ":" + name);
353                 }
354
355                 public override void WriteChars (char [] chars, int start, int len)
356                 {
357                         WriteString (new string (chars, start, len));
358                 }
359
360                 public override void WriteRaw (string data)
361                 {
362                         // It never supports raw string.
363                         WriteString (data);
364                 }
365
366                 public override void WriteRaw (char [] chars, int start, int len)
367                 {
368                         // It never supports raw string.
369                         WriteChars (chars, start, len);
370                 }
371
372                 public override void WriteBase64 (byte [] data, int start, int len)
373                 {
374                         // It never supports raw string.
375                         WriteString (Convert.ToBase64String (data, start, len));
376                 }
377                 
378                 public override void WriteBinHex (byte [] data, int start, int len)
379                 {
380                         throw new NotImplementedException ();
381                 }
382
383                 public override void WriteSurrogateCharEntity (char c1, char c2)
384                 {
385                         throw new NotImplementedException ();
386                 }
387         }
388 }