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 & 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 & 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