Initial commit
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Core / XmlWellFormedWriterHelpers.cs
1
2 //------------------------------------------------------------------------------
3 // <copyright file="XmlWellFormedWriterHelpers.cs" company="Microsoft">
4 //     Copyright (c) Microsoft Corporation.  All rights reserved.
5 // </copyright>
6 // <owner current="true" primary="true">[....]</owner>
7 //------------------------------------------------------------------------------
8
9 using System;
10 using System.Text;
11 using System.Diagnostics;
12 using System.Collections.Generic;
13
14 namespace System.Xml {
15
16     internal partial class XmlWellFormedWriter : XmlWriter {
17
18         //
19         // Private types
20         //
21         class NamespaceResolverProxy : IXmlNamespaceResolver {
22             XmlWellFormedWriter wfWriter;
23
24             internal NamespaceResolverProxy(XmlWellFormedWriter wfWriter) {
25                 this.wfWriter = wfWriter;
26             }
27
28             IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope(XmlNamespaceScope scope) {
29                 throw new NotImplementedException();
30             }
31             string IXmlNamespaceResolver.LookupNamespace(string prefix) {
32                 return wfWriter.LookupNamespace(prefix);
33             }
34
35             string IXmlNamespaceResolver.LookupPrefix(string namespaceName) {
36                 return wfWriter.LookupPrefix(namespaceName);
37             }
38         }
39
40         partial struct ElementScope {
41
42             internal int prevNSTop;
43             internal string prefix;
44             internal string localName;
45             internal string namespaceUri;
46             internal XmlSpace xmlSpace;
47             internal string xmlLang;
48
49             internal void Set(string prefix, string localName, string namespaceUri, int prevNSTop) {
50                 this.prevNSTop = prevNSTop;
51                 this.prefix = prefix;
52                 this.namespaceUri = namespaceUri;
53                 this.localName = localName;
54                 this.xmlSpace = (System.Xml.XmlSpace)(int)-1;
55                 this.xmlLang = null;
56             }
57
58             internal void WriteEndElement(XmlRawWriter rawWriter) {
59                 rawWriter.WriteEndElement(prefix, localName, namespaceUri);
60             }
61
62             internal void WriteFullEndElement(XmlRawWriter rawWriter) {
63                 rawWriter.WriteFullEndElement(prefix, localName, namespaceUri);
64             }
65         }
66
67         enum NamespaceKind {
68             Written,
69             NeedToWrite,
70             Implied,
71             Special,
72         }
73
74         partial struct Namespace {
75
76             internal string prefix;
77             internal string namespaceUri;
78             internal NamespaceKind kind;
79             internal int prevNsIndex;
80
81             internal void Set(string prefix, string namespaceUri, NamespaceKind kind) {
82                 this.prefix = prefix;
83                 this.namespaceUri = namespaceUri;
84                 this.kind = kind;
85                 this.prevNsIndex = -1;
86             }
87
88             internal void WriteDecl(XmlWriter writer, XmlRawWriter rawWriter) {
89                 Debug.Assert(kind == NamespaceKind.NeedToWrite);
90                 if (null != rawWriter) {
91                     rawWriter.WriteNamespaceDeclaration(prefix, namespaceUri);
92                 }
93                 else {
94                     if (prefix.Length == 0) {
95                         writer.WriteStartAttribute(string.Empty, "xmlns", XmlReservedNs.NsXmlNs);
96                     }
97                     else {
98                         writer.WriteStartAttribute("xmlns", prefix, XmlReservedNs.NsXmlNs);
99                     }
100                     writer.WriteString(namespaceUri);
101                     writer.WriteEndAttribute();
102                 }
103             }
104         }
105
106         struct AttrName {
107             internal string prefix;
108             internal string namespaceUri;
109             internal string localName;
110             internal int prev;
111
112             internal void Set(string prefix, string localName, string namespaceUri) {
113                 this.prefix = prefix;
114                 this.namespaceUri = namespaceUri;
115                 this.localName = localName;
116                 this.prev = 0;
117             }
118
119             internal bool IsDuplicate(string prefix, string localName, string namespaceUri) {
120                 return ((this.localName == localName)
121                     && ((this.prefix == prefix) || (this.namespaceUri == namespaceUri)));
122             }
123         }
124
125         enum SpecialAttribute {
126             No = 0,
127             DefaultXmlns,
128             PrefixedXmlns,
129             XmlSpace,
130             XmlLang
131         }
132
133         partial class AttributeValueCache {
134
135             enum ItemType {
136                 EntityRef,
137                 CharEntity,
138                 SurrogateCharEntity,
139                 Whitespace,
140                 String,
141                 StringChars,
142                 Raw,
143                 RawChars,
144                 ValueString,
145             }
146
147             class Item {
148                 internal ItemType type;
149                 internal object data;
150
151                 internal Item() { }
152
153                 internal void Set(ItemType type, object data) {
154                     this.type = type;
155                     this.data = data;
156                 }
157             }
158
159             class BufferChunk {
160                 internal char[] buffer;
161                 internal int index;
162                 internal int count;
163
164                 internal BufferChunk(char[] buffer, int index, int count) {
165                     this.buffer = buffer;
166                     this.index = index;
167                     this.count = count;
168                 }
169             }
170
171             StringBuilder stringValue = new StringBuilder();
172             string singleStringValue; // special-case for a single WriteString call
173             Item[] items;
174             int firstItem;
175             int lastItem = -1;
176
177             internal string StringValue {
178                 get {
179                     if (singleStringValue != null) {
180                         return singleStringValue;
181                     }
182                     else {
183                         return stringValue.ToString();
184                     }
185                 }
186             }
187
188             internal void WriteEntityRef(string name) {
189                 if (singleStringValue != null) {
190                     StartComplexValue();
191                 }
192
193                 switch (name) {
194                     case "lt":
195                         stringValue.Append('<');
196                         break;
197                     case "gt":
198                         stringValue.Append('>');
199                         break;
200                     case "quot":
201                         stringValue.Append('"');
202                         break;
203                     case "apos":
204                         stringValue.Append('\'');
205                         break;
206                     case "amp":
207                         stringValue.Append('&');
208                         break;
209                     default:
210                         stringValue.Append('&');
211                         stringValue.Append(name);
212                         stringValue.Append(';');
213                         break;
214                 }
215
216                 AddItem(ItemType.EntityRef, name);
217             }
218
219             internal void WriteCharEntity(char ch) {
220                 if (singleStringValue != null) {
221                     StartComplexValue();
222                 }
223                 stringValue.Append(ch);
224                 AddItem(ItemType.CharEntity, ch);
225             }
226
227             internal void WriteSurrogateCharEntity(char lowChar, char highChar) {
228                 if (singleStringValue != null) {
229                     StartComplexValue();
230                 }
231                 stringValue.Append(highChar);
232                 stringValue.Append(lowChar);
233                 AddItem(ItemType.SurrogateCharEntity, new char[] { lowChar, highChar });
234             }
235
236             internal void WriteWhitespace(string ws) {
237                 if (singleStringValue != null) {
238                     StartComplexValue();
239                 }
240                 stringValue.Append(ws);
241                 AddItem(ItemType.Whitespace, ws);
242             }
243
244             internal void WriteString(string text) {
245                 if (singleStringValue != null) {
246                     StartComplexValue();
247                 }
248                 else {
249                     // special-case for a single WriteString
250                     if (lastItem == -1) {
251                         singleStringValue = text;
252                         return;
253                     }
254                 }
255                 
256                 stringValue.Append(text);
257                 AddItem(ItemType.String, text);
258             }
259
260             internal void WriteChars(char[] buffer, int index, int count) {
261                 if (singleStringValue != null) {
262                     StartComplexValue();
263                 }
264                 stringValue.Append(buffer, index, count);
265                 AddItem(ItemType.StringChars, new BufferChunk(buffer, index, count));
266             }
267
268             internal void WriteRaw(char[] buffer, int index, int count) {
269                 if (singleStringValue != null) {
270                     StartComplexValue();
271                 }
272                 stringValue.Append(buffer, index, count);
273                 AddItem(ItemType.RawChars, new BufferChunk(buffer, index, count));
274             }
275
276             internal void WriteRaw(string data) {
277                 if (singleStringValue != null) {
278                     StartComplexValue();
279                 }
280                 stringValue.Append(data);
281                 AddItem(ItemType.Raw, data);
282             }
283
284             internal void WriteValue(string value) {
285                 if (singleStringValue != null) {
286                     StartComplexValue();
287                 }
288                 stringValue.Append(value);
289                 AddItem(ItemType.ValueString, value);
290             }
291
292             internal void Replay(XmlWriter writer) {
293                 if (singleStringValue != null) {
294                     writer.WriteString(singleStringValue);
295                     return;
296                 }
297
298                 BufferChunk bufChunk;
299                 for (int i = firstItem; i <= lastItem; i++) {
300                     Item item = items[i];
301                     switch (item.type) {
302                         case ItemType.EntityRef:
303                             writer.WriteEntityRef((string)item.data);
304                             break;
305                         case ItemType.CharEntity:
306                             writer.WriteCharEntity((char)item.data);
307                             break;
308                         case ItemType.SurrogateCharEntity:
309                             char[] chars = (char[])item.data;
310                             writer.WriteSurrogateCharEntity(chars[0], chars[1]);
311                             break;
312                         case ItemType.Whitespace:
313                             writer.WriteWhitespace((string)item.data);
314                             break;
315                         case ItemType.String:
316                             writer.WriteString((string)item.data);
317                             break;
318                         case ItemType.StringChars:
319                             bufChunk = (BufferChunk)item.data;
320                             writer.WriteChars(bufChunk.buffer, bufChunk.index, bufChunk.count);
321                             break;
322                         case ItemType.Raw:
323                             writer.WriteRaw((string)item.data);
324                             break;
325                         case ItemType.RawChars:
326                             bufChunk = (BufferChunk)item.data;
327                             writer.WriteChars(bufChunk.buffer, bufChunk.index, bufChunk.count);
328                             break;
329                         case ItemType.ValueString:
330                             writer.WriteValue((string)item.data);
331                             break;
332                         default:
333                             Debug.Assert(false, "Unexpected ItemType value.");
334                             break;
335                     }
336                 }
337             }
338
339             // This method trims whitespaces from the beginnig and the end of the string and cached writer events
340             internal void Trim() {
341                 // if only one string value -> trim the write spaces directly
342                 if (singleStringValue != null) {
343                     singleStringValue = XmlConvert.TrimString(singleStringValue);
344                     return;
345                 }
346
347                 // trim the string in StringBuilder
348                 string valBefore = stringValue.ToString();
349                 string valAfter = XmlConvert.TrimString(valBefore);
350                 if (valBefore != valAfter) {
351                     stringValue = new StringBuilder(valAfter);
352                 }
353
354                 // trim the beginning of the recorded writer events
355                 XmlCharType xmlCharType = XmlCharType.Instance;
356
357                 int i = firstItem;
358                 while (i == firstItem && i <= lastItem) {
359                     Item item = items[i];
360                     switch (item.type) {
361                         case ItemType.Whitespace:
362                             firstItem++;
363                             break;
364                         case ItemType.String:
365                         case ItemType.Raw:
366                         case ItemType.ValueString:
367                             item.data = XmlConvert.TrimStringStart((string)item.data);
368                             if (((string)item.data).Length == 0) {
369                                 // no characters left -> move the firstItem index to exclude it from the Replay
370                                 firstItem++;
371                             }
372                             break;
373                         case ItemType.StringChars:
374                         case ItemType.RawChars:
375                             BufferChunk bufChunk = (BufferChunk)item.data;
376                             int endIndex = bufChunk.index + bufChunk.count;
377                             while (bufChunk.index < endIndex && xmlCharType.IsWhiteSpace(bufChunk.buffer[bufChunk.index])) {
378                                 bufChunk.index++;
379                                 bufChunk.count--;
380                             }
381                             if (bufChunk.index == endIndex) {
382                                 // no characters left -> move the firstItem index to exclude it from the Replay
383                                 firstItem++;
384                             }
385                             break;
386                     }
387                     i++;
388                 }
389
390                 // trim the end of the recorded writer events
391                 i = lastItem;
392                 while (i == lastItem && i >= firstItem) {
393                     Item item = items[i];
394                     switch (item.type) {
395                         case ItemType.Whitespace:
396                             lastItem--;
397                             break;
398                         case ItemType.String:
399                         case ItemType.Raw:
400                         case ItemType.ValueString:
401                             item.data = XmlConvert.TrimStringEnd((string)item.data);
402                             if (((string)item.data).Length == 0) {
403                                 // no characters left -> move the lastItem index to exclude it from the Replay
404                                 lastItem--;
405                             }
406                             break;
407                         case ItemType.StringChars:
408                         case ItemType.RawChars:
409                             BufferChunk bufChunk = (BufferChunk)item.data;  
410                             while (bufChunk.count > 0 && xmlCharType.IsWhiteSpace(bufChunk.buffer[bufChunk.index + bufChunk.count - 1])) {
411                                 bufChunk.count--;
412                             }
413                             if (bufChunk.count == 0) {
414                                 // no characters left -> move the lastItem index to exclude it from the Replay
415                                 lastItem--;
416                             }
417                             break;
418                     }
419                     i--;
420                 }
421             }
422
423             internal void Clear() {
424                 singleStringValue = null;
425                 lastItem = -1;
426                 firstItem = 0;
427                 stringValue.Length = 0;
428             }
429
430             private void StartComplexValue() {
431                 Debug.Assert(singleStringValue != null);
432                 Debug.Assert(lastItem == -1);
433
434                 stringValue.Append( singleStringValue );
435                 AddItem(ItemType.String, singleStringValue);
436                 
437                 singleStringValue = null;
438             }
439
440             void AddItem(ItemType type, object data) {
441                 int newItemIndex = lastItem + 1;
442                 if (items == null) {
443                     items = new Item[4];
444                 }
445                 else if (items.Length == newItemIndex) {
446                     Item[] newItems = new Item[newItemIndex * 2];
447                     Array.Copy(items, newItems, newItemIndex);
448                     items = newItems;
449                 }
450                 if (items[newItemIndex] == null) {
451                     items[newItemIndex] = new Item();
452                 }
453                 items[newItemIndex].Set(type, data);
454                 lastItem = newItemIndex;
455             }
456
457         }
458     }
459 }