1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 // Following comment might not be valid anymore as this code is fairly old and a lot happened since it was written...
6 // WARNING: This file is generated and should not be modified directly.  Instead,
7 // modify XmlTextWriterGenerator.cxx and run gen.bat in the same directory.
8 // This batch file will execute the following commands:
9 //
10 //   cl.exe /C /EP /D _XML_UTF8_TEXT_WRITER HtmlTextWriterGenerator.cxx > HtmlUtf8TextWriter.cs
11 //   cl.exe /C /EP /D _XML_ENCODED_TEXT_WRITER HtmlTextWriterGenerator.cxx > HtmlEncodedTextWriter.cs
12 //
13 // Because these two implementations of XmlTextWriter are so similar, the C++ preprocessor
14 // is used to generate each implementation from one template file, using macros and ifdefs.
15 
16 using System;
17 using System.IO;
18 using System.Text;
19 using System.Xml;
20 using System.Xml.Schema;
21 using System.Diagnostics;
22 using MS.Internal.Xml;
23 
24 namespace System.Xml
25 {
26     internal class HtmlUtf8RawTextWriter : XmlUtf8RawTextWriter
27     {
28         protected ByteStack elementScope;
29         protected ElementProperties currentElementProperties;
30         private AttributeProperties _currentAttributeProperties;
31 
32         private bool _endsWithAmpersand;
33         private byte[] _uriEscapingBuffer;
34 
35         private string _mediaType;
36         private bool _doNotEscapeUriAttributes;
37 
38         protected static TernaryTreeReadOnly elementPropertySearch;
39         protected static TernaryTreeReadOnly attributePropertySearch;
40 
41         private const int StackIncrement = 10;
42 
HtmlUtf8RawTextWriter(Stream stream, XmlWriterSettings settings)43         public HtmlUtf8RawTextWriter(Stream stream, XmlWriterSettings settings) : base(stream, settings)
44         {
45             Init(settings);
46         }
47 
WriteXmlDeclaration(XmlStandalone standalone)48         internal override void WriteXmlDeclaration(XmlStandalone standalone)
49         {
50             // Ignore xml declaration
51         }
52 
WriteXmlDeclaration(string xmldecl)53         internal override void WriteXmlDeclaration(string xmldecl)
54         {
55             // Ignore xml declaration
56         }
57 
58         /// Html rules allow public ID without system ID and always output "html"
WriteDocType(string name, string pubid, string sysid, string subset)59         public override void WriteDocType(string name, string pubid, string sysid, string subset)
60         {
61             Debug.Assert(name != null && name.Length > 0);
62 
63             RawText("<!DOCTYPE ");
64 
65             // Bug 114337: Always output "html" or "HTML" in doc-type, even if "name" is something else
66             if (name == "HTML")
67                 RawText("HTML");
68             else
69                 RawText("html");
70 
71             if (pubid != null)
72             {
73                 RawText(" PUBLIC \"");
74                 RawText(pubid);
75                 if (sysid != null)
76                 {
77                     RawText("\" \"");
78                     RawText(sysid);
79                 }
80                 bufBytes[bufPos++] = (byte)'"';
81             }
82             else if (sysid != null)
83             {
84                 RawText(" SYSTEM \"");
85                 RawText(sysid);
86                 bufBytes[bufPos++] = (byte)'"';
87             }
88             else
89             {
90                 bufBytes[bufPos++] = (byte)' ';
91             }
92 
93             if (subset != null)
94             {
95                 bufBytes[bufPos++] = (byte)'[';
96                 RawText(subset);
97                 bufBytes[bufPos++] = (byte)']';
98             }
99 
100             bufBytes[this.bufPos++] = (byte)'>';
101         }
102 
103         // For the HTML element, it should call this method with ns and prefix as String.Empty
WriteStartElement(string prefix, string localName, string ns)104         public override void WriteStartElement(string prefix, string localName, string ns)
105         {
106             Debug.Assert(localName != null && localName.Length != 0 && prefix != null && ns != null);
107 
108             elementScope.Push((byte)currentElementProperties);
109 
110             if (ns.Length == 0)
111             {
112                 Debug.Assert(prefix.Length == 0);
113 
114 
115                 currentElementProperties = (ElementProperties)elementPropertySearch.FindCaseInsensitiveString(localName);
116                 base.bufBytes[bufPos++] = (byte)'<';
117                 base.RawText(localName);
118                 base.attrEndPos = bufPos;
119             }
120             else
121             {
122                 // Since the HAS_NS has no impact to the ElementTextBlock behavior,
123                 // we don't need to push it into the stack.
124                 currentElementProperties = ElementProperties.HAS_NS;
125                 base.WriteStartElement(prefix, localName, ns);
126             }
127         }
128 
129         // Output >. For HTML needs to output META info
StartElementContent()130         internal override void StartElementContent()
131         {
132             base.bufBytes[base.bufPos++] = (byte)'>';
133 
134             // Detect whether content is output
135             this.contentPos = this.bufPos;
136 
137             if ((currentElementProperties & ElementProperties.HEAD) != 0)
138             {
139                 WriteMetaElement();
140             }
141         }
142 
143         // end element with />
144         // for HTML(ns.Length == 0)
145         //    not an empty tag <h1></h1>
146         //    empty tag <basefont>
WriteEndElement(string prefix, string localName, string ns)147         internal override void WriteEndElement(string prefix, string localName, string ns)
148         {
149             if (ns.Length == 0)
150             {
151                 Debug.Assert(prefix.Length == 0);
152 
153 
154 
155                 if ((currentElementProperties & ElementProperties.EMPTY) == 0)
156                 {
157                     bufBytes[base.bufPos++] = (byte)'<';
158                     bufBytes[base.bufPos++] = (byte)'/';
159                     base.RawText(localName);
160                     bufBytes[base.bufPos++] = (byte)'>';
161                 }
162             }
163             else
164             {
165                 //xml content
166                 base.WriteEndElement(prefix, localName, ns);
167             }
168 
169             currentElementProperties = (ElementProperties)elementScope.Pop();
170         }
171 
WriteFullEndElement(string prefix, string localName, string ns)172         internal override void WriteFullEndElement(string prefix, string localName, string ns)
173         {
174             if (ns.Length == 0)
175             {
176                 Debug.Assert(prefix.Length == 0);
177 
178 
179 
180                 if ((currentElementProperties & ElementProperties.EMPTY) == 0)
181                 {
182                     bufBytes[base.bufPos++] = (byte)'<';
183                     bufBytes[base.bufPos++] = (byte)'/';
184                     base.RawText(localName);
185                     bufBytes[base.bufPos++] = (byte)'>';
186                 }
187             }
188             else
189             {
190                 //xml content
191                 base.WriteFullEndElement(prefix, localName, ns);
192             }
193 
194             currentElementProperties = (ElementProperties)elementScope.Pop();
195         }
196 
197         // 1. How the outputBooleanAttribute(fBOOL) and outputHtmlUriText(fURI) being set?
198         // When SA is called.
199         //
200         //             BOOL_PARENT   URI_PARENT   Others
201         //  fURI
202         //  URI att       false         true       false
203         //
204         //  fBOOL
205         //  BOOL att      true          false      false
206         //
207         //  How they change the attribute output behaviors?
208         //
209         //  1)       fURI=true             fURI=false
210         //  SA         a="                      a="
211         //  AT       HtmlURIText             HtmlText
212         //  EA          "                       "
213         //
214         //  2)      fBOOL=true             fBOOL=false
215         //  SA         a                       a="
216         //  AT      HtmlText                output nothing
217         //  EA     output nothing               "
218         //
219         // When they get reset?
220         //  At the end of attribute.
221 
222         // 2. How the outputXmlTextElementScoped(fENs) and outputXmlTextattributeScoped(fANs) are set?
223         //  fANs is in the scope of the fENs.
224         //
225         //          SE(localName)    SE(ns, pre, localName)  SA(localName)  SA(ns, pre, localName)
226         //  fENs      false(default)      true(action)
227         //  fANs      false(default)     false(default)      false(default)      true(action)
228 
229         // how they get reset?
230         //
231         //          EE(localName)  EE(ns, pre, localName) EENC(ns, pre, localName) EA(localName)  EA(ns, pre, localName)
232         //  fENs                      false(action)
233         //  fANs                                                                                        false(action)
234 
235         // How they change the TextOutput?
236         //
237         //         fENs | fANs              Else
238         //  AT      XmlText                  HtmlText
239         //
240         //
241         // 3. Flags for processing &{ split situations
242         //
243         // When the flag is set?
244         //
245         //  AT     src[lastchar]='&' flag&{ = true;
246         //
247         // when it get result?
248         //
249         //  AT method.
250         //
251         // How it changes the behaviors?
252         //
253         //         flag&{=true
254         //
255         //  AT     if (src[0] == '{') {
256         //             output "&{"
257         //         }
258         //         else {
259         //             output &amp;
260         //         }
261         //
262         //  EA     output amp;
263         //
264 
265         //  SA  if (flagBOOL == false) { output =";}
266         //
267         //  AT  if (flagBOOL) { return};
268         //      if (flagNS) {XmlText;} {
269         //      }
270         //      else if (flagURI) {
271         //          HtmlURIText;
272         //      }
273         //      else {
274         //          HtmlText;
275         //      }
276         //
277 
278         //  AT  if (flagNS) {XmlText;} {
279         //      }
280         //      else if (flagURI) {
281         //          HtmlURIText;
282         //      }
283         //      else if (!flagBOOL) {
284         //          HtmlText; //flag&{ handling
285         //      }
286         //
287         //
288         //  EA if (flagBOOL == false) { output "
289         //     }
290         //     else if (flag&{) {
291         //          output amp;
292         //     }
293         //
WriteStartAttribute(string prefix, string localName, string ns)294         public override void WriteStartAttribute(string prefix, string localName, string ns)
295         {
296             Debug.Assert(localName != null && localName.Length != 0 && prefix != null && ns != null);
297 
298             if (ns.Length == 0)
299             {
300                 Debug.Assert(prefix.Length == 0);
301 
302 
303                 if (base.attrEndPos == bufPos)
304                 {
305                     base.bufBytes[bufPos++] = (byte)' ';
306                 }
307                 base.RawText(localName);
308 
309                 if ((currentElementProperties & (ElementProperties.BOOL_PARENT | ElementProperties.URI_PARENT | ElementProperties.NAME_PARENT)) != 0)
310                 {
311                     _currentAttributeProperties = (AttributeProperties)attributePropertySearch.FindCaseInsensitiveString(localName) &
312                                                  (AttributeProperties)currentElementProperties;
313 
314                     if ((_currentAttributeProperties & AttributeProperties.BOOLEAN) != 0)
315                     {
316                         base.inAttributeValue = true;
317                         return;
318                     }
319                 }
320                 else
321                 {
322                     _currentAttributeProperties = AttributeProperties.DEFAULT;
323                 }
324 
325                 base.bufBytes[bufPos++] = (byte)'=';
326                 base.bufBytes[bufPos++] = (byte)'"';
327             }
328             else
329             {
330                 base.WriteStartAttribute(prefix, localName, ns);
331                 _currentAttributeProperties = AttributeProperties.DEFAULT;
332             }
333 
334             base.inAttributeValue = true;
335         }
336 
337         // Output the amp; at end of EndAttribute
WriteEndAttribute()338         public override void WriteEndAttribute()
339         {
340             if ((_currentAttributeProperties & AttributeProperties.BOOLEAN) != 0)
341             {
342                 base.attrEndPos = bufPos;
343             }
344             else
345             {
346                 if (_endsWithAmpersand)
347                 {
348                     OutputRestAmps();
349                     _endsWithAmpersand = false;
350                 }
351 
352 
353 
354                 base.bufBytes[bufPos++] = (byte)'"';
355             }
356             base.inAttributeValue = false;
357             base.attrEndPos = bufPos;
358         }
359 
360         // HTML PI's use ">" to terminate rather than "?>".
WriteProcessingInstruction(string target, string text)361         public override void WriteProcessingInstruction(string target, string text)
362         {
363             Debug.Assert(target != null && target.Length != 0 && text != null);
364 
365 
366 
367             bufBytes[base.bufPos++] = (byte)'<';
368             bufBytes[base.bufPos++] = (byte)'?';
369             base.RawText(target);
370             bufBytes[base.bufPos++] = (byte)' ';
371 
372             base.WriteCommentOrPi(text, '?');
373 
374             base.bufBytes[base.bufPos++] = (byte)'>';
375 
376             if (base.bufPos > base.bufLen)
377             {
378                 FlushBuffer();
379             }
380         }
381 
382         // Serialize either attribute or element text using HTML rules.
WriteString(string text)383         public override unsafe void WriteString(string text)
384         {
385             Debug.Assert(text != null);
386 
387 
388 
389             fixed (char* pSrc = text)
390             {
391                 char* pSrcEnd = pSrc + text.Length;
392                 if (base.inAttributeValue)
393                 {
394                     WriteHtmlAttributeTextBlock(pSrc, pSrcEnd);
395                 }
396                 else
397                 {
398                     WriteHtmlElementTextBlock(pSrc, pSrcEnd);
399                 }
400             }
401         }
402 
WriteEntityRef(string name)403         public override void WriteEntityRef(string name)
404         {
405             throw new InvalidOperationException(SR.Xml_InvalidOperation);
406         }
407 
WriteCharEntity(char ch)408         public override void WriteCharEntity(char ch)
409         {
410             throw new InvalidOperationException(SR.Xml_InvalidOperation);
411         }
412 
WriteSurrogateCharEntity(char lowChar, char highChar)413         public override void WriteSurrogateCharEntity(char lowChar, char highChar)
414         {
415             throw new InvalidOperationException(SR.Xml_InvalidOperation);
416         }
417 
WriteChars(char[] buffer, int index, int count)418         public override unsafe void WriteChars(char[] buffer, int index, int count)
419         {
420             Debug.Assert(buffer != null);
421             Debug.Assert(index >= 0);
422             Debug.Assert(count >= 0 && index + count <= buffer.Length);
423 
424 
425 
426             fixed (char* pSrcBegin = &buffer[index])
427             {
428                 if (inAttributeValue)
429                 {
430                     WriteAttributeTextBlock(pSrcBegin, pSrcBegin + count);
431                 }
432                 else
433                 {
434                     WriteElementTextBlock(pSrcBegin, pSrcBegin + count);
435                 }
436             }
437         }
438 
Init(XmlWriterSettings settings)439         private void Init(XmlWriterSettings settings)
440         {
441             Debug.Assert((int)ElementProperties.URI_PARENT == (int)AttributeProperties.URI);
442             Debug.Assert((int)ElementProperties.BOOL_PARENT == (int)AttributeProperties.BOOLEAN);
443             Debug.Assert((int)ElementProperties.NAME_PARENT == (int)AttributeProperties.NAME);
444 
445             if (elementPropertySearch == null)
446             {
447                 //elementPropertySearch should be init last for the mutli thread safe situation.
448                 attributePropertySearch = new TernaryTreeReadOnly(HtmlTernaryTree.htmlAttributes);
449                 elementPropertySearch = new TernaryTreeReadOnly(HtmlTernaryTree.htmlElements);
450             }
451 
452             elementScope = new ByteStack(StackIncrement);
453             _uriEscapingBuffer = new byte[5];
454             currentElementProperties = ElementProperties.DEFAULT;
455 
456             _mediaType = settings.MediaType;
457             _doNotEscapeUriAttributes = settings.DoNotEscapeUriAttributes;
458         }
459 
WriteMetaElement()460         protected void WriteMetaElement()
461         {
462             base.RawText("<META http-equiv=\"Content-Type\"");
463 
464             if (_mediaType == null)
465             {
466                 _mediaType = "text/html";
467             }
468 
469             base.RawText(" content=\"");
470             base.RawText(_mediaType);
471             base.RawText("; charset=");
472             base.RawText(base.encoding.WebName);
473             base.RawText("\">");
474         }
475 
476         // Justify the stack usage:
477         //
478         // Nested elements has following possible position combinations
479         // 1. <E1>Content1<E2>Content2</E2></E1>
480         // 2. <E1><E2>Content2</E2>Content1</E1>
481         // 3. <E1>Content<E2>Cotent2</E2>Content1</E1>
482         //
483         // In the situation 2 and 3, the stored currentElementProrperties will be E2's,
484         // only the top of the stack is the real E1 element properties.
WriteHtmlElementTextBlock(char* pSrc, char* pSrcEnd)485         protected unsafe void WriteHtmlElementTextBlock(char* pSrc, char* pSrcEnd)
486         {
487             if ((currentElementProperties & ElementProperties.NO_ENTITIES) != 0)
488             {
489                 base.RawText(pSrc, pSrcEnd);
490             }
491             else
492             {
493                 base.WriteElementTextBlock(pSrc, pSrcEnd);
494             }
495         }
496 
WriteHtmlAttributeTextBlock(char* pSrc, char* pSrcEnd)497         protected unsafe void WriteHtmlAttributeTextBlock(char* pSrc, char* pSrcEnd)
498         {
499             if ((_currentAttributeProperties & (AttributeProperties.BOOLEAN | AttributeProperties.URI | AttributeProperties.NAME)) != 0)
500             {
501                 if ((_currentAttributeProperties & AttributeProperties.BOOLEAN) != 0)
502                 {
503                     //if output boolean attribute, ignore this call.
504                     return;
505                 }
506 
507                 if ((_currentAttributeProperties & (AttributeProperties.URI | AttributeProperties.NAME)) != 0 && !_doNotEscapeUriAttributes)
508                 {
509                     WriteUriAttributeText(pSrc, pSrcEnd);
510                 }
511                 else
512                 {
513                     WriteHtmlAttributeText(pSrc, pSrcEnd);
514                 }
515             }
516             else if ((currentElementProperties & ElementProperties.HAS_NS) != 0)
517             {
518                 base.WriteAttributeTextBlock(pSrc, pSrcEnd);
519             }
520             else
521             {
522                 WriteHtmlAttributeText(pSrc, pSrcEnd);
523             }
524         }
525 
526         //
527         // &{ split cases
528         // 1). HtmlAttributeText("a&");
529         //     HtmlAttributeText("{b}");
530         //
531         // 2). HtmlAttributeText("a&");
532         //     EndAttribute();
533 
534         // 3).split with Flush by the user
535         //     HtmlAttributeText("a&");
536         //     FlushBuffer();
537         //     HtmlAttributeText("{b}");
538 
539         //
540         // Solutions:
541         // case 1)hold the &amp; output as &
542         //      if the next income character is {, output {
543         //      else output amp;
544         //
545 
WriteHtmlAttributeText(char* pSrc, char* pSrcEnd)546         private unsafe void WriteHtmlAttributeText(char* pSrc, char* pSrcEnd)
547         {
548             if (_endsWithAmpersand)
549             {
550                 if (pSrcEnd - pSrc > 0 && pSrc[0] != '{')
551                 {
552                     OutputRestAmps();
553                 }
554                 _endsWithAmpersand = false;
555             }
556 
557             fixed (byte* pDstBegin = bufBytes)
558             {
559                 byte* pDst = pDstBegin + this.bufPos;
560 
561                 char ch = (char)0;
562                 for (;;)
563                 {
564                     byte* pDstEnd = pDst + (pSrcEnd - pSrc);
565                     if (pDstEnd > pDstBegin + bufLen)
566                     {
567                         pDstEnd = pDstBegin + bufLen;
568                     }
569 
570                     while (pDst < pDstEnd && (xmlCharType.IsAttributeValueChar((char)(ch = *pSrc)) && ch <= 0x7F))
571                     {
572                         *pDst++ = (byte)ch;
573                         pSrc++;
574                     }
575                     Debug.Assert(pSrc <= pSrcEnd);
576 
577                     // end of value
578                     if (pSrc >= pSrcEnd)
579                     {
580                         break;
581                     }
582 
583                     // end of buffer
584                     if (pDst >= pDstEnd)
585                     {
586                         bufPos = (int)(pDst - pDstBegin);
587                         FlushBuffer();
588                         pDst = pDstBegin + 1;
589                         continue;
590                     }
591 
592                     // some character needs to be escaped
593                     switch (ch)
594                     {
595                         case '&':
596                             if (pSrc + 1 == pSrcEnd)
597                             {
598                                 _endsWithAmpersand = true;
599                             }
600                             else if (pSrc[1] != '{')
601                             {
602                                 pDst = XmlUtf8RawTextWriter.AmpEntity(pDst);
603                                 break;
604                             }
605                             *pDst++ = (byte)ch;
606                             break;
607                         case '"':
608                             pDst = QuoteEntity(pDst);
609                             break;
610                         case '<':
611                         case '>':
612                         case '\'':
613                         case (char)0x9:
614                             *pDst++ = (byte)ch;
615                             break;
616                         case (char)0xD:
617                             // do not normalize new lines in attributes - just escape them
618                             pDst = CarriageReturnEntity(pDst);
619                             break;
620                         case (char)0xA:
621                             // do not normalize new lines in attributes - just escape them
622                             pDst = LineFeedEntity(pDst);
623                             break;
624                         default:
625                             EncodeChar(ref pSrc, pSrcEnd, ref pDst);
626                             continue;
627                     }
628                     pSrc++;
629                 }
630                 bufPos = (int)(pDst - pDstBegin);
631             }
632         }
633 
WriteUriAttributeText(char* pSrc, char* pSrcEnd)634         private unsafe void WriteUriAttributeText(char* pSrc, char* pSrcEnd)
635         {
636             if (_endsWithAmpersand)
637             {
638                 if (pSrcEnd - pSrc > 0 && pSrc[0] != '{')
639                 {
640                     OutputRestAmps();
641                 }
642                 _endsWithAmpersand = false;
643             }
644 
645             fixed (byte* pDstBegin = bufBytes)
646             {
647                 byte* pDst = pDstBegin + this.bufPos;
648 
649                 char ch = (char)0;
650                 for (;;)
651                 {
652                     byte* pDstEnd = pDst + (pSrcEnd - pSrc);
653                     if (pDstEnd > pDstBegin + bufLen)
654                     {
655                         pDstEnd = pDstBegin + bufLen;
656                     }
657 
658                     while (pDst < pDstEnd && (xmlCharType.IsAttributeValueChar((char)(ch = *pSrc)) && ch < 0x80))
659                     {
660                         *pDst++ = (byte)ch;
661                         pSrc++;
662                     }
663                     Debug.Assert(pSrc <= pSrcEnd);
664 
665                     // end of value
666                     if (pSrc >= pSrcEnd)
667                     {
668                         break;
669                     }
670 
671                     // end of buffer
672                     if (pDst >= pDstEnd)
673                     {
674                         bufPos = (int)(pDst - pDstBegin);
675                         FlushBuffer();
676                         pDst = pDstBegin + 1;
677                         continue;
678                     }
679 
680                     // some character needs to be escaped
681                     switch (ch)
682                     {
683                         case '&':
684                             if (pSrc + 1 == pSrcEnd)
685                             {
686                                 _endsWithAmpersand = true;
687                             }
688                             else if (pSrc[1] != '{')
689                             {
690                                 pDst = XmlUtf8RawTextWriter.AmpEntity(pDst);
691                                 break;
692                             }
693                             *pDst++ = (byte)ch;
694                             break;
695                         case '"':
696                             pDst = QuoteEntity(pDst);
697                             break;
698                         case '<':
699                         case '>':
700                         case '\'':
701                         case (char)0x9:
702                             *pDst++ = (byte)ch;
703                             break;
704                         case (char)0xD:
705                             // do not normalize new lines in attributes - just escape them
706                             pDst = CarriageReturnEntity(pDst);
707                             break;
708                         case (char)0xA:
709                             // do not normalize new lines in attributes - just escape them
710                             pDst = LineFeedEntity(pDst);
711                             break;
712                         default:
713                             const string hexDigits = "0123456789ABCDEF";
714                             Debug.Assert(_uriEscapingBuffer?.Length > 0);
715                             fixed (byte* pUriEscapingBuffer = &_uriEscapingBuffer[0])
716                             {
717                                 byte* pByte = pUriEscapingBuffer;
718                                 byte* pEnd = pByte;
719 
720                                 XmlUtf8RawTextWriter.CharToUTF8(ref pSrc, pSrcEnd, ref pEnd);
721 
722                                 while (pByte < pEnd)
723                                 {
724                                     *pDst++ = (byte)'%';
725                                     *pDst++ = (byte)hexDigits[*pByte >> 4];
726                                     *pDst++ = (byte)hexDigits[*pByte & 0xF];
727                                     pByte++;
728                                 }
729                             }
730                             continue;
731                     }
732                     pSrc++;
733                 }
734                 bufPos = (int)(pDst - pDstBegin);
735             }
736         }
737 
738         // For handling &{ in Html text field. If & is not followed by {, it still needs to be escaped.
OutputRestAmps()739         private void OutputRestAmps()
740         {
741             base.bufBytes[bufPos++] = (byte)'a';
742             base.bufBytes[bufPos++] = (byte)'m';
743             base.bufBytes[bufPos++] = (byte)'p';
744             base.bufBytes[bufPos++] = (byte)';';
745         }
746     }
747 
748 
749     //
750     // Indentation HtmlWriter only indent <BLOCK><BLOCK> situations
751     //
752     // Here are all the cases:
753     //       ELEMENT1     actions          ELEMENT2          actions                                 SC              EE
754     // 1).    SE SC   store SE blockPro       SE           a). check ELEMENT1 blockPro                  <A>           </A>
755     //        EE     if SE, EE are blocks                  b). true: check ELEMENT2 blockPro                <B>            <B>
756     //                                                     c). detect ELEMENT is SE, SC
757     //                                                     d). increase the indexlevel
758     //
759     // 2).    SE SC,  Store EE blockPro       EE            a). check stored blockPro                    <A></A>            </A>
760     //         EE    if SE, EE are blocks                  b). true:  indexLevel same                                  </B>
761     //
762 
763 
764     //
765     // This is an alternative way to make the output looks better
766     //
767     // Indentation HtmlWriter only indent <BLOCK><BLOCK> situations
768     //
769     // Here are all the cases:
770     //       ELEMENT1     actions           ELEMENT2          actions                                 Samples
771     // 1).    SE SC   store SE blockPro       SE            a). check ELEMENT1 blockPro                  <A>(blockPos)
772     //                                                     b). true: check ELEMENT2 blockPro                <B>
773     //                                                     c). detect ELEMENT is SE, SC
774     //                                                     d). increase the indentLevel
775     //
776     // 2).     EE     Store EE blockPro       SE            a). check stored blockPro                    </A>
777     //                                                     b). true:  indentLevel same                   <B>
778     //                                                     c). output block2
779     //
780     // 3).     EE      same as above          EE            a). check stored blockPro                          </A>
781     //                                                     b). true:  --indentLevel                        </B>
782     //                                                     c). output block2
783     //
784     // 4).    SE SC    same as above          EE            a). check stored blockPro                      <A></A>
785     //                                                     b). true:  indentLevel no change
786     internal class HtmlUtf8RawTextWriterIndent : HtmlUtf8RawTextWriter
787     {
788         //
789         // Fields
790         //
791         private int _indentLevel;
792 
793         // for detecting SE SC sitution
794         private int _endBlockPos;
795 
796         // settings
797         private string _indentChars;
798         private bool _newLineOnAttributes;
799 
800         //
801         // Constructors
802         //
803 
804 
805 
806 
807 
808 
809 
HtmlUtf8RawTextWriterIndent(Stream stream, XmlWriterSettings settings)810         public HtmlUtf8RawTextWriterIndent(Stream stream, XmlWriterSettings settings) : base(stream, settings)
811         {
812             Init(settings);
813         }
814 
815         //
816         // XmlRawWriter overrides
817         //
818         /// <summary>
819         /// Serialize the document type declaration.
820         /// </summary>
WriteDocType(string name, string pubid, string sysid, string subset)821         public override void WriteDocType(string name, string pubid, string sysid, string subset)
822         {
823             base.WriteDocType(name, pubid, sysid, subset);
824 
825             // Allow indentation after DocTypeDecl
826             _endBlockPos = base.bufPos;
827         }
828 
WriteStartElement(string prefix, string localName, string ns)829         public override void WriteStartElement(string prefix, string localName, string ns)
830         {
831             Debug.Assert(localName != null && localName.Length != 0 && prefix != null && ns != null);
832 
833 
834 
835             base.elementScope.Push((byte)base.currentElementProperties);
836 
837             if (ns.Length == 0)
838             {
839                 Debug.Assert(prefix.Length == 0);
840 
841                 base.currentElementProperties = (ElementProperties)elementPropertySearch.FindCaseInsensitiveString(localName);
842 
843                 if (_endBlockPos == base.bufPos && (base.currentElementProperties & ElementProperties.BLOCK_WS) != 0)
844                 {
845                     WriteIndent();
846                 }
847                 _indentLevel++;
848 
849                 base.bufBytes[bufPos++] = (byte)'<';
850             }
851             else
852             {
853                 base.currentElementProperties = ElementProperties.HAS_NS | ElementProperties.BLOCK_WS;
854 
855                 if (_endBlockPos == base.bufPos)
856                 {
857                     WriteIndent();
858                 }
859                 _indentLevel++;
860 
861                 base.bufBytes[base.bufPos++] = (byte)'<';
862                 if (prefix.Length != 0)
863                 {
864                     base.RawText(prefix);
865                     base.bufBytes[base.bufPos++] = (byte)':';
866                 }
867             }
868             base.RawText(localName);
869             base.attrEndPos = bufPos;
870         }
871 
StartElementContent()872         internal override void StartElementContent()
873         {
874             base.bufBytes[base.bufPos++] = (byte)'>';
875 
876             // Detect whether content is output
877             base.contentPos = base.bufPos;
878 
879             if ((currentElementProperties & ElementProperties.HEAD) != 0)
880             {
881                 WriteIndent();
882                 WriteMetaElement();
883                 _endBlockPos = base.bufPos;
884             }
885             else if ((base.currentElementProperties & ElementProperties.BLOCK_WS) != 0)
886             {
887                 // store the element block position
888                 _endBlockPos = base.bufPos;
889             }
890         }
891 
WriteEndElement(string prefix, string localName, string ns)892         internal override void WriteEndElement(string prefix, string localName, string ns)
893         {
894             bool isBlockWs;
895             Debug.Assert(localName != null && localName.Length != 0 && prefix != null && ns != null);
896 
897             _indentLevel--;
898 
899             // If this element has block whitespace properties,
900             isBlockWs = (base.currentElementProperties & ElementProperties.BLOCK_WS) != 0;
901             if (isBlockWs)
902             {
903                 // And if the last node to be output had block whitespace properties,
904                 // And if content was output within this element,
905                 if (_endBlockPos == base.bufPos && base.contentPos != base.bufPos)
906                 {
907                     // Then indent
908                     WriteIndent();
909                 }
910             }
911 
912             base.WriteEndElement(prefix, localName, ns);
913 
914             // Reset contentPos in case of empty elements
915             base.contentPos = 0;
916 
917             // Mark end of element in buffer for element's with block whitespace properties
918             if (isBlockWs)
919             {
920                 _endBlockPos = base.bufPos;
921             }
922         }
923 
WriteStartAttribute(string prefix, string localName, string ns)924         public override void WriteStartAttribute(string prefix, string localName, string ns)
925         {
926             if (_newLineOnAttributes)
927             {
928                 RawText(base.newLineChars);
929                 _indentLevel++;
930                 WriteIndent();
931                 _indentLevel--;
932             }
933             base.WriteStartAttribute(prefix, localName, ns);
934         }
935 
FlushBuffer()936         protected override void FlushBuffer()
937         {
938             // Make sure the buffer will reset the block position
939             _endBlockPos = (_endBlockPos == base.bufPos) ? 1 : 0;
940             base.FlushBuffer();
941         }
942 
943         //
944         // Private methods
945         //
Init(XmlWriterSettings settings)946         private void Init(XmlWriterSettings settings)
947         {
948             _indentLevel = 0;
949             _indentChars = settings.IndentChars;
950             _newLineOnAttributes = settings.NewLineOnAttributes;
951         }
952 
WriteIndent()953         private void WriteIndent()
954         {
955             // <block><inline>  -- suppress ws betw <block> and <inline>
956             // <block><block>   -- don't suppress ws betw <block> and <block>
957             // <block>text      -- suppress ws betw <block> and text (handled by wcharText method)
958             // <block><?PI?>    -- suppress ws betw <block> and PI
959             // <block><!-- -->  -- suppress ws betw <block> and comment
960 
961             // <inline><block>  -- suppress ws betw <inline> and <block>
962             // <inline><inline> -- suppress ws betw <inline> and <inline>
963             // <inline>text     -- suppress ws betw <inline> and text (handled by wcharText method)
964             // <inline><?PI?>   -- suppress ws betw <inline> and PI
965             // <inline><!-- --> -- suppress ws betw <inline> and comment
966 
967             RawText(base.newLineChars);
968             for (int i = _indentLevel; i > 0; i--)
969             {
970                 RawText(_indentChars);
971             }
972         }
973     }
974 }
975 
976 
977