Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Core / XmlAutoDetectWriter.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlAutoDetectWriter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8
9 namespace System.Xml {
10     using System;
11     using System.Diagnostics;
12     using System.IO;
13     using System.Text;
14     using System.Xml;
15     using System.Xml.Schema;
16
17
18     /// <summary>
19     /// This writer implements XmlOutputMethod.AutoDetect.  If the first element is "html", then output will be
20     /// directed to an Html writer.  Otherwise, output will be directed to an Xml writer.
21     /// </summary>
22     internal class XmlAutoDetectWriter : XmlRawWriter, IRemovableWriter {
23         private XmlRawWriter wrapped;
24         private OnRemoveWriter onRemove;
25         private XmlWriterSettings writerSettings;
26         private XmlEventCache eventCache;           // Cache up events until first StartElement is encountered
27         private TextWriter textWriter;
28         private Stream strm;
29
30         //-----------------------------------------------
31         // Constructors
32         //-----------------------------------------------
33
34         private XmlAutoDetectWriter(XmlWriterSettings writerSettings) {
35             Debug.Assert(writerSettings.OutputMethod == XmlOutputMethod.AutoDetect);
36
37             this.writerSettings = (XmlWriterSettings)writerSettings.Clone();
38             this.writerSettings.ReadOnly = true;
39
40             // Start caching all events
41             this.eventCache = new XmlEventCache(string.Empty, true);
42         }
43
44         public XmlAutoDetectWriter(TextWriter textWriter, XmlWriterSettings writerSettings)
45             : this(writerSettings) {
46             this.textWriter = textWriter;
47         }
48
49         public XmlAutoDetectWriter(Stream strm, XmlWriterSettings writerSettings)
50             : this(writerSettings) {
51             this.strm = strm;
52         }
53
54
55         //-----------------------------------------------
56         // IRemovableWriter interface
57         //-----------------------------------------------
58
59         /// <summary>
60         /// This writer will raise this event once it has determined whether to replace itself with the Html or Xml writer.
61         /// </summary>
62         public OnRemoveWriter OnRemoveWriterEvent {
63             get { return this.onRemove; }
64             set { this.onRemove = value; }
65         }
66
67
68         //-----------------------------------------------
69         // XmlWriter interface
70         //-----------------------------------------------
71
72         public override XmlWriterSettings Settings {
73             get { return this.writerSettings; }
74         }
75
76         public override void WriteDocType(string name, string pubid, string sysid, string subset) {
77             EnsureWrappedWriter(XmlOutputMethod.Xml);
78             this.wrapped.WriteDocType(name, pubid, sysid, subset);
79         }
80
81         public override void WriteStartElement(string prefix, string localName, string ns) {
82             if (this.wrapped == null) {
83                 // This is the first time WriteStartElement has been called, so create the Xml or Html writer
84                 if (ns.Length == 0 && IsHtmlTag(localName))
85                     CreateWrappedWriter(XmlOutputMethod.Html);
86                 else
87                     CreateWrappedWriter(XmlOutputMethod.Xml);
88             }
89             this.wrapped.WriteStartElement(prefix, localName, ns);
90         }
91
92         public override void WriteStartAttribute(string prefix, string localName, string ns) {
93             EnsureWrappedWriter(XmlOutputMethod.Xml);
94             this.wrapped.WriteStartAttribute(prefix, localName, ns);
95         }
96
97         public override void WriteEndAttribute() {
98             Debug.Assert(this.wrapped != null);
99             this.wrapped.WriteEndAttribute();
100         }
101
102         public override void WriteCData(string text) {
103             if (TextBlockCreatesWriter(text))
104                 this.wrapped.WriteCData(text);
105             else
106                 this.eventCache.WriteCData(text);
107         }
108
109         public override void WriteComment(string text) {
110             if (this.wrapped == null)
111                 this.eventCache.WriteComment(text);
112             else
113                 this.wrapped.WriteComment(text);
114         }
115
116         public override void WriteProcessingInstruction(string name, string text) {
117             if (this.wrapped == null)
118                 this.eventCache.WriteProcessingInstruction(name, text);
119             else
120                 this.wrapped.WriteProcessingInstruction(name, text);
121         }
122
123         public override void WriteWhitespace(string ws) {
124             if (this.wrapped == null)
125                 this.eventCache.WriteWhitespace(ws);
126             else
127                 this.wrapped.WriteWhitespace(ws);
128         }
129
130         public override void WriteString(string text) {
131             if (TextBlockCreatesWriter(text))
132                 this.wrapped.WriteString(text);
133             else
134                 this.eventCache.WriteString(text);
135         }
136
137         public override void WriteChars(char[] buffer, int index, int count) {
138             WriteString(new string(buffer, index, count));
139         }
140
141         public override void WriteRaw(char[] buffer, int index, int count) {
142             WriteRaw(new string(buffer, index, count));
143         }
144
145         public override void WriteRaw(string data) {
146             if (TextBlockCreatesWriter(data))
147                 this.wrapped.WriteRaw(data);
148             else
149                 this.eventCache.WriteRaw(data);
150         }
151
152         public override void WriteEntityRef(string name) {
153             EnsureWrappedWriter(XmlOutputMethod.Xml);
154             this.wrapped.WriteEntityRef(name);
155         }
156
157         public override void WriteCharEntity(char ch) {
158             EnsureWrappedWriter(XmlOutputMethod.Xml);
159             this.wrapped.WriteCharEntity(ch);
160         }
161
162         public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
163             EnsureWrappedWriter(XmlOutputMethod.Xml);
164             this.wrapped.WriteSurrogateCharEntity(lowChar, highChar);
165         }
166
167         public override void WriteBase64(byte[] buffer, int index, int count) {
168             EnsureWrappedWriter(XmlOutputMethod.Xml);
169             this.wrapped.WriteBase64(buffer, index, count);
170         }
171
172         public override void WriteBinHex(byte[] buffer, int index, int count) {
173             EnsureWrappedWriter(XmlOutputMethod.Xml);
174             this.wrapped.WriteBinHex(buffer, index, count);
175         }
176
177         public override void Close() {
178             // Flush any cached events to an Xml writer
179             EnsureWrappedWriter(XmlOutputMethod.Xml);
180             this.wrapped.Close();
181         }
182
183         public override void Flush() {
184             // Flush any cached events to an Xml writer
185             EnsureWrappedWriter(XmlOutputMethod.Xml);
186             this.wrapped.Flush();
187         }
188
189         public override void WriteValue(object value) {
190             EnsureWrappedWriter(XmlOutputMethod.Xml);
191             this.wrapped.WriteValue(value);
192         }
193
194         public override void WriteValue(string value) {
195             EnsureWrappedWriter(XmlOutputMethod.Xml);
196             this.wrapped.WriteValue(value);
197         }
198
199         public override void WriteValue(bool value) {
200             EnsureWrappedWriter(XmlOutputMethod.Xml);
201             this.wrapped.WriteValue(value);
202         }
203
204         public override void WriteValue(DateTime value) {
205             EnsureWrappedWriter(XmlOutputMethod.Xml);
206             this.wrapped.WriteValue(value);
207         }
208
209         public override void WriteValue(DateTimeOffset value) {
210             EnsureWrappedWriter(XmlOutputMethod.Xml);
211             this.wrapped.WriteValue(value);
212         }
213
214         public override void WriteValue(double value) {
215             EnsureWrappedWriter(XmlOutputMethod.Xml);
216             this.wrapped.WriteValue(value);
217         }
218
219         public override void WriteValue(float value) {
220             EnsureWrappedWriter(XmlOutputMethod.Xml);
221             this.wrapped.WriteValue(value);
222         }
223
224         public override void WriteValue(decimal value) {
225             EnsureWrappedWriter(XmlOutputMethod.Xml);
226             this.wrapped.WriteValue(value);
227         }
228
229         public override void WriteValue(int value) {
230             EnsureWrappedWriter(XmlOutputMethod.Xml);
231             this.wrapped.WriteValue(value);
232         }
233
234         public override void WriteValue(long value) {
235             EnsureWrappedWriter(XmlOutputMethod.Xml);
236             this.wrapped.WriteValue(value);
237         }
238
239         //-----------------------------------------------
240         // XmlRawWriter interface
241         //-----------------------------------------------
242
243         internal override IXmlNamespaceResolver NamespaceResolver {
244             get {
245                 return this.resolver;
246             }
247             set {
248                 this.resolver = value;
249
250                 if (this.wrapped == null)
251                     this.eventCache.NamespaceResolver = value;
252                 else
253                     this.wrapped.NamespaceResolver = value;
254             }
255         }
256
257         internal override void WriteXmlDeclaration(XmlStandalone standalone) {
258             // Forces xml writer to be created
259             EnsureWrappedWriter(XmlOutputMethod.Xml);
260             this.wrapped.WriteXmlDeclaration(standalone);
261         }
262
263         internal override void WriteXmlDeclaration(string xmldecl) {
264             // Forces xml writer to be created
265             EnsureWrappedWriter(XmlOutputMethod.Xml);
266             this.wrapped.WriteXmlDeclaration(xmldecl);
267         }
268
269         internal override void StartElementContent() {
270             Debug.Assert(this.wrapped != null);
271             this.wrapped.StartElementContent();
272         }
273
274         internal override void WriteEndElement(string prefix, string localName, string ns) {
275             Debug.Assert(this.wrapped != null);
276             this.wrapped.WriteEndElement(prefix, localName, ns);
277         }
278
279         internal override void WriteFullEndElement(string prefix, string localName, string ns) {
280             Debug.Assert(this.wrapped != null);
281             this.wrapped.WriteFullEndElement(prefix, localName, ns);
282         }
283
284         internal override void WriteNamespaceDeclaration(string prefix, string ns) {
285             EnsureWrappedWriter(XmlOutputMethod.Xml);
286             this.wrapped.WriteNamespaceDeclaration(prefix, ns);
287         }
288
289         internal override bool SupportsNamespaceDeclarationInChunks {
290             get {
291                 return this.wrapped.SupportsNamespaceDeclarationInChunks;
292             }
293         }
294
295         internal override void WriteStartNamespaceDeclaration( string prefix ) {
296             EnsureWrappedWriter(XmlOutputMethod.Xml);
297             this.wrapped.WriteStartNamespaceDeclaration(prefix);
298         }
299
300         internal override void WriteEndNamespaceDeclaration() {
301             this.wrapped.WriteEndNamespaceDeclaration();
302         }
303
304         //-----------------------------------------------
305         // Helper methods
306         //-----------------------------------------------
307
308         /// <summary>
309         /// Return true if "tagName" == "html" (case-insensitive).
310         /// </summary>
311         private static bool IsHtmlTag(string tagName) {
312             if (tagName.Length != 4)
313                 return false;
314
315             if (tagName[0] != 'H' && tagName[0] != 'h')
316                 return false;
317
318             if (tagName[1] != 'T' && tagName[1] != 't')
319                 return false;
320
321             if (tagName[2] != 'M' && tagName[2] != 'm')
322                 return false;
323
324             if (tagName[3] != 'L' && tagName[3] != 'l')
325                 return false;
326
327             return true;
328         }
329
330         /// <summary>
331         /// If a wrapped writer has not yet been created, create one.
332         /// </summary>
333         private void EnsureWrappedWriter(XmlOutputMethod outMethod) {
334             if (this.wrapped == null)
335                 CreateWrappedWriter(outMethod);
336         }
337
338         /// <summary>
339         /// If the specified text consist only of whitespace, then cache the whitespace, as it is not enough to
340         /// force the creation of a wrapped writer.  Otherwise, create a wrapped writer if one has not yet been
341         /// created and return true.
342         /// </summary>
343         private bool TextBlockCreatesWriter(string textBlock) {
344             if (this.wrapped == null) {
345                 // Whitespace-only text blocks aren't enough to determine Xml vs. Html
346                 if (XmlCharType.Instance.IsOnlyWhitespace(textBlock)) {
347                     return false;
348                 }
349
350                 // Non-whitespace text block selects Xml method
351                 CreateWrappedWriter(XmlOutputMethod.Xml);
352             }
353
354             return true;
355         }
356
357         /// <summary>
358         /// Create either the Html or Xml writer and send any cached events to it.
359         /// </summary>
360         private void CreateWrappedWriter(XmlOutputMethod outMethod) {
361             Debug.Assert(this.wrapped == null);
362
363             // Create either the Xml or Html writer
364             this.writerSettings.ReadOnly = false;
365             this.writerSettings.OutputMethod = outMethod;
366
367             // If Indent was not set by the user, then default to True for Html
368             if (outMethod == XmlOutputMethod.Html && this.writerSettings.IndentInternal == TriState.Unknown)
369                 this.writerSettings.Indent = true;
370
371             this.writerSettings.ReadOnly = true;
372
373             if (textWriter != null)
374                 this.wrapped = ((XmlWellFormedWriter)XmlWriter.Create(this.textWriter, this.writerSettings)).RawWriter;
375             else
376                 this.wrapped = ((XmlWellFormedWriter)XmlWriter.Create(this.strm, this.writerSettings)).RawWriter;
377
378             // Send cached events to the new writer
379             this.eventCache.EndEvents();
380             this.eventCache.EventsToWriter(this.wrapped);
381
382             // Send OnRemoveWriter event
383             if (this.onRemove != null)
384                 (this.onRemove)(this.wrapped);
385         }
386     }
387 }