1 //------------------------------------------------------------------------------ 2 // <copyright file="RecordBuilder.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // <owner current="true" primary="true">Microsoft</owner> 6 //------------------------------------------------------------------------------ 7 8 namespace System.Xml.Xsl.XsltOld { 9 using Res = System.Xml.Utils.Res; 10 using System; 11 using System.Diagnostics; 12 using System.Text; 13 using System.Xml; 14 using System.Xml.XPath; 15 using System.Collections; 16 17 internal sealed class RecordBuilder { 18 private int outputState; 19 private RecordBuilder next; 20 21 RecordOutput output; 22 23 // Atomization: 24 private XmlNameTable nameTable; 25 private OutKeywords atoms; 26 27 // Namespace manager for output 28 private OutputScopeManager scopeManager; 29 30 // Main node + Fields Collection 31 private BuilderInfo mainNode = new BuilderInfo(); 32 private ArrayList attributeList = new ArrayList(); 33 private int attributeCount; 34 private ArrayList namespaceList = new ArrayList(); 35 private int namespaceCount; 36 private BuilderInfo dummy = new BuilderInfo(); 37 38 // Current position in the list 39 private BuilderInfo currentInfo; 40 // Builder state 41 private bool popScope; 42 private int recordState; 43 private int recordDepth; 44 45 private const int NoRecord = 0; // No part of a new record was generated (old record was cleared out) 46 private const int SomeRecord = 1; // Record was generated partially (can be eventually record) 47 private const int HaveRecord = 2; // Record was fully generated 48 49 private const char s_Minus = '-'; 50 private const string s_Space = " "; 51 private const string s_SpaceMinus = " -"; 52 private const char s_Question = '?'; 53 private const char s_Greater = '>'; 54 private const string s_SpaceGreater = " >"; 55 56 private const string PrefixFormat = "xp_{0}"; 57 RecordBuilder(RecordOutput output, XmlNameTable nameTable)58 internal RecordBuilder(RecordOutput output, XmlNameTable nameTable) { 59 Debug.Assert(output != null); 60 this.output = output; 61 this.nameTable = nameTable != null ? nameTable : new NameTable(); 62 this.atoms = new OutKeywords(this.nameTable); 63 this.scopeManager = new OutputScopeManager(this.nameTable, this.atoms); 64 } 65 66 // 67 // Internal properties 68 // 69 70 internal int OutputState { 71 get { return this.outputState; } 72 set { this.outputState = value; } 73 } 74 75 internal RecordBuilder Next { 76 get { return this.next; } 77 set { this.next = value; } 78 } 79 80 internal RecordOutput Output { 81 get { return this.output; } 82 } 83 84 internal BuilderInfo MainNode { 85 get { return this.mainNode; } 86 } 87 88 internal ArrayList AttributeList { 89 get { return this.attributeList; } 90 } 91 92 internal int AttributeCount { 93 get { return this.attributeCount; } 94 } 95 96 internal OutputScopeManager Manager { 97 get { return this.scopeManager; } 98 } 99 ValueAppend(string s, bool disableOutputEscaping)100 private void ValueAppend(string s, bool disableOutputEscaping) { 101 this.currentInfo.ValueAppend(s, disableOutputEscaping); 102 } 103 CanOutput(int state)104 private bool CanOutput(int state) { 105 Debug.Assert(this.recordState != HaveRecord); 106 107 // If we have no record cached or the next event doesn't start new record, we are OK 108 109 if (this.recordState == NoRecord || (state & StateMachine.BeginRecord) == 0) { 110 return true; 111 } 112 else { 113 this.recordState = HaveRecord; 114 FinalizeRecord(); 115 SetEmptyFlag(state); 116 return this.output.RecordDone(this) == Processor.OutputResult.Continue; 117 } 118 } 119 BeginEvent(int state, XPathNodeType nodeType, string prefix, string name, string nspace, bool empty, Object htmlProps, bool search)120 internal Processor.OutputResult BeginEvent(int state, XPathNodeType nodeType, string prefix, string name, string nspace, bool empty, Object htmlProps, bool search) { 121 if (! CanOutput(state)) { 122 return Processor.OutputResult.Overflow; 123 } 124 125 Debug.Assert(this.recordState == NoRecord || (state & StateMachine.BeginRecord) == 0); 126 127 AdjustDepth(state); 128 ResetRecord(state); 129 PopElementScope(); 130 131 prefix = (prefix != null) ? this.nameTable.Add(prefix) : this.atoms.Empty; 132 name = (name != null) ? this.nameTable.Add(name) : this.atoms.Empty; 133 nspace = (nspace != null) ? this.nameTable.Add(nspace) : this.atoms.Empty; 134 135 switch (nodeType) { 136 case XPathNodeType.Element: 137 this.mainNode.htmlProps = htmlProps as HtmlElementProps; 138 this.mainNode.search = search; 139 BeginElement(prefix, name, nspace, empty); 140 break; 141 case XPathNodeType.Attribute: 142 BeginAttribute(prefix, name, nspace, htmlProps, search); 143 break; 144 case XPathNodeType.Namespace: 145 BeginNamespace(name, nspace); 146 break; 147 case XPathNodeType.Text: 148 break; 149 case XPathNodeType.ProcessingInstruction: 150 if (BeginProcessingInstruction(prefix, name, nspace) == false) { 151 return Processor.OutputResult.Error; 152 } 153 break; 154 case XPathNodeType.Comment: 155 BeginComment(); 156 break; 157 case XPathNodeType.Root: 158 break; 159 case XPathNodeType.Whitespace: 160 case XPathNodeType.SignificantWhitespace: 161 case XPathNodeType.All: 162 break; 163 } 164 165 return CheckRecordBegin(state); 166 } 167 TextEvent(int state, string text, bool disableOutputEscaping)168 internal Processor.OutputResult TextEvent(int state, string text, bool disableOutputEscaping) { 169 if (! CanOutput(state)) { 170 return Processor.OutputResult.Overflow; 171 } 172 173 Debug.Assert(this.recordState == NoRecord || (state & StateMachine.BeginRecord) == 0); 174 175 AdjustDepth(state); 176 ResetRecord(state); 177 PopElementScope(); 178 179 if ((state & StateMachine.BeginRecord) != 0) { 180 this.currentInfo.Depth = this.recordDepth; 181 this.currentInfo.NodeType = XmlNodeType.Text; 182 } 183 184 ValueAppend(text, disableOutputEscaping); 185 186 return CheckRecordBegin(state); 187 } 188 EndEvent(int state, XPathNodeType nodeType)189 internal Processor.OutputResult EndEvent(int state, XPathNodeType nodeType) { 190 if (! CanOutput(state)) { 191 return Processor.OutputResult.Overflow; 192 } 193 194 AdjustDepth(state); 195 PopElementScope(); 196 this.popScope = (state & StateMachine.PopScope) != 0; 197 198 if ((state & StateMachine.EmptyTag) != 0 && this.mainNode.IsEmptyTag == true) { 199 return Processor.OutputResult.Continue; 200 } 201 202 ResetRecord(state); 203 204 if ((state & StateMachine.BeginRecord) != 0) { 205 if(nodeType == XPathNodeType.Element) { 206 EndElement(); 207 } 208 } 209 210 return CheckRecordEnd(state); 211 } 212 Reset()213 internal void Reset() { 214 if (this.recordState == HaveRecord) { 215 this.recordState = NoRecord; 216 } 217 } 218 TheEnd()219 internal void TheEnd() { 220 if (this.recordState == SomeRecord) { 221 this.recordState = HaveRecord; 222 FinalizeRecord(); 223 this.output.RecordDone(this); 224 } 225 this.output.TheEnd(); 226 } 227 228 // 229 // Utility implementation methods 230 // 231 FindAttribute(string name, string nspace, ref string prefix)232 private int FindAttribute(string name, string nspace, ref string prefix) { 233 Debug.Assert(this.attributeCount <= this.attributeList.Count); 234 235 for (int attrib = 0; attrib < this.attributeCount; attrib ++) { 236 Debug.Assert(this.attributeList[attrib] != null && this.attributeList[attrib] is BuilderInfo); 237 238 BuilderInfo attribute = (BuilderInfo) this.attributeList[attrib]; 239 240 if (Ref.Equal(attribute.LocalName, name)) { 241 if (Ref.Equal(attribute.NamespaceURI, nspace)) { 242 return attrib; 243 } 244 if (Ref.Equal(attribute.Prefix, prefix)) { 245 // prefix conflict. Should be renamed. 246 prefix = string.Empty; 247 } 248 } 249 250 } 251 252 return -1; 253 } 254 BeginElement(string prefix, string name, string nspace, bool empty)255 private void BeginElement(string prefix, string name, string nspace, bool empty) { 256 Debug.Assert(this.attributeCount == 0); 257 258 this.currentInfo.NodeType = XmlNodeType.Element; 259 this.currentInfo.Prefix = prefix; 260 this.currentInfo.LocalName = name; 261 this.currentInfo.NamespaceURI = nspace; 262 this.currentInfo.Depth = this.recordDepth; 263 this.currentInfo.IsEmptyTag = empty; 264 265 this.scopeManager.PushScope(name, nspace, prefix); 266 } 267 EndElement()268 private void EndElement() { 269 Debug.Assert(this.attributeCount == 0); 270 OutputScope elementScope = this.scopeManager.CurrentElementScope; 271 272 this.currentInfo.NodeType = XmlNodeType.EndElement; 273 this.currentInfo.Prefix = elementScope.Prefix; 274 this.currentInfo.LocalName = elementScope.Name; 275 this.currentInfo.NamespaceURI = elementScope.Namespace; 276 this.currentInfo.Depth = this.recordDepth; 277 } 278 NewAttribute()279 private int NewAttribute() { 280 if (this.attributeCount >= this.attributeList.Count) { 281 Debug.Assert(this.attributeCount == this.attributeList.Count); 282 this.attributeList.Add(new BuilderInfo()); 283 } 284 return this.attributeCount ++; 285 } 286 BeginAttribute(string prefix, string name, string nspace, Object htmlAttrProps, bool search)287 private void BeginAttribute(string prefix, string name, string nspace, Object htmlAttrProps, bool search) { 288 int attrib = FindAttribute(name, nspace, ref prefix); 289 290 if (attrib == -1) { 291 attrib = NewAttribute(); 292 } 293 294 Debug.Assert(this.attributeList[attrib] != null && this.attributeList[attrib] is BuilderInfo); 295 296 BuilderInfo attribute = (BuilderInfo) this.attributeList[attrib]; 297 attribute.Initialize(prefix, name, nspace); 298 attribute.Depth = this.recordDepth; 299 attribute.NodeType = XmlNodeType.Attribute; 300 attribute.htmlAttrProps = htmlAttrProps as HtmlAttributeProps; 301 attribute.search = search; 302 this.currentInfo = attribute; 303 } 304 BeginNamespace(string name, string nspace)305 private void BeginNamespace(string name, string nspace) { 306 bool thisScope = false; 307 if (Ref.Equal(name, this.atoms.Empty)) { 308 if (Ref.Equal(nspace, this.scopeManager.DefaultNamespace)) { 309 // Main Node is OK 310 } 311 else if (Ref.Equal(this.mainNode.NamespaceURI, this.atoms.Empty)) { 312 // http://www.w3.org/1999/11/REC-xslt-19991116-errata/ E25 313 // Should throw an error but ingnoring it in Everett. 314 // Would be a breaking change 315 } 316 else { 317 DeclareNamespace(nspace, name); 318 } 319 } 320 else { 321 string nspaceDeclared = this.scopeManager.ResolveNamespace(name, out thisScope); 322 if (nspaceDeclared != null) { 323 if (! Ref.Equal(nspace, nspaceDeclared)) { 324 if(!thisScope) { 325 DeclareNamespace(nspace, name); 326 } 327 } 328 } 329 else { 330 DeclareNamespace(nspace, name); 331 } 332 } 333 this.currentInfo = dummy; 334 currentInfo.NodeType = XmlNodeType.Attribute; 335 } 336 BeginProcessingInstruction(string prefix, string name, string nspace)337 private bool BeginProcessingInstruction(string prefix, string name, string nspace) { 338 this.currentInfo.NodeType = XmlNodeType.ProcessingInstruction; 339 this.currentInfo.Prefix = prefix; 340 this.currentInfo.LocalName = name; 341 this.currentInfo.NamespaceURI = nspace; 342 this.currentInfo.Depth = this.recordDepth; 343 return true; 344 } 345 BeginComment()346 private void BeginComment() { 347 this.currentInfo.NodeType = XmlNodeType.Comment; 348 this.currentInfo.Depth = this.recordDepth; 349 } 350 AdjustDepth(int state)351 private void AdjustDepth(int state) { 352 switch (state & StateMachine.DepthMask) { 353 case StateMachine.DepthUp: 354 this.recordDepth ++; 355 break; 356 case StateMachine.DepthDown: 357 this.recordDepth --; 358 break; 359 default: 360 break; 361 } 362 } 363 ResetRecord(int state)364 private void ResetRecord(int state) { 365 Debug.Assert(this.recordState == NoRecord || this.recordState == SomeRecord); 366 367 if ((state & StateMachine.BeginRecord) != 0) { 368 this.attributeCount = 0; 369 this.namespaceCount = 0; 370 this.currentInfo = this.mainNode; 371 372 this.currentInfo.Initialize(this.atoms.Empty, this.atoms.Empty, this.atoms.Empty); 373 this.currentInfo.NodeType = XmlNodeType.None; 374 this.currentInfo.IsEmptyTag = false; 375 this.currentInfo.htmlProps = null; 376 this.currentInfo.htmlAttrProps = null; 377 } 378 } 379 PopElementScope()380 private void PopElementScope() { 381 if (this.popScope) { 382 this.scopeManager.PopScope(); 383 this.popScope = false; 384 } 385 } 386 CheckRecordBegin(int state)387 private Processor.OutputResult CheckRecordBegin(int state) { 388 Debug.Assert(this.recordState == NoRecord || this.recordState == SomeRecord); 389 390 if ((state & StateMachine.EndRecord) != 0) { 391 this.recordState = HaveRecord; 392 FinalizeRecord(); 393 SetEmptyFlag(state); 394 return this.output.RecordDone(this); 395 } 396 else { 397 this.recordState = SomeRecord; 398 return Processor.OutputResult.Continue; 399 } 400 } 401 CheckRecordEnd(int state)402 private Processor.OutputResult CheckRecordEnd(int state) { 403 Debug.Assert(this.recordState == NoRecord || this.recordState == SomeRecord); 404 405 if ((state & StateMachine.EndRecord) != 0) { 406 this.recordState = HaveRecord; 407 FinalizeRecord(); 408 SetEmptyFlag(state); 409 return this.output.RecordDone(this); 410 } 411 else { 412 // For end event, if there is no end token, don't force token 413 return Processor.OutputResult.Continue; 414 } 415 } 416 SetEmptyFlag(int state)417 private void SetEmptyFlag(int state) { 418 Debug.Assert(this.mainNode != null); 419 420 if ((state & StateMachine.BeginChild) != 0) { 421 this.mainNode.IsEmptyTag = false; 422 } 423 } 424 425 AnalyzeSpaceLang()426 private void AnalyzeSpaceLang() { 427 Debug.Assert(this.mainNode.NodeType == XmlNodeType.Element); 428 429 for (int attr = 0; attr < this.attributeCount; attr ++) { 430 Debug.Assert(this.attributeList[attr] is BuilderInfo); 431 BuilderInfo info = (BuilderInfo) this.attributeList[attr]; 432 433 if (Ref.Equal(info.Prefix, this.atoms.Xml)) { 434 OutputScope scope = this.scopeManager.CurrentElementScope; 435 436 if (Ref.Equal(info.LocalName, this.atoms.Lang)) { 437 scope.Lang = info.Value; 438 } 439 else if (Ref.Equal(info.LocalName, this.atoms.Space)) { 440 scope.Space = TranslateXmlSpace(info.Value); 441 } 442 } 443 } 444 } 445 FixupElement()446 private void FixupElement() { 447 Debug.Assert(this.mainNode.NodeType == XmlNodeType.Element); 448 449 if (Ref.Equal(this.mainNode.NamespaceURI, this.atoms.Empty)) { 450 this.mainNode.Prefix = this.atoms.Empty; 451 } 452 453 if (Ref.Equal(this.mainNode.Prefix, this.atoms.Empty)) { 454 if (Ref.Equal(this.mainNode.NamespaceURI, this.scopeManager.DefaultNamespace)) { 455 // Main Node is OK 456 } 457 else { 458 DeclareNamespace(this.mainNode.NamespaceURI, this.mainNode.Prefix); 459 } 460 } 461 else { 462 bool thisScope = false; 463 string nspace = this.scopeManager.ResolveNamespace(this.mainNode.Prefix, out thisScope); 464 if (nspace != null) { 465 if (! Ref.Equal(this.mainNode.NamespaceURI, nspace)) { 466 if (thisScope) { // Prefix conflict 467 this.mainNode.Prefix = GetPrefixForNamespace(this.mainNode.NamespaceURI); 468 } 469 else { 470 DeclareNamespace(this.mainNode.NamespaceURI, this.mainNode.Prefix); 471 } 472 } 473 } 474 else { 475 DeclareNamespace(this.mainNode.NamespaceURI, this.mainNode.Prefix); 476 } 477 } 478 479 OutputScope elementScope = this.scopeManager.CurrentElementScope; 480 elementScope.Prefix = this.mainNode.Prefix; 481 } 482 FixupAttributes(int attributeCount)483 private void FixupAttributes(int attributeCount) { 484 for (int attr = 0; attr < attributeCount; attr ++) { 485 Debug.Assert(this.attributeList[attr] is BuilderInfo); 486 BuilderInfo info = (BuilderInfo) this.attributeList[attr]; 487 488 489 if (Ref.Equal(info.NamespaceURI, this.atoms.Empty)) { 490 info.Prefix = this.atoms.Empty; 491 } 492 else { 493 if (Ref.Equal(info.Prefix, this.atoms.Empty)) { 494 info.Prefix = GetPrefixForNamespace(info.NamespaceURI); 495 } 496 else { 497 bool thisScope = false; 498 string nspace = this.scopeManager.ResolveNamespace(info.Prefix, out thisScope); 499 if (nspace != null) { 500 if (! Ref.Equal(info.NamespaceURI, nspace)) { 501 if(thisScope) { // prefix conflict 502 info.Prefix = GetPrefixForNamespace(info.NamespaceURI); 503 } 504 else { 505 DeclareNamespace(info.NamespaceURI, info.Prefix); 506 } 507 } 508 } 509 else { 510 DeclareNamespace(info.NamespaceURI, info.Prefix); 511 } 512 } 513 } 514 } 515 } 516 AppendNamespaces()517 private void AppendNamespaces() { 518 for (int i = this.namespaceCount - 1; i >= 0; i --) { 519 BuilderInfo attribute = (BuilderInfo) this.attributeList[NewAttribute()]; 520 attribute.Initialize((BuilderInfo)this.namespaceList[i]); 521 } 522 } 523 AnalyzeComment()524 private void AnalyzeComment() { 525 Debug.Assert(this.mainNode.NodeType == XmlNodeType.Comment); 526 Debug.Assert((object) this.currentInfo == (object) this.mainNode); 527 528 StringBuilder newComment = null; 529 string comment = this.mainNode.Value; 530 bool minus = false; 531 int index = 0, begin = 0; 532 533 for (; index < comment.Length; index ++) { 534 switch (comment[index]) { 535 case s_Minus: 536 if (minus) { 537 if (newComment == null) 538 newComment = new StringBuilder(comment, begin, index, 2 * comment.Length); 539 else 540 newComment.Append(comment, begin, index - begin); 541 542 newComment.Append(s_SpaceMinus); 543 begin = index + 1; 544 } 545 minus = true; 546 break; 547 default: 548 minus = false; 549 break; 550 } 551 } 552 553 if (newComment != null) { 554 if (begin < comment.Length) 555 newComment.Append(comment, begin, comment.Length - begin); 556 557 if (minus) 558 newComment.Append(s_Space); 559 560 this.mainNode.Value = newComment.ToString(); 561 } 562 else if (minus) { 563 this.mainNode.ValueAppend(s_Space, false); 564 } 565 } 566 AnalyzeProcessingInstruction()567 private void AnalyzeProcessingInstruction() { 568 Debug.Assert(this.mainNode.NodeType == XmlNodeType.ProcessingInstruction || this.mainNode.NodeType == XmlNodeType.XmlDeclaration); 569 //Debug.Assert((object) this.currentInfo == (object) this.mainNode); 570 571 StringBuilder newPI = null; 572 string pi = this.mainNode.Value; 573 bool question = false; 574 int index = 0, begin = 0; 575 576 for (; index < pi.Length; index ++) { 577 switch (pi[index]) { 578 case s_Question: 579 question = true; 580 break; 581 case s_Greater: 582 if (question) { 583 if (newPI == null) { 584 newPI = new StringBuilder(pi, begin, index, 2 * pi.Length); 585 } 586 else { 587 newPI.Append(pi, begin, index - begin); 588 } 589 newPI.Append(s_SpaceGreater); 590 begin = index + 1; 591 } 592 question = false; 593 break; 594 default: 595 question = false; 596 break; 597 } 598 } 599 600 if (newPI != null) { 601 if (begin < pi.Length) { 602 newPI.Append(pi, begin, pi.Length - begin); 603 } 604 this.mainNode.Value = newPI.ToString(); 605 } 606 } 607 FinalizeRecord()608 private void FinalizeRecord() { 609 switch (this.mainNode.NodeType) { 610 case XmlNodeType.Element: 611 // Save count since FixupElement can add attribute... 612 int attributeCount = this.attributeCount; 613 614 FixupElement(); 615 FixupAttributes(attributeCount); 616 AnalyzeSpaceLang(); 617 AppendNamespaces(); 618 break; 619 case XmlNodeType.Comment: 620 AnalyzeComment(); 621 break; 622 case XmlNodeType.ProcessingInstruction: 623 AnalyzeProcessingInstruction(); 624 break; 625 } 626 } 627 NewNamespace()628 private int NewNamespace() { 629 if (this.namespaceCount >= this.namespaceList.Count) { 630 Debug.Assert(this.namespaceCount == this.namespaceList.Count); 631 this.namespaceList.Add(new BuilderInfo()); 632 } 633 return this.namespaceCount ++; 634 } 635 DeclareNamespace(string nspace, string prefix)636 private void DeclareNamespace(string nspace, string prefix) { 637 int index = NewNamespace(); 638 639 Debug.Assert(this.namespaceList[index] != null && this.namespaceList[index] is BuilderInfo); 640 641 BuilderInfo ns = (BuilderInfo) this.namespaceList[index]; 642 if (prefix == this.atoms.Empty) { 643 ns.Initialize(this.atoms.Empty, this.atoms.Xmlns, this.atoms.XmlnsNamespace); 644 } 645 else { 646 ns.Initialize(this.atoms.Xmlns, prefix, this.atoms.XmlnsNamespace); 647 } 648 ns.Depth = this.recordDepth; 649 ns.NodeType = XmlNodeType.Attribute; 650 ns.Value = nspace; 651 652 this.scopeManager.PushNamespace(prefix, nspace); 653 } 654 DeclareNewNamespace(string nspace)655 private string DeclareNewNamespace(string nspace) { 656 string prefix = this.scopeManager.GeneratePrefix(PrefixFormat); 657 DeclareNamespace(nspace, prefix); 658 return prefix; 659 } 660 GetPrefixForNamespace(string nspace)661 internal string GetPrefixForNamespace(string nspace) { 662 string prefix = null; 663 664 if (this.scopeManager.FindPrefix(nspace, out prefix)) { 665 Debug.Assert(prefix != null && prefix.Length > 0); 666 return prefix; 667 } 668 else { 669 return DeclareNewNamespace(nspace); 670 } 671 } 672 TranslateXmlSpace(string space)673 private static XmlSpace TranslateXmlSpace(string space) { 674 if (space == "default") { 675 return XmlSpace.Default; 676 } 677 else if (space == "preserve") { 678 return XmlSpace.Preserve; 679 } 680 else { 681 return XmlSpace.None; 682 } 683 } 684 } 685 } 686