1//------------------------------------------------------------------------------ 2// <copyright file="NavigatorInput.cs" company="Microsoft"> 3// Copyright (c) Microsoft Corporation. All rights reserved. 4// </copyright> 5//------------------------------------------------------------------------------ 6using System; 7using System.Text; 8using System.Collections; 9using System.Diagnostics; 10using System.Xml; 11using System.Xml.XPath; 12using ISourceLineInfo = MS.Internal.Xml.ISourceLineInfo; 13 14namespace Microsoft.Xml.Query.Xslt { 15 16 // This class is designed to help XSLT parser with reading stylesheet. We should be able to load SS from 17 // XmlReader, URL, XPathNavigator. The simples way to load from URL and XmlReader is to load xml to XPathDocument and 18 // use navigator over XPathDocument. To avoid intermediate storage XsltInput can work directly from reader and limits 19 // navigation over SS nodes to XmlReader like MoveTo* functions. 20 // document("") function in the case of XslInput(XmlReader) will read SS again. To be able to "reread" SS 21 // we should be garanteed that Xml.Reader.BaseUri != "". 22 23 // Simplest way to implement two ways if XslInput is to make it abstract class and put implementation in its subclasses. 24 // In purpose to not have two additional classes, two additional files and bunch of virtual functions we put both 25 // implementation into base XslImput class. (navigator == null) means that reader is used. 26 27 // To aline XPathNavigator and XmlReader we 28 // 1. Restrict all navigation to pure reader ability: 29 // a) Forward only, one pass. 30 // b) You should call MoveToFirstChildren on nonempty element node. (or may be skip) 31 // c) You should call MoveToParent when no more children is available. 32 // 2. Extend XmlReader to XPath data model. 33 // a) merge all text, WS and CDATA nodes. 34 // b) make distinction between NS and other attributes 35 // c) keep track of all namespaces available for each node 36 // 3. Real problem in XmlReader part we have with MoveToParent(). Navigator will be positioned on real parent while 37 // Reader can't do this. It will be positioned on EndElement or anythig after it. To allign behavior here 38 // we are prohibinting to assess node properties (NodeType, LocalName, Value) after MoveToParent(). 39 // 3.1. The special part of this problem for XmlReader is <A></A>. MoveToFirstChild() sould be called. It it should 40 // return false (to line with Navigator) Reader stays on </A> 41 // 4. We don't need value of Element nodes. To lineup with XmlReader set it to string.Empty. 42 // 5. XPathDocument ignores whitespaces ot the root level. To acomplish this with reader we use it's Depth property. 43 // 6. Namespaces are in reverce order in XPathDocument. We are not adressing this problem. 44 45 internal class XsltInput : ISourceLineInfo { 46 private InputScopeManager scopeManager; 47 private XsltInput next; 48 private Keywords atoms; 49 50 private XPathNavigator navigator; 51 private XmlValidatingReader reader; 52 private string text; // Special state for reader base Input. when it != null reader stays after all 53 private bool textIsWhite; // merged text nodes. textIsWhite true alny if all partes was WS or SWS 54 private bool shouldCloseReader; 55 private int depth; 56 57 // Cahced properties. MoveTo* functions set them. 58 private XPathNodeType nodeType; 59 private string localName; 60 private string namespaceUri; 61 private string value; 62 63 public XsltInput(XPathNavigator navigator) { 64 Init(navigator); 65 } 66 67 public XsltInput(XmlReader reader) { 68 if (reader.BaseURI == null || reader.BaseURI == "") { 69 Init((new System.Xml.XPath.XPathDocument(reader, XmlSpace.Preserve)).CreateNavigator()); 70 } 71 else { 72 Init(reader); 73 } 74 } 75 76 public XsltInput(string uri) { 77 CompareInputs(uri); 78 Init(new XmlTextReader(uri)); 79 // Debug.Assert(uri == this.reader.BaseURI); 80 } 81 private void Init(XPathNavigator navigator) { 82 if (navigator == null) throw new ArgumentException(nameof(navigator)); 83 this.navigator = navigator; 84 this.scopeManager = new InputScopeManager(navigator.NameTable); 85 this.atoms = new Keywords(navigator.NameTable); 86 if (this.navigator.NodeType == XPathNodeType.Root) { 87 this.navigator.MoveToFirstChild(); 88 // ??? Do we need to check result. 89 } 90 StepOnNodeNav(); 91 } 92 93 private void Init(XmlReader reader) { 94 if (reader == null) throw new ArgumentException(nameof(reader)); 95 this.reader = EnsureExpandEntities(reader); 96 this.scopeManager = new InputScopeManager(reader.NameTable); 97 this.atoms = new Keywords(reader.NameTable); 98 this.ReadNextSiblig(); 99 StepOnNodeRdr(); 100 } 101 102 private XmlValidatingReader EnsureExpandEntities(XmlReader reader) { 103 XmlValidatingReader vr = reader as XmlValidatingReader; 104 if (vr == null || vr.EntityHandling != EntityHandling.ExpandEntities) { 105 vr = new XmlValidatingReader( reader ); 106 vr.EntityHandling = EntityHandling.ExpandEntities; 107 vr.ValidationType = ValidationType.None; 108 this.shouldCloseReader = true; 109 } 110 return vr; 111 } 112 113 public void Close() { 114 if (shouldCloseReader) { 115 Debug.Assert(this.reader != null); 116 this.reader.Close(); 117 this.reader = null; 118 } 119 } 120 121 private void SetCachedPropertiesFromNavigator() { 122 Debug.Assert(this.navigator != null); 123 nodeType = this.navigator.NodeType; 124 localName = this.navigator.LocalName; 125 namespaceUri = this.navigator.NamespaceURI; 126 value = (nodeType != XPathNodeType.Element) ? this.navigator.Value : string.Empty; 127 } 128 129 private void SetCachedPropertiesFromReader() { 130 Debug.Assert(this.reader != null); 131 nodeType = ConvertNodeType(this.reader.NodeType); 132 localName = this.reader.LocalName; 133 namespaceUri = this.reader.NamespaceURI; 134 value = this.reader.Value; 135 if (nodeType == XPathNodeType.Attribute && IsNamespace(this.reader)) { 136 nodeType = XPathNodeType.Namespace; 137 namespaceUri = string.Empty; 138 if (localName == "xmlns") { 139 localName = string.Empty; 140 } 141 } 142 } 143 144 public XsltInput Next { 145 get { return this.next; } 146 set { this.next = value; } 147 } 148 149 public Keywords Atoms { get { return this.atoms; } } 150 151 public XPathNavigator Navigator { get { return this.navigator; } } 152 153 // Propertes we have cached 154 public XPathNodeType NodeType { get { return this.nodeType; } } 155 public string Value { get { return this.value; } } 156 public string LocalName { get { return this.localName; } } 157 public string NamespaceUri { get { return this.namespaceUri; } } 158 159 public int Depth { get { return this.depth; } } 160 161 // Prefix, IsEmptyElement and BaseURI are not cached 162 public string Prefix { 163 get { 164 Debug.Assert(! IsTextType(this.NodeType)); 165 if (navigator != null) { 166 return this.navigator.Prefix; 167 } 168 else { 169 return this.reader.Prefix; 170 } 171 } 172 } 173 174 public bool IsEmptyElement { 175 get { 176 Debug.Assert(! IsTextType(this.NodeType)); 177 if (navigator != null) { 178 return this.navigator.IsEmptyElement; 179 } 180 else { 181 return this.reader.IsEmptyElement; 182 } 183 } 184 } 185 186 public string BaseUri { 187 get { 188 Debug.Assert(! IsTextType(this.NodeType)); 189 if (navigator != null) { 190 return this.navigator.BaseURI; 191 } 192 else { 193 return this.reader.BaseURI; 194 } 195 } 196 } 197 198 public string LookupXmlNamespace(string prefix) { 199 Debug.Assert(prefix != null); 200 string nsUri = (navigator != null 201 ? this.navigator.GetNamespace(prefix) 202 : this.reader.LookupNamespace(prefix) 203 ); 204 if (nsUri != null) { 205 return nsUri; 206 } 207 if (prefix.Length == 0) { 208 return string.Empty; 209 } 210 throw new XmlException("SR.Xslt_InvalidPrefix, prefix"); 211 } 212 213 static bool IsNamespace(XmlReader reader) { 214 Debug.Assert(reader.NodeType == XmlNodeType.Attribute); 215 return reader.Prefix == "xmlns" || reader.Prefix.Length == 0 && reader.LocalName == "xmlns"; 216 } 217 218 static bool NextAttribute(XmlReader reader) { 219 do { 220 if (! reader.MoveToNextAttribute()) { 221 return false; 222 } 223 }while (IsNamespace(reader)); 224 return true; 225 } 226 227 static bool NextNamespace(XmlReader reader) { 228 do { 229 if (! reader.MoveToNextAttribute()) { 230 return false; 231 } 232 }while (! IsNamespace(reader)); 233 return true; 234 } 235 236 public bool MoveToFirstAttribute() { 237 Debug.Assert(this.NodeType == XPathNodeType.Element); 238 if (navigator != null) { 239 if (this.navigator.MoveToFirstAttribute()) { 240 SetCachedPropertiesFromNavigator(); 241 return true; 242 } 243 } 244 else { 245 if (NextAttribute(this.reader)) { 246 SetCachedPropertiesFromReader(); 247 return true; 248 } 249 else { 250 // Attributes and namespaces are mixed in reader. It's posible that we are position on NS now. 251 // To fixup this problem: 252 this.reader.MoveToElement(); 253 } 254 } 255 return false; 256 } 257 258 public bool MoveToNextAttribute() { 259 Debug.Assert(this.NodeType == XPathNodeType.Attribute); 260 if (navigator != null) { 261 if (this.navigator.MoveToNextAttribute()) { 262 SetCachedPropertiesFromNavigator(); 263 return true; 264 } 265 } 266 else { 267 if (NextAttribute(this.reader)) { 268 SetCachedPropertiesFromReader(); 269 return true; 270 } 271 } 272 return false; 273 } 274 275 public bool MoveToFirstNamespace() { 276 Debug.Assert(this.NodeType == XPathNodeType.Element); 277 if (navigator != null) { 278 if (this.navigator.MoveToFirstNamespace(XPathNamespaceScope.Local)) { 279 SetCachedPropertiesFromNavigator(); 280 return true; 281 } 282 } 283 else { 284 if (NextNamespace(this.reader)) { 285 SetCachedPropertiesFromReader(); 286 return true; 287 } 288 else { 289 // Attributes and namespaces are mixed in reader. It's posible that we are position on Attribute now. 290 // To fixup this problem: 291 this.reader.MoveToElement(); 292 } 293 } 294 return false; 295 } 296 297 public bool MoveToNextNamespace() { 298 Debug.Assert(this.NodeType == XPathNodeType.Namespace); 299 if (navigator != null) { 300 if (this.navigator.MoveToNextNamespace(XPathNamespaceScope.Local)) { 301 SetCachedPropertiesFromNavigator(); 302 return true; 303 } 304 } 305 else { 306 if (NextNamespace(this.reader)) { 307 SetCachedPropertiesFromReader(); 308 return true; 309 } 310 } 311 return false; 312 } 313 314 public void MoveToElement() { 315 Debug.Assert(this.NodeType == XPathNodeType.Attribute || this.NodeType == XPathNodeType.Namespace, "We should be on an attribute or on a namespace to call MoveToElement"); 316 if (navigator != null) { 317 this.navigator.MoveToParent(); 318 SetCachedPropertiesFromNavigator(); 319 } 320 else { 321 this.reader.MoveToElement(); 322 SetCachedPropertiesFromReader(); 323 } 324 } 325 326 // Debulg subsystem 327 enum Moves { 328 Next, Child, Parent 329 }; 330 331 Moves lastMove = Moves.Child; 332 bool lastResult = true; 333 334 [System.Diagnostics.Conditional("DEBUG")] 335 private void SetLastMove(Moves lastMove, bool lastResult) { 336 this.lastMove = lastMove; 337 this.lastResult = lastResult; 338 } 339 // -------------------- 340 341 private void StepOnNodeNav() { 342 SetCachedPropertiesFromNavigator(); 343 if (this.NodeType == XPathNodeType.Element) { 344 this.scopeManager.PushScope(); 345 } 346 } 347 348 private void StepOnNodeRdr() { 349 if (this.text == null) { 350 SetCachedPropertiesFromReader(); 351 } 352 else { 353 this.value = this.text; 354 this.localName = string.Empty; 355 this.namespaceUri = string.Empty; 356 this.nodeType = ( 357 (! this.textIsWhite ) ? XPathNodeType.Text : 358 (reader.XmlSpace == XmlSpace.Preserve) ? XPathNodeType.SignificantWhitespace : 359 /*default: */ XPathNodeType.Whitespace 360 ); 361 } 362 if (this.NodeType == XPathNodeType.Element) { 363 this.scopeManager.PushScope(); 364 } 365 } 366 367 private void StepOffNode () { 368 if (this.NodeType == XPathNodeType.Element) { 369 this.scopeManager.PopScope(); 370 } 371 } 372 373 private bool MoveToFirstChildAny() { 374 bool result; 375 if (navigator != null) { 376 result = this.navigator.MoveToFirstChild(); 377 if (result) { 378 StepOnNodeNav(); 379 depth ++; 380 } 381 } 382 else { 383 if (this.reader.IsEmptyElement) { 384 result = false; 385 } 386 else { 387 result = this.ReadNextSiblig(); 388 Debug.Assert(result, "For non-empty element ReadNextSiblig() can't fail"); 389 StepOnNodeRdr(); 390 depth ++; 391 } 392 } 393 return result; 394 } 395 396 public bool MoveToFirstChild() { 397 Debug.Assert(this.lastResult && this.lastMove != Moves.Parent, "We should sucessfuly move MoveToNextSibling() after MoveToParent()"); 398 Debug.Assert(this.NodeType == XPathNodeType.Element); 399 bool result = MoveToFirstChildAny(); 400 if (result) { 401 // Pass commants and PIs 402 if(this.NodeType == XPathNodeType.Comment || this.NodeType == XPathNodeType.ProcessingInstruction) { 403 result = this.MoveToNextSibling(); 404 } 405 if (! result) { 406 this.MoveToParent(); 407 } 408 } 409 SetLastMove(Moves.Child, result); 410 return result; 411 } 412 413 private bool MoveToNextSiblingAny() { 414 bool result; 415 StepOffNode(); 416 if (navigator != null) { 417 result = this.navigator.MoveToNext(); 418 if (result) { 419 StepOnNodeNav(); 420 } 421 } 422 else { 423 result = this.ReadNextSiblig(); 424 if (result) { 425 StepOnNodeRdr(); 426 } 427 } 428 return result; 429 } 430 431 public bool MoveToNextSibling() { 432#if DEBUG 433 if (this.lastMove == Moves.Next) { 434 Debug.Assert(this.lastResult, "we can't move next if we already failed doing this"); 435 Debug.Assert(this.NodeType != XPathNodeType.Element, "We should call MoveToFirstChild() on all elements. See SkipElement()."); 436 } 437#endif 438 bool result; 439 do { 440 result = this.MoveToNextSiblingAny(); 441 }while (result && (this.NodeType == XPathNodeType.Comment || this.NodeType == XPathNodeType.ProcessingInstruction)); 442 SetLastMove(Moves.Next, result); 443 return result; 444 } 445 446 public bool MoveToParent() { 447 Debug.Assert(this.lastMove == Moves.Next && ! this.lastResult, "we can't move parent before read all children"); 448 bool result; 449 // We shouldn't call StepOffNode() here because we already left last node. 450 if (navigator != null) { 451 result = this.navigator.MoveToParent(); 452 } 453 else { 454 result = true; // this not exectely true if we are on root. 455 } 456 depth --; 457 SetLastMove(Moves.Parent, result); 458 return result; 459 } 460 461 public void SkipNode() { 462 if (this.NodeType == XPathNodeType.Element && this.MoveToFirstChild()) { 463 do { 464 this.SkipNode(); 465 } while(this.MoveToNextSibling()); 466 this.MoveToParent(); 467 } 468 } 469 470 private bool ReadNextSiblig() { 471 if (this.text != null) { 472 this.text = null; 473 return this.reader.NodeType != XmlNodeType.EndElement; 474 } 475 XmlSpace space = reader.XmlSpace; 476 this.textIsWhite = true; 477 do { 478 if (! reader.Read()) { 479 return this.text != null; 480 } 481 switch (reader.NodeType) { 482 case XmlNodeType.Text: 483 this.textIsWhite = false; 484 goto case XmlNodeType.SignificantWhitespace; 485 case XmlNodeType.CDATA: 486 if (this.textIsWhite && ! IsWhitespace(reader.Value)) { 487 this.textIsWhite = false; 488 } 489 goto case XmlNodeType.SignificantWhitespace; 490 case XmlNodeType.Whitespace: 491 case XmlNodeType.SignificantWhitespace: 492 if (this.text == null) { 493 if (this.reader.Depth == 0 && this.textIsWhite) { 494 continue; // We should ignore whitespace nodes on root level as XPathDocument does this. 495 } 496 this.text = reader.Value; 497 } 498 else { 499 this.text += reader.Value; 500 } 501 break; 502 case XmlNodeType.EntityReference : 503 Debug.Assert(false, "Entety references whould be resolved for us"); 504 break; 505 case XmlNodeType.DocumentType: 506 case XmlNodeType.XmlDeclaration: 507 break; 508 default: 509 if (this.text != null) { 510 return true; 511 } 512 return this.reader.NodeType != XmlNodeType.EndElement; 513 } 514 }while (true); 515 } 516 517 private static bool IsWhitespace(string text) { 518 for (int i = text.Length - 1; 0 <= i; i --) { 519 if (! XmlCharType.IsWhiteSpace(text[i])) { 520 return false; 521 } 522 } 523 return true; 524 } 525 526 private static XPathNodeType ConvertNodeType(XmlNodeType type) { 527 switch(type) { 528 case XmlNodeType.Element : return XPathNodeType.Element ; 529 case XmlNodeType.EndElement : return XPathNodeType.Element ; 530 case XmlNodeType.Attribute : return XPathNodeType.Attribute ; 531 case XmlNodeType.ProcessingInstruction : return XPathNodeType.ProcessingInstruction; 532 case XmlNodeType.Comment : return XPathNodeType.Comment ; 533 case XmlNodeType.Text : return XPathNodeType.Text ; 534 // case XmlNodeType.Document : return XPathNodeType.Root ; 535 default : 536 Debug.Assert(false, "we should't be on this node"); 537 // We can't be on Text WS, SWS, CDATA -- because we reading them all 538 // We can't be on DocumentFragment, Notation, EntityReference, Entity, DocumentType, DocumentFragment, Notation, 539 // EndElement, EndEntity, XmlDeclaration - we don't know what to do with them on XPath 540 // XsltInput shoud pass them 541 return XPathNodeType.All; 542 } 543 } 544 private static bool IsTextType(XPathNodeType nodeType) { 545 return ( 546 nodeType == XPathNodeType.Text || 547 nodeType == XPathNodeType.Whitespace || 548 nodeType == XPathNodeType.SignificantWhitespace 549 ); 550 } 551 552 // -------------------- Keywords testing -------------------- 553 554 public bool IsNs(string ns) { return Ref.Equal(ns, this.NamespaceUri); } 555 public bool IsKeyword(string kwd) { return Ref.Equal(kwd, this.LocalName); } 556 public bool IsXsltNamespace() { return IsNs(atoms.UrnXsl); } 557 public bool IsNullNamespace() { return IsNs(string.Empty); } 558 public bool IsXsltInstruction(string kwd) { return IsKeyword(kwd) && IsXsltNamespace(); } 559 public bool IsXsltAttribute(string kwd) { return IsKeyword(kwd) && IsNullNamespace(); } 560 public bool IsWithParam() { return IsXsltInstruction(this.Atoms.WithParam); } 561 public bool IsSort() { return IsXsltInstruction(this.Atoms.Sort ); } 562 563 564 // -------------------- Scope Management -------------------- 565 // See private class InputScopeManager bellow. 566 // InputScopeManager handles some flags and values with respect of scope level where they as defined. 567 // To parse XSLT style sheet we need the folloing values: 568 // ForwardCompatibility -- this flag is set when xsl:version!='1.0'. 569 // CanHaveApplyImports -- we allow xsl:apply-templates instruction to apear in any template with match!=null, but not inside xsl:for-each 570 // so it can't be inside global variable and has initial value = false 571 // ExtentionNamespace -- is defined by extension-element-prefixes attribute on LRE or xsl:stylesheet 572 573 public bool CanHaveApplyImports { 574 get { return this.scopeManager.CanHaveApplyImports; } 575 set { this.scopeManager.CanHaveApplyImports = value;} 576 } 577 578 public void AddExtensionNamespace(string uri) { this.scopeManager.AddExtensionNamespace(uri); } 579 public bool IsExtensionNamespace( string uri) { return this.scopeManager.IsExtensionNamespace(uri); } 580 581 public bool ForwardCompatibility { 582 get { return this.scopeManager.ForwardCompatibility; } 583 } 584 585 public void SetVersion(string ver) { 586 // BugBug! -- version shouldn't be NaN ! 587 if (XmlConvert.ToDouble(ver) < 1) { // should be XmlConvert.ToXPathDouble 588 throw new XmlException("InvalidAttrValueKeywords "); 589 } 590 this.scopeManager.ForwardCompatibility = (ver != "1.0"); 591 } 592 593 [System.Diagnostics.Conditional("DEBUG")] 594 private void CompareInputs(string uri) { 595 XsltInput navInput = new XsltInput((new System.Xml.XPath.XPathDocument(uri, XmlSpace.Preserve)).CreateNavigator()); 596 XsltInput rdrInput = new XsltInput(new XmlTextReader(uri)); 597 // Test all nodes on the root level. It can be comments, PIs and one element node. 598 bool navMove, rdrMove; 599 do { 600 CompareNodes(navInput, rdrInput); 601 navMove = navInput.MoveToNextSibling(); 602 rdrMove = rdrInput.MoveToNextSibling(); 603 Debug.Assert(navMove == rdrMove); 604 }while(navMove); 605 } 606 607 [System.Diagnostics.Conditional("DEBUG")] 608 private void CompareNodes(XsltInput navInput, XsltInput rdrInput) { 609 XPathNodeType navNodeType = navInput.NodeType ; 610 XPathNodeType rdrNodeType = rdrInput.NodeType ; 611 string navValue = navInput.Value ; 612 string rdrValue = rdrInput.Value ; 613 string navLocalName = navInput.LocalName ; 614 string rdrLocalName = rdrInput.LocalName ; 615 string navNamespaceUri = navInput.NamespaceUri; 616 string rdrNamespaceUri = rdrInput.NamespaceUri; 617 int navDepth = navInput.Depth ; 618 int rdrDepth = rdrInput.Depth ; 619 Debug.Assert(navNodeType == rdrNodeType ); 620 Debug.Assert(navLocalName == rdrLocalName ); 621 Debug.Assert(navNamespaceUri == rdrNamespaceUri); 622 Debug.Assert(navValue == rdrValue ); 623 Debug.Assert(navDepth == rdrDepth ); 624 if (IsTextType(navNodeType)) { 625 return; // Text is syntetic node for reader breanch. Nothing else is available 626 } 627 string navPrefix = navInput.Prefix ; 628 string rdrPrefix = rdrInput.Prefix ; 629 string navBaseUri = navInput.BaseUri ; 630 string rdrBaseUri = rdrInput.BaseUri ; 631 bool navIsEmptyElement = navInput.IsEmptyElement; 632 bool rdrIsEmptyElement = rdrInput.IsEmptyElement; 633 Debug.Assert(navIsEmptyElement == rdrIsEmptyElement); 634 Debug.Assert(navPrefix == rdrPrefix ); 635 Debug.Assert(navBaseUri == rdrBaseUri ); 636 if (navInput.NodeType == XPathNodeType.Element) { 637 bool navMove, rdrMove; 638 /* Compare Namespases (local)*/ { 639 // reader has oposit order of namespaces that navigator. 640 rdrMove = rdrInput.MoveToFirstNamespace(); 641 navMove = navInput.MoveToFirstNamespace(); 642 Debug.Assert(navMove == rdrMove); 643 if (navMove) { 644 ArrayList names = new ArrayList(); 645 ArrayList nss = new ArrayList(); 646 do { 647 names.Add(rdrInput.LocalName); 648 nss .Add(rdrInput.Value ); 649 rdrMove = rdrInput.MoveToNextNamespace(); 650 }while(rdrMove); 651 int count; 652 for (count = names.Count - 1; 0 <= count; -- count) { 653 Debug.Assert(navMove); 654 string name = (string) names[count]; 655 string ns = (string) nss [count]; 656 Debug.Assert(name == navInput.LocalName); 657 Debug.Assert(ns == navInput.Value ); 658 navMove = navInput.MoveToNextNamespace(); 659 } 660 Debug.Assert(-1 == count, "all namespaces from reader was counted"); 661 Debug.Assert(! navMove, "all namespaces from navigator was passed"); 662 navInput.MoveToElement(); 663 rdrInput.MoveToElement(); 664 } 665 } 666 /* Compare attributes */ { 667 navMove = navInput.MoveToFirstAttribute(); 668 rdrMove = rdrInput.MoveToFirstAttribute(); 669 Debug.Assert(navMove == rdrMove); 670 if (navMove) { 671 do { 672 CompareNodes(navInput, rdrInput); 673 navMove = navInput.MoveToNextAttribute(); 674 rdrMove = rdrInput.MoveToNextAttribute(); 675 Debug.Assert(navMove == rdrMove); 676 }while(navMove); 677 navInput.MoveToElement(); 678 rdrInput.MoveToElement(); 679 } 680 } 681 /* Compare Children */ { 682 navMove = navInput.MoveToFirstChild(); 683 rdrMove = rdrInput.MoveToFirstChild(); 684 Debug.Assert(navMove == rdrMove); 685 if (navMove) { 686 do { 687 CompareNodes(navInput, rdrInput); 688 navMove = navInput.MoveToNextSibling(); 689 rdrMove = rdrInput.MoveToNextSibling(); 690 Debug.Assert(navMove == rdrMove); 691 }while(navMove); 692 navInput.MoveToParent(); 693 rdrInput.MoveToParent(); 694 } 695 } 696 } 697 } 698 699 // --------------- GetAttributes(...) ------------------------- 700 // All Xslt Instructions allows fixed set of attributes in null-ns, no in XSLT-ns and any in other ns. 701 // In ForwardCompatibility mode we should ignore any of this problems. 702 // We not use these functions for parseing LiteralResultElement and xsl:stylesheet 703 704 private string[] names = new string[11]; 705 private string[] values = new string[11]; 706 707 public ContextInfo GetAttributes() { 708 return GetAttributes(0, 0, names, values); 709 } 710 711 public ContextInfo GetAttributes(int required, string name, out string value) { 712 names[0] = name; 713 ContextInfo ctxInfo = GetAttributes(required, 1, names, values); 714 value = values[0]; 715 return ctxInfo; 716 } 717 718 public ContextInfo GetAttributes(int required, string name0, out string value0, string name1, out string value1) { 719 names[0] = name0; 720 names[1] = name1; 721 ContextInfo ctxInfo = GetAttributes(required, 2, names, values); 722 value0 = values[0]; 723 value1 = values[1]; 724 return ctxInfo; 725 } 726 727 public ContextInfo GetAttributes(int required, 728 string name0, out string value0, 729 string name1, out string value1, 730 string name2, out string value2 731 ) { 732 names[0] = name0; 733 names[1] = name1; 734 names[2] = name2; 735 ContextInfo ctxInfo = GetAttributes(required, 3, names, values); 736 value0 = values[0]; 737 value1 = values[1]; 738 value2 = values[2]; 739 return ctxInfo; 740 } 741 742 public ContextInfo GetAttributes(int required, 743 string name0, out string value0, 744 string name1, out string value1, 745 string name2, out string value2, 746 string name3, out string value3 747 ) { 748 names[0] = name0; 749 names[1] = name1; 750 names[2] = name2; 751 names[3] = name3; 752 ContextInfo ctxInfo = GetAttributes(required, 4, names, values); 753 value0 = values[0]; 754 value1 = values[1]; 755 value2 = values[2]; 756 value3 = values[3]; 757 return ctxInfo; 758 } 759 760 public ContextInfo GetAttributes(int required, 761 string name0, out string value0, 762 string name1, out string value1, 763 string name2, out string value2, 764 string name3, out string value3, 765 string name4, out string value4 766 ) { 767 names[0] = name0; 768 names[1] = name1; 769 names[2] = name2; 770 names[3] = name3; 771 names[4] = name4; 772 ContextInfo ctxInfo = GetAttributes(required, 4, names, values); 773 value0 = values[0]; 774 value1 = values[1]; 775 value2 = values[2]; 776 value3 = values[3]; 777 value4 = values[4]; 778 return ctxInfo; 779 } 780 781 public ContextInfo GetAttributes(int required, 782 string name0, out string value0, 783 string name1, out string value1, 784 string name2, out string value2, 785 string name3, out string value3, 786 string name4, out string value4, 787 string name5, out string value5, 788 string name6, out string value6, 789 string name7, out string value7, 790 string name8, out string value8 791 ) { 792 names[0] = name0; 793 names[1] = name1; 794 names[2] = name2; 795 names[3] = name3; 796 names[4] = name4; 797 names[5] = name5; 798 names[6] = name6; 799 names[7] = name7; 800 names[8] = name8; 801 ContextInfo ctxInfo = GetAttributes(required, 9, names, values); 802 value0 = values[0]; 803 value1 = values[1]; 804 value2 = values[2]; 805 value3 = values[3]; 806 value4 = values[4]; 807 value5 = values[5]; 808 value6 = values[6]; 809 value7 = values[7]; 810 value8 = values[8]; 811 return ctxInfo; 812 } 813 814 public ContextInfo GetAttributes(int required, 815 string name0, out string value0, 816 string name1, out string value1, 817 string name2, out string value2, 818 string name3, out string value3, 819 string name4, out string value4, 820 string name5, out string value5, 821 string name6, out string value6, 822 string name7, out string value7, 823 string name8, out string value8, 824 string name9, out string value9 825 ) { 826 names[0] = name0; 827 names[1] = name1; 828 names[2] = name2; 829 names[3] = name3; 830 names[4] = name4; 831 names[5] = name5; 832 names[6] = name6; 833 names[7] = name7; 834 names[8] = name8; 835 names[9] = name9; 836 ContextInfo ctxInfo = GetAttributes(required, 10, names, values); 837 value0 = values[0]; 838 value1 = values[1]; 839 value2 = values[2]; 840 value3 = values[3]; 841 value4 = values[4]; 842 value5 = values[5]; 843 value6 = values[6]; 844 value7 = values[7]; 845 value8 = values[8]; 846 value9 = values[9]; 847 return ctxInfo; 848 } 849 850 public ContextInfo GetAttributes(int required, 851 string name0, out string value0, 852 string name1, out string value1, 853 string name2, out string value2, 854 string name3, out string value3, 855 string name4, out string value4, 856 string name5, out string value5, 857 string name6, out string value6, 858 string name7, out string value7, 859 string name8, out string value8, 860 string name9, out string value9, 861 string name10, out string value10 862 ) { 863 names[0] = name0; 864 names[1] = name1; 865 names[2] = name2; 866 names[3] = name3; 867 names[4] = name4; 868 names[5] = name5; 869 names[6] = name6; 870 names[7] = name7; 871 names[8] = name8; 872 names[9] = name9; 873 names[10] = name10; 874 ContextInfo ctxInfo = GetAttributes(required, 11, names, values); 875 value0 = values[0]; 876 value1 = values[1]; 877 value2 = values[2]; 878 value3 = values[3]; 879 value4 = values[4]; 880 value5 = values[5]; 881 value6 = values[6]; 882 value7 = values[7]; 883 value8 = values[8]; 884 value9 = values[9]; 885 value10 = values[10]; 886 return ctxInfo; 887 } 888 889 private ContextInfo GetAttributes(int required, int number, string[] names, string[] values) { 890 for(int i = 0; i < number; i ++) { 891 values[i] = null; 892 } 893 string unknownAttribute = null; 894 ContextInfo ctxInfo = new ContextInfo(this); 895 if (this.MoveToFirstAttribute()) { 896 do { 897 ctxInfo.AddAttribute(this); 898 bool found = false; 899 for(int i = 0; i < number; i ++) { 900 if (this.IsXsltAttribute(names[i])) { 901 found = true; 902 values[i] = this.Value; 903 if (Ref.Equal(names[i], Atoms.Version)) { 904 this.SetVersion(this.Value); 905 } 906 break; 907 } 908 } 909 if (! found && (this.IsNullNamespace() || this.IsXsltNamespace())) { 910 unknownAttribute = this.LocalName; 911 } 912 } while(this.MoveToNextAttribute()); 913 this.MoveToElement(); 914 if (unknownAttribute != null && ! this.scopeManager.ForwardCompatibility) { 915 throw new XmlException("Element has an attribute that XSLT 1.0 does not allow the element to have"); 916 } 917 } 918 ctxInfo.Finish(this); 919 for (int i = 0; i < required; i ++) { 920 if (values[i] == null) { 921 throw new XmlException("SR.Xslt_MissingAttribute names[i]"); 922 } 923 } 924 return ctxInfo; 925 } 926 927 public ISourceLineInfo BuildLineInfo() { 928 if (this.FileName == null) { 929 return null; 930 } 931 return new SourceLineInfo((ISourceLineInfo) this); 932 } 933 934 public NsDecl BuildNamespaceList() { 935 // Build namespace list of current node: 936 NsDecl last = null; 937 if (this.MoveToFirstNamespace()) { 938 do { 939 last = new NsDecl(last, this.LocalName, this.Value); 940 }while(this.MoveToNextNamespace()); 941 this.MoveToElement(); 942 } 943 return last; 944 } 945 946 // ----------------------- ISourceLineInfo ----------------------- 947 public string FileName { get { return this.reader != null ? this.reader.BaseURI : null; } } 948 public int StartLine { get { return this.reader != null ? ((IXmlLineInfo)this.reader).LineNumber : -1; } } 949 public int StartPos { get { return this.reader != null ? ((IXmlLineInfo)this.reader).LinePosition : -1; } } 950 public int EndLine { get { return this.StartLine ; } } 951 public int EndPos { get { return this.StartPos + this.GetNameSize(); } } 952 953 private int GetNameSize() { 954 int nameSize = this.LocalName.Length; 955 string prefix = this.Prefix; 956 if (prefix.Length != 0) { 957 return prefix.Length + 1 + nameSize; // + ':' 958 } 959 return nameSize; 960 } 961 962 private class InputScopeManager { 963 private enum ScopeFlags { 964 ForwardCompatibility = 0x1, 965 CanHaveApplyImports = 0x2, 966 NsExtension = 0x4, 967 968 InheretedFlags = ForwardCompatibility | CanHaveApplyImports, 969 } 970 private struct ScopeReord { 971 public int scopeCount; 972 public ScopeFlags scopeFlags; 973 public string nsUri; 974 } 975 976 XmlNameTable nameTable; 977 ScopeReord[] records = new ScopeReord[32]; 978 int lastRecord = 0; 979 int lastScopes = 0; // This is cash of records[lastRecord].scopeCount field; 980 // most often we will have PushScope()/PopScope pare over the same record. 981 // It has sence to avoid adresing this field through array access. 982 983 public InputScopeManager(XmlNameTable nameTable) { 984 this.nameTable = nameTable; 985 records[0].scopeFlags = 0; 986 PushScope(); 987 } 988 989 public void PushScope() { 990 lastScopes ++; 991 } 992 993 public void PopScope() { 994 if (0 < lastScopes) { 995 lastScopes --; 996 } 997 else { 998 while(records[-- lastRecord].scopeCount == 0) ; 999 lastScopes = records[lastRecord].scopeCount; 1000 lastScopes --; 1001 } 1002 } 1003 1004 private void AddRecord() { 1005 records[lastRecord].scopeCount = lastScopes; 1006 lastRecord ++; 1007 if (lastRecord == records.Length) { 1008 ScopeReord[] newRecords = new ScopeReord[lastRecord * 2]; 1009 Array.Copy(records, 0, newRecords, 0, lastRecord); 1010 records = newRecords; 1011 } 1012 lastScopes = 0; 1013 } 1014 1015 private void SetFlag(bool value, ScopeFlags flag) { 1016 Debug.Assert(flag == ScopeFlags.ForwardCompatibility || flag == ScopeFlags.CanHaveApplyImports); 1017 ScopeFlags lastFlags = records[lastRecord].scopeFlags; 1018 // lastScopes == records[lastRecord].scopeCount; // we know this because we are cashing it. 1019 bool canReuseLastRecord = lastScopes == 0; // last record is from last scope 1020 if (! canReuseLastRecord) { 1021 AddRecord(); 1022 lastFlags = lastFlags & ScopeFlags.InheretedFlags; 1023 } 1024 records[lastRecord].scopeFlags = value ? (lastFlags | flag) : (lastFlags & ~flag); 1025 } 1026 1027 public bool ForwardCompatibility { 1028 get { return (records[lastRecord].scopeFlags & ScopeFlags.ForwardCompatibility) != 0; } 1029 set { SetFlag(value, ScopeFlags.ForwardCompatibility) ;} 1030 } 1031 1032 public bool CanHaveApplyImports { 1033 get { return (records[lastRecord].scopeFlags & ScopeFlags.CanHaveApplyImports) != 0; } 1034 set { SetFlag(value, ScopeFlags.CanHaveApplyImports) ;} 1035 } 1036 1037 public void AddExtensionNamespace(string uri) { 1038 Debug.Assert(uri != null); 1039 1040 uri = nameTable.Add(uri); 1041 1042 ScopeFlags lastFlags = records[lastRecord].scopeFlags; 1043 // lastScopes == records[lastRecord].scopeCount; // we know this because we are cashing it. 1044 bool canReuseLastRecord = ( 1045 lastScopes == 0 && // last record is from last scope 1046 (lastFlags & ScopeFlags.NsExtension) == 0 // only flag fields are used in this record 1047 ); 1048 if (! canReuseLastRecord) { 1049 AddRecord(); 1050 lastFlags = lastFlags & ScopeFlags.InheretedFlags; 1051 } 1052 records[lastRecord].nsUri = uri; 1053 records[lastRecord].scopeFlags = lastFlags | ScopeFlags.NsExtension; 1054 } 1055 1056 public bool IsExtensionNamespace(string nsUri) { 1057 for (int record = this.lastRecord; 0 <= record; record --) { 1058 if ( 1059 (records[record].scopeFlags & ScopeFlags.NsExtension) != 0 && 1060 (records[record].nsUri == nsUri) 1061 ) { 1062 return true; 1063 } 1064 } 1065 return false; 1066 } 1067 } 1068 1069 // -------------------------------- ContextInfo ------------------------------------ 1070 1071#if ! NO_Qil_COMMENTS 1072 StringBuilder dbgCommentBuilder = new StringBuilder(); 1073#endif 1074 1075 public struct ContextInfo { 1076 public NsDecl nsList; 1077 public string dbgComment; 1078 public ISourceLineInfo lineInfo ; 1079 1080 public ContextInfo(XsltInput input) { 1081 this.nsList = input.BuildNamespaceList(); 1082 this.lineInfo = input.BuildLineInfo(); 1083#if ! NO_Qil_COMMENTS 1084 this.dbgComment = null; 1085 Debug.Assert(input.dbgCommentBuilder.Length == 0); 1086 input.dbgCommentBuilder.Append("<"); 1087 if (input.Prefix.Length != 0) { 1088 input.dbgCommentBuilder.Append(input.Prefix); 1089 input.dbgCommentBuilder.Append(":"); 1090 } 1091 input.dbgCommentBuilder.Append(input.LocalName); 1092#endif 1093 } 1094 1095 public void AddAttribute(XsltInput input) { 1096#if ! NO_Qil_COMMENTS 1097 input.dbgCommentBuilder.Append(" "); 1098 if (input.Prefix.Length != 0) { 1099 input.dbgCommentBuilder.Append(input.Prefix); 1100 input.dbgCommentBuilder.Append(":"); 1101 } 1102 input.dbgCommentBuilder.Append(input.LocalName); 1103 input.dbgCommentBuilder.Append("=\""); 1104 input.dbgCommentBuilder.Append(input.Value); 1105 input.dbgCommentBuilder.Append("\""); 1106#endif 1107 } 1108 1109 public void Finish(XsltInput input) { 1110#if ! NO_Qil_COMMENTS 1111 input.dbgCommentBuilder.Append(input.IsEmptyElement ? "/>" : ">..."); 1112 this.dbgComment = input.dbgCommentBuilder.ToString(); 1113 input.dbgCommentBuilder.Length = 0; 1114#endif 1115 } 1116 } 1117 } 1118} 1119