1 //------------------------------------------------------------------------------
2 // <copyright file="XmlAutoDetectWriter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
11 using System.Diagnostics;
15 using System.Xml.Schema;
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.
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;
30 //-----------------------------------------------
32 //-----------------------------------------------
34 private XmlAutoDetectWriter(XmlWriterSettings writerSettings) {
35 Debug.Assert(writerSettings.OutputMethod == XmlOutputMethod.AutoDetect);
37 this.writerSettings = (XmlWriterSettings)writerSettings.Clone();
38 this.writerSettings.ReadOnly = true;
40 // Start caching all events
41 this.eventCache = new XmlEventCache(string.Empty, true);
44 public XmlAutoDetectWriter(TextWriter textWriter, XmlWriterSettings writerSettings)
45 : this(writerSettings) {
46 this.textWriter = textWriter;
49 public XmlAutoDetectWriter(Stream strm, XmlWriterSettings writerSettings)
50 : this(writerSettings) {
55 //-----------------------------------------------
56 // IRemovableWriter interface
57 //-----------------------------------------------
60 /// This writer will raise this event once it has determined whether to replace itself with the Html or Xml writer.
62 public OnRemoveWriter OnRemoveWriterEvent {
63 get { return this.onRemove; }
64 set { this.onRemove = value; }
68 //-----------------------------------------------
69 // XmlWriter interface
70 //-----------------------------------------------
72 public override XmlWriterSettings Settings {
73 get { return this.writerSettings; }
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);
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);
87 CreateWrappedWriter(XmlOutputMethod.Xml);
89 this.wrapped.WriteStartElement(prefix, localName, ns);
92 public override void WriteStartAttribute(string prefix, string localName, string ns) {
93 EnsureWrappedWriter(XmlOutputMethod.Xml);
94 this.wrapped.WriteStartAttribute(prefix, localName, ns);
97 public override void WriteEndAttribute() {
98 Debug.Assert(this.wrapped != null);
99 this.wrapped.WriteEndAttribute();
102 public override void WriteCData(string text) {
103 if (TextBlockCreatesWriter(text))
104 this.wrapped.WriteCData(text);
106 this.eventCache.WriteCData(text);
109 public override void WriteComment(string text) {
110 if (this.wrapped == null)
111 this.eventCache.WriteComment(text);
113 this.wrapped.WriteComment(text);
116 public override void WriteProcessingInstruction(string name, string text) {
117 if (this.wrapped == null)
118 this.eventCache.WriteProcessingInstruction(name, text);
120 this.wrapped.WriteProcessingInstruction(name, text);
123 public override void WriteWhitespace(string ws) {
124 if (this.wrapped == null)
125 this.eventCache.WriteWhitespace(ws);
127 this.wrapped.WriteWhitespace(ws);
130 public override void WriteString(string text) {
131 if (TextBlockCreatesWriter(text))
132 this.wrapped.WriteString(text);
134 this.eventCache.WriteString(text);
137 public override void WriteChars(char[] buffer, int index, int count) {
138 WriteString(new string(buffer, index, count));
141 public override void WriteRaw(char[] buffer, int index, int count) {
142 WriteRaw(new string(buffer, index, count));
145 public override void WriteRaw(string data) {
146 if (TextBlockCreatesWriter(data))
147 this.wrapped.WriteRaw(data);
149 this.eventCache.WriteRaw(data);
152 public override void WriteEntityRef(string name) {
153 EnsureWrappedWriter(XmlOutputMethod.Xml);
154 this.wrapped.WriteEntityRef(name);
157 public override void WriteCharEntity(char ch) {
158 EnsureWrappedWriter(XmlOutputMethod.Xml);
159 this.wrapped.WriteCharEntity(ch);
162 public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
163 EnsureWrappedWriter(XmlOutputMethod.Xml);
164 this.wrapped.WriteSurrogateCharEntity(lowChar, highChar);
167 public override void WriteBase64(byte[] buffer, int index, int count) {
168 EnsureWrappedWriter(XmlOutputMethod.Xml);
169 this.wrapped.WriteBase64(buffer, index, count);
172 public override void WriteBinHex(byte[] buffer, int index, int count) {
173 EnsureWrappedWriter(XmlOutputMethod.Xml);
174 this.wrapped.WriteBinHex(buffer, index, count);
177 public override void Close() {
178 // Flush any cached events to an Xml writer
179 EnsureWrappedWriter(XmlOutputMethod.Xml);
180 this.wrapped.Close();
183 public override void Flush() {
184 // Flush any cached events to an Xml writer
185 EnsureWrappedWriter(XmlOutputMethod.Xml);
186 this.wrapped.Flush();
189 public override void WriteValue(object value) {
190 EnsureWrappedWriter(XmlOutputMethod.Xml);
191 this.wrapped.WriteValue(value);
194 public override void WriteValue(string value) {
195 EnsureWrappedWriter(XmlOutputMethod.Xml);
196 this.wrapped.WriteValue(value);
199 public override void WriteValue(bool value) {
200 EnsureWrappedWriter(XmlOutputMethod.Xml);
201 this.wrapped.WriteValue(value);
204 public override void WriteValue(DateTime value) {
205 EnsureWrappedWriter(XmlOutputMethod.Xml);
206 this.wrapped.WriteValue(value);
209 public override void WriteValue(DateTimeOffset value) {
210 EnsureWrappedWriter(XmlOutputMethod.Xml);
211 this.wrapped.WriteValue(value);
214 public override void WriteValue(double value) {
215 EnsureWrappedWriter(XmlOutputMethod.Xml);
216 this.wrapped.WriteValue(value);
219 public override void WriteValue(float value) {
220 EnsureWrappedWriter(XmlOutputMethod.Xml);
221 this.wrapped.WriteValue(value);
224 public override void WriteValue(decimal value) {
225 EnsureWrappedWriter(XmlOutputMethod.Xml);
226 this.wrapped.WriteValue(value);
229 public override void WriteValue(int value) {
230 EnsureWrappedWriter(XmlOutputMethod.Xml);
231 this.wrapped.WriteValue(value);
234 public override void WriteValue(long value) {
235 EnsureWrappedWriter(XmlOutputMethod.Xml);
236 this.wrapped.WriteValue(value);
239 //-----------------------------------------------
240 // XmlRawWriter interface
241 //-----------------------------------------------
243 internal override IXmlNamespaceResolver NamespaceResolver {
245 return this.resolver;
248 this.resolver = value;
250 if (this.wrapped == null)
251 this.eventCache.NamespaceResolver = value;
253 this.wrapped.NamespaceResolver = value;
257 internal override void WriteXmlDeclaration(XmlStandalone standalone) {
258 // Forces xml writer to be created
259 EnsureWrappedWriter(XmlOutputMethod.Xml);
260 this.wrapped.WriteXmlDeclaration(standalone);
263 internal override void WriteXmlDeclaration(string xmldecl) {
264 // Forces xml writer to be created
265 EnsureWrappedWriter(XmlOutputMethod.Xml);
266 this.wrapped.WriteXmlDeclaration(xmldecl);
269 internal override void StartElementContent() {
270 Debug.Assert(this.wrapped != null);
271 this.wrapped.StartElementContent();
274 internal override void WriteEndElement(string prefix, string localName, string ns) {
275 Debug.Assert(this.wrapped != null);
276 this.wrapped.WriteEndElement(prefix, localName, ns);
279 internal override void WriteFullEndElement(string prefix, string localName, string ns) {
280 Debug.Assert(this.wrapped != null);
281 this.wrapped.WriteFullEndElement(prefix, localName, ns);
284 internal override void WriteNamespaceDeclaration(string prefix, string ns) {
285 EnsureWrappedWriter(XmlOutputMethod.Xml);
286 this.wrapped.WriteNamespaceDeclaration(prefix, ns);
289 internal override bool SupportsNamespaceDeclarationInChunks {
291 return this.wrapped.SupportsNamespaceDeclarationInChunks;
295 internal override void WriteStartNamespaceDeclaration( string prefix ) {
296 EnsureWrappedWriter(XmlOutputMethod.Xml);
297 this.wrapped.WriteStartNamespaceDeclaration(prefix);
300 internal override void WriteEndNamespaceDeclaration() {
301 this.wrapped.WriteEndNamespaceDeclaration();
304 //-----------------------------------------------
306 //-----------------------------------------------
309 /// Return true if "tagName" == "html" (case-insensitive).
311 private static bool IsHtmlTag(string tagName) {
312 if (tagName.Length != 4)
315 if (tagName[0] != 'H' && tagName[0] != 'h')
318 if (tagName[1] != 'T' && tagName[1] != 't')
321 if (tagName[2] != 'M' && tagName[2] != 'm')
324 if (tagName[3] != 'L' && tagName[3] != 'l')
331 /// If a wrapped writer has not yet been created, create one.
333 private void EnsureWrappedWriter(XmlOutputMethod outMethod) {
334 if (this.wrapped == null)
335 CreateWrappedWriter(outMethod);
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.
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)) {
350 // Non-whitespace text block selects Xml method
351 CreateWrappedWriter(XmlOutputMethod.Xml);
358 /// Create either the Html or Xml writer and send any cached events to it.
360 private void CreateWrappedWriter(XmlOutputMethod outMethod) {
361 Debug.Assert(this.wrapped == null);
363 // Create either the Xml or Html writer
364 this.writerSettings.ReadOnly = false;
365 this.writerSettings.OutputMethod = outMethod;
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;
371 this.writerSettings.ReadOnly = true;
373 if (textWriter != null)
374 this.wrapped = ((XmlWellFormedWriter)XmlWriter.Create(this.textWriter, this.writerSettings)).RawWriter;
376 this.wrapped = ((XmlWellFormedWriter)XmlWriter.Create(this.strm, this.writerSettings)).RawWriter;
378 // Send cached events to the new writer
379 this.eventCache.EndEvents();
380 this.eventCache.EventsToWriter(this.wrapped);
382 // Send OnRemoveWriter event
383 if (this.onRemove != null)
384 (this.onRemove)(this.wrapped);