Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / IlGen / XmlILConstructAnalyzer.cs
blob26daac8b9ab535f8d2f5d309f3794d27a779fefc
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlILConstructAnalyzer.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7 using System;
8 using System.Xml;
9 using System.Xml.Schema;
10 using System.Xml.XPath;
11 using System.Diagnostics;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Collections.Specialized;
15 using System.Xml.Xsl.Qil;
17 namespace System.Xml.Xsl.IlGen {
20 /// <summary>
21 /// Until run-time, the exact xml state cannot always be determined. However, the construction analyzer
22 /// keeps track of the set of possible xml states at each node in order to reduce run-time state management.
23 /// </summary>
24 internal enum PossibleXmlStates {
25 None = 0,
26 WithinSequence,
27 EnumAttrs,
28 WithinContent,
29 WithinAttr,
30 WithinComment,
31 WithinPI,
32 Any,
36 /// <summary>
37 /// 1. Some expressions are lazily materialized by creating an iterator over the results (ex. LiteralString, Content).
38 /// 2. Some expressions are incrementally constructed by a Writer (ex. ElementCtor, XsltCopy).
39 /// 3. Some expressions can be iterated or written (ex. List).
40 /// </summary>
41 internal enum XmlILConstructMethod {
42 Iterator, // Construct iterator over expression's results
43 Writer, // Construct expression through calls to Writer
44 WriterThenIterator, // Construct expression through calls to caching Writer; then construct iterator over cached results
45 IteratorThenWriter, // Iterate over expression's results and send each item to Writer
49 /// <summary>
50 /// Every node is annotated with information about how it will be constructed by ILGen.
51 /// </summary>
52 internal class XmlILConstructInfo : IQilAnnotation {
53 private QilNodeType nodeType;
54 private PossibleXmlStates xstatesInitial, xstatesFinal, xstatesBeginLoop, xstatesEndLoop;
55 private bool isNmspInScope, mightHaveNmsp, mightHaveAttrs, mightHaveDupAttrs, mightHaveNmspAfterAttrs;
56 private XmlILConstructMethod constrMeth;
57 private XmlILConstructInfo parentInfo;
58 private ArrayList callersInfo;
59 private bool isReadOnly;
61 private static volatile XmlILConstructInfo Default;
63 /// <summary>
64 /// Get ConstructInfo annotation for the specified node. Lazily create if necessary.
65 /// </summary>
66 public static XmlILConstructInfo Read(QilNode nd) {
67 XmlILAnnotation ann = nd.Annotation as XmlILAnnotation;
68 XmlILConstructInfo constrInfo = (ann != null) ? ann.ConstructInfo : null;
70 if (constrInfo == null) {
71 if (Default == null) {
72 constrInfo = new XmlILConstructInfo(QilNodeType.Unknown);
73 constrInfo.isReadOnly = true;
75 Default = constrInfo;
77 else {
78 constrInfo = Default;
82 return constrInfo;
85 /// <summary>
86 /// Create and initialize XmlILConstructInfo annotation for the specified node.
87 /// </summary>
88 public static XmlILConstructInfo Write(QilNode nd) {
89 XmlILAnnotation ann = XmlILAnnotation.Write(nd);
90 XmlILConstructInfo constrInfo = ann.ConstructInfo;
92 if (constrInfo == null || constrInfo.isReadOnly) {
93 constrInfo = new XmlILConstructInfo(nd.NodeType);
94 ann.ConstructInfo = constrInfo;
97 return constrInfo;
100 /// <summary>
101 /// Default to worst possible construction information.
102 /// </summary>
103 private XmlILConstructInfo(QilNodeType nodeType) {
104 this.nodeType = nodeType;
105 this.xstatesInitial = this.xstatesFinal = PossibleXmlStates.Any;
106 this.xstatesBeginLoop = this.xstatesEndLoop = PossibleXmlStates.None;
107 this.isNmspInScope = false;
108 this.mightHaveNmsp = true;
109 this.mightHaveAttrs = true;
110 this.mightHaveDupAttrs = true;
111 this.mightHaveNmspAfterAttrs = true;
112 this.constrMeth = XmlILConstructMethod.Iterator;
113 this.parentInfo = null;
116 /// <summary>
117 /// Xml states that are possible as construction of the annotated expression begins.
118 /// </summary>
119 public PossibleXmlStates InitialStates {
120 get { return this.xstatesInitial; }
121 set {
122 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
123 this.xstatesInitial = value;
127 /// <summary>
128 /// Xml states that are possible as construction of the annotated expression ends.
129 /// </summary>
130 public PossibleXmlStates FinalStates {
131 get { return this.xstatesFinal; }
132 set {
133 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
134 this.xstatesFinal = value;
138 /// <summary>
139 /// Xml states that are possible as looping begins. This is None if the annotated expression does not loop.
140 /// </summary>
141 public PossibleXmlStates BeginLoopStates {
142 //get { return this.xstatesBeginLoop; }
143 set {
144 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
145 this.xstatesBeginLoop = value;
149 /// <summary>
150 /// Xml states that are possible as looping ends. This is None if the annotated expression does not loop.
151 /// </summary>
152 public PossibleXmlStates EndLoopStates {
153 //get { return this.xstatesEndLoop; }
154 set {
155 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
156 this.xstatesEndLoop = value;
160 /// <summary>
161 /// Return the method that will be used to construct the annotated node.
162 /// </summary>
163 public XmlILConstructMethod ConstructMethod {
164 get { return this.constrMeth; }
165 set {
166 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
167 this.constrMeth = value;
171 /// <summary>
172 /// Returns true if construction method is Writer or WriterThenIterator.
173 /// </summary>
174 public bool PushToWriterFirst {
175 get { return this.constrMeth == XmlILConstructMethod.Writer || this.constrMeth == XmlILConstructMethod.WriterThenIterator; }
176 set {
177 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
178 Debug.Assert(value);
180 switch (this.constrMeth) {
181 case XmlILConstructMethod.Iterator:
182 this.constrMeth = XmlILConstructMethod.WriterThenIterator;
183 break;
185 case XmlILConstructMethod.IteratorThenWriter:
186 this.constrMeth = XmlILConstructMethod.Writer;
187 break;
192 /// <summary>
193 /// Returns true if construction method is Writer or IteratorThenWriter.
194 /// </summary>
195 public bool PushToWriterLast {
196 get { return this.constrMeth == XmlILConstructMethod.Writer || this.constrMeth == XmlILConstructMethod.IteratorThenWriter; }
197 set {
198 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
199 Debug.Assert(value);
201 switch (this.constrMeth) {
202 case XmlILConstructMethod.Iterator:
203 this.constrMeth = XmlILConstructMethod.IteratorThenWriter;
204 break;
206 case XmlILConstructMethod.WriterThenIterator:
207 this.constrMeth = XmlILConstructMethod.Writer;
208 break;
213 /// <summary>
214 /// Returns true if construction method is IteratorThenWriter or Iterator.
215 /// </summary>
216 public bool PullFromIteratorFirst {
217 get { return this.constrMeth == XmlILConstructMethod.IteratorThenWriter || this.constrMeth == XmlILConstructMethod.Iterator; }
218 set {
219 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
220 Debug.Assert(value);
222 switch (this.constrMeth) {
223 case XmlILConstructMethod.Writer:
224 this.constrMeth = XmlILConstructMethod.IteratorThenWriter;
225 break;
227 case XmlILConstructMethod.WriterThenIterator:
228 this.constrMeth = XmlILConstructMethod.Iterator;
229 break;
234 /// <summary>
235 /// If the annotated expression will be constructed as the content of another constructor, and this can be
236 /// guaranteed at compile-time, then this property will be the non-null XmlILConstructInfo of that constructor.
237 /// </summary>
238 public XmlILConstructInfo ParentInfo {
239 //get { return this.parentInfo; }
240 set {
241 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
242 this.parentInfo = value;
246 /// <summary>
247 /// If the annotated expression will be constructed as the content of an ElementCtor, and this can be
248 /// guaranteed at compile-time, then this property will be the non-null XmlILConstructInfo of that constructor.
249 /// </summary>
250 public XmlILConstructInfo ParentElementInfo {
251 get {
252 if (this.parentInfo != null && this.parentInfo.nodeType == QilNodeType.ElementCtor)
253 return this.parentInfo;
255 return null;
259 /// <summary>
260 /// This annotation is only applicable to NamespaceDecl nodes and to ElementCtor and AttributeCtor nodes with
261 /// literal names. If the namespace is already guaranteed to be constructed, then this property will be true.
262 /// </summary>
263 public bool IsNamespaceInScope {
264 get { return this.isNmspInScope; }
265 set {
266 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
267 this.isNmspInScope = value;
271 /// <summary>
272 /// This annotation is only applicable to ElementCtor nodes. If the element might have local namespaces
273 /// added to it at runtime, then this property will be true.
274 /// </summary>
275 public bool MightHaveNamespaces {
276 get { return this.mightHaveNmsp; }
277 set {
278 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
279 this.mightHaveNmsp = value;
283 /// <summary>
284 /// This annotation is only applicable to ElementCtor nodes. If the element might have namespaces added to it after
285 /// attributes have already been added, then this property will be true.
286 /// </summary>
287 public bool MightHaveNamespacesAfterAttributes {
288 get { return this.mightHaveNmspAfterAttrs; }
289 set {
290 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
291 this.mightHaveNmspAfterAttrs = value;
295 /// <summary>
296 /// This annotation is only applicable to ElementCtor nodes. If the element might have attributes added to it at
297 /// runtime, then this property will be true.
298 /// </summary>
299 public bool MightHaveAttributes {
300 get { return this.mightHaveAttrs; }
301 set {
302 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
303 this.mightHaveAttrs = value;
307 /// <summary>
308 /// This annotation is only applicable to ElementCtor nodes. If the element might have multiple attributes added to
309 /// it with the same name, then this property will be true.
310 /// </summary>
311 public bool MightHaveDuplicateAttributes {
312 get { return this.mightHaveDupAttrs; }
313 set {
314 Debug.Assert(!this.isReadOnly, "This XmlILConstructInfo instance is read-only.");
315 this.mightHaveDupAttrs = value;
319 /// <summary>
320 /// This annotation is only applicable to Function nodes. It contains a list of XmlILConstructInfo annontations
321 /// for all QilInvoke nodes which call the annotated function.
322 /// </summary>
323 public ArrayList CallersInfo {
324 get {
325 if (this.callersInfo == null)
326 this.callersInfo = new ArrayList();
328 return this.callersInfo;
332 /// <summary>
333 /// Return name of this annotation.
334 /// </summary>
335 public virtual string Name {
336 get { return "ConstructInfo"; }
339 /// <summary>
340 /// Return string representation of this annotation.
341 /// </summary>
342 public override string ToString() {
343 string s = "";
345 if (this.constrMeth != XmlILConstructMethod.Iterator) {
346 s += this.constrMeth.ToString();
348 s += ", " + this.xstatesInitial;
350 if (this.xstatesBeginLoop != PossibleXmlStates.None) {
351 s += " => " + this.xstatesBeginLoop.ToString() + " => " + this.xstatesEndLoop.ToString();
354 s += " => " + this.xstatesFinal;
356 if (!MightHaveAttributes)
357 s += ", NoAttrs";
359 if (!MightHaveDuplicateAttributes)
360 s += ", NoDupAttrs";
362 if (!MightHaveNamespaces)
363 s += ", NoNmsp";
365 if (!MightHaveNamespacesAfterAttributes)
366 s += ", NoNmspAfterAttrs";
369 return s;
374 /// <summary>
375 /// Scans the content of an constructor and tries to minimize the number of well-formed checks that will have
376 /// to be made at runtime when constructing content.
377 /// </summary>
378 internal class XmlILStateAnalyzer {
379 protected XmlILConstructInfo parentInfo;
380 protected QilFactory fac;
381 protected PossibleXmlStates xstates;
382 protected bool withinElem;
384 /// <summary>
385 /// Constructor.
386 /// </summary>
387 public XmlILStateAnalyzer(QilFactory fac) {
388 this.fac = fac;
391 /// <summary>
392 /// Perform analysis on the specified constructor and its content. Return the ndContent that was passed in,
393 /// or a replacement.
394 /// </summary>
395 public virtual QilNode Analyze(QilNode ndConstr, QilNode ndContent) {
396 if (ndConstr == null) {
397 // Root expression is analyzed
398 this.parentInfo = null;
399 this.xstates = PossibleXmlStates.WithinSequence;
400 this.withinElem = false;
402 Debug.Assert(ndContent != null);
403 ndContent = AnalyzeContent(ndContent);
405 else {
406 this.parentInfo = XmlILConstructInfo.Write(ndConstr);
408 if (ndConstr.NodeType == QilNodeType.Function) {
409 // Results of function should be pushed to writer
410 this.parentInfo.ConstructMethod = XmlILConstructMethod.Writer;
412 // Start with PossibleXmlStates.None and then add additional possible starting states
413 PossibleXmlStates xstates = PossibleXmlStates.None;
414 foreach (XmlILConstructInfo infoCaller in this.parentInfo.CallersInfo) {
415 if (xstates == PossibleXmlStates.None) {
416 xstates = infoCaller.InitialStates;
418 else if (xstates != infoCaller.InitialStates) {
419 xstates = PossibleXmlStates.Any;
422 // Function's results are pushed to Writer, so make sure that Invoke nodes' construct methods match
423 infoCaller.PushToWriterFirst = true;
425 this.parentInfo.InitialStates = xstates;
427 else {
428 // Build a standalone tree, with this constructor as its root
429 if (ndConstr.NodeType != QilNodeType.Choice)
430 this.parentInfo.InitialStates = this.parentInfo.FinalStates = PossibleXmlStates.WithinSequence;
432 // Don't stream Rtf; fully cache the Rtf and copy it into any containing tree in order to simplify XmlILVisitor.VisitRtfCtor
433 if (ndConstr.NodeType != QilNodeType.RtfCtor)
434 this.parentInfo.ConstructMethod = XmlILConstructMethod.WriterThenIterator;
437 // Set withinElem = true if analyzing element content
438 this.withinElem = (ndConstr.NodeType == QilNodeType.ElementCtor);
440 switch (ndConstr.NodeType) {
441 case QilNodeType.DocumentCtor: this.xstates = PossibleXmlStates.WithinContent; break;
442 case QilNodeType.ElementCtor: this.xstates = PossibleXmlStates.EnumAttrs; break;
443 case QilNodeType.AttributeCtor: this.xstates = PossibleXmlStates.WithinAttr; break;
444 case QilNodeType.NamespaceDecl: Debug.Assert(ndContent == null); break;
445 case QilNodeType.TextCtor: Debug.Assert(ndContent == null); break;
446 case QilNodeType.RawTextCtor: Debug.Assert(ndContent == null); break;
447 case QilNodeType.CommentCtor: this.xstates = PossibleXmlStates.WithinComment; break;
448 case QilNodeType.PICtor: this.xstates = PossibleXmlStates.WithinPI; break;
449 case QilNodeType.XsltCopy: this.xstates = PossibleXmlStates.Any; break;
450 case QilNodeType.XsltCopyOf: Debug.Assert(ndContent == null); break;
451 case QilNodeType.Function: this.xstates = this.parentInfo.InitialStates; break;
452 case QilNodeType.RtfCtor: this.xstates = PossibleXmlStates.WithinContent; break;
453 case QilNodeType.Choice: this.xstates = PossibleXmlStates.Any; break;
454 default: Debug.Assert(false, ndConstr.NodeType + " is not handled by XmlILStateAnalyzer."); break;
457 if (ndContent != null)
458 ndContent = AnalyzeContent(ndContent);
460 if (ndConstr.NodeType == QilNodeType.Choice)
461 AnalyzeChoice(ndConstr as QilChoice, this.parentInfo);
463 // Since Function will never be another node's content, set its final states here
464 if (ndConstr.NodeType == QilNodeType.Function)
465 this.parentInfo.FinalStates = this.xstates;
468 return ndContent;
471 /// <summary>
472 /// Recursively analyze content. Return "nd" or a replacement for it.
473 /// </summary>
474 protected virtual QilNode AnalyzeContent(QilNode nd) {
475 XmlILConstructInfo info;
476 QilNode ndChild;
478 // Handle special node-types that are replaced
479 switch (nd.NodeType) {
480 case QilNodeType.For:
481 case QilNodeType.Let:
482 case QilNodeType.Parameter:
483 // Iterator references are shared and cannot be annotated directly with ConstructInfo,
484 // so wrap them with Nop node.
485 nd = this.fac.Nop(nd);
486 break;
489 // Get node's ConstructInfo annotation
490 info = XmlILConstructInfo.Write(nd);
492 // Set node's guaranteed parent constructor
493 info.ParentInfo = this.parentInfo;
495 // Construct all content using the Writer
496 info.PushToWriterLast = true;
498 // Set states that are possible before expression is constructed
499 info.InitialStates = this.xstates;
501 switch (nd.NodeType) {
502 case QilNodeType.Loop: AnalyzeLoop(nd as QilLoop, info); break;
503 case QilNodeType.Sequence: AnalyzeSequence(nd as QilList, info); break;
504 case QilNodeType.Conditional: AnalyzeConditional(nd as QilTernary, info); break;
505 case QilNodeType.Choice: AnalyzeChoice(nd as QilChoice, info); break;
507 case QilNodeType.Error:
508 case QilNodeType.Warning:
509 // Ensure that construct method is Writer
510 info.ConstructMethod = XmlILConstructMethod.Writer;
511 break;
513 case QilNodeType.Nop:
514 ndChild = (nd as QilUnary).Child;
515 switch (ndChild.NodeType) {
516 case QilNodeType.For:
517 case QilNodeType.Let:
518 case QilNodeType.Parameter:
519 // Copy iterator items as content
520 AnalyzeCopy(nd, info);
521 break;
523 default:
524 // Ensure that construct method is Writer and recursively analyze content
525 info.ConstructMethod = XmlILConstructMethod.Writer;
526 AnalyzeContent(ndChild);
527 break;
529 break;
531 default:
532 AnalyzeCopy(nd, info);
533 break;
536 // Set states that are possible after expression is constructed
537 info.FinalStates = this.xstates;
539 return nd;
542 /// <summary>
543 /// Analyze loop.
544 /// </summary>
545 protected virtual void AnalyzeLoop(QilLoop ndLoop, XmlILConstructInfo info) {
546 XmlQueryType typ = ndLoop.XmlType;
548 // Ensure that construct method is Writer
549 info.ConstructMethod = XmlILConstructMethod.Writer;
551 if (!typ.IsSingleton)
552 StartLoop(typ, info);
554 // Body constructs content
555 ndLoop.Body = AnalyzeContent(ndLoop.Body);
557 if (!typ.IsSingleton)
558 EndLoop(typ, info);
561 /// <summary>
562 /// Analyze list.
563 /// </summary>
564 protected virtual void AnalyzeSequence(QilList ndSeq, XmlILConstructInfo info) {
565 // Ensure that construct method is Writer
566 info.ConstructMethod = XmlILConstructMethod.Writer;
568 // Analyze each item in the list
569 for (int idx = 0; idx < ndSeq.Count; idx++)
570 ndSeq[idx] = AnalyzeContent(ndSeq[idx]);
573 /// <summary>
574 /// Analyze conditional.
575 /// </summary>
576 protected virtual void AnalyzeConditional(QilTernary ndCond, XmlILConstructInfo info) {
577 PossibleXmlStates xstatesTrue;
579 // Ensure that construct method is Writer
580 info.ConstructMethod = XmlILConstructMethod.Writer;
582 // Visit true branch; save resulting states
583 ndCond.Center = AnalyzeContent(ndCond.Center);
584 xstatesTrue = this.xstates;
586 // Restore starting states and visit false branch
587 this.xstates = info.InitialStates;
588 ndCond.Right = AnalyzeContent(ndCond.Right);
590 // Conditional ending states consist of combination of true and false branch states
591 if (xstatesTrue != this.xstates)
592 this.xstates = PossibleXmlStates.Any;
595 /// <summary>
596 /// Analyze choice.
597 /// </summary>
598 protected virtual void AnalyzeChoice(QilChoice ndChoice, XmlILConstructInfo info) {
599 PossibleXmlStates xstatesChoice;
600 int idx;
602 // Visit default branch; save resulting states
603 idx = ndChoice.Branches.Count - 1;
604 ndChoice.Branches[idx] = AnalyzeContent(ndChoice.Branches[idx]);
605 xstatesChoice = this.xstates;
607 // Visit all other branches
608 while (--idx >= 0) {
609 // Restore starting states and visit the next branch
610 this.xstates = info.InitialStates;
611 ndChoice.Branches[idx] = AnalyzeContent(ndChoice.Branches[idx]);
613 // Choice ending states consist of combination of all branch states
614 if (xstatesChoice != this.xstates)
615 xstatesChoice = PossibleXmlStates.Any;
618 this.xstates = xstatesChoice;
621 /// <summary>
622 /// Analyze copying items.
623 /// </summary>
624 protected virtual void AnalyzeCopy(QilNode ndCopy, XmlILConstructInfo info) {
625 XmlQueryType typ = ndCopy.XmlType;
627 // Copying item(s) to output involves looping if there is not exactly one item in the sequence
628 if (!typ.IsSingleton)
629 StartLoop(typ, info);
631 // Determine state transitions that may take place
632 if (MaybeContent(typ)) {
633 if (MaybeAttrNmsp(typ)) {
634 // Node might be Attr/Nmsp or non-Attr/Nmsp, so transition from EnumAttrs to WithinContent *may* occur
635 if (this.xstates == PossibleXmlStates.EnumAttrs)
636 this.xstates = PossibleXmlStates.Any;
638 else {
639 // Node is guaranteed not to be Attr/Nmsp, so transition to WithinContent will occur if starting
640 // state is EnumAttrs or if constructing within an element (guaranteed to be in EnumAttrs or WithinContent state)
641 if (this.xstates == PossibleXmlStates.EnumAttrs || this.withinElem)
642 this.xstates = PossibleXmlStates.WithinContent;
646 if (!typ.IsSingleton)
647 EndLoop(typ, info);
650 /// <summary>
651 /// Calculate starting xml states that will result when iterating over and constructing an expression of the specified type.
652 /// </summary>
653 private void StartLoop(XmlQueryType typ, XmlILConstructInfo info) {
654 Debug.Assert(!typ.IsSingleton);
656 // This is tricky, because the looping introduces a feedback loop:
657 // 1. Because loops may be executed many times, the beginning set of states must include the ending set of states.
658 // 2. Because loops may be executed 0 times, the final set of states after all looping is complete must include
659 // the initial set of states.
661 // +-- states-initial
662 // | |
663 // | states-begin-loop <--+
664 // | | |
665 // | +--------------+ |
666 // | | Construction | |
667 // | +--------------+ |
668 // | | |
669 // | states-end-loop ----+
670 // | |
671 // +--> states-final
673 // Save starting loop states
674 info.BeginLoopStates = this.xstates;
676 if (typ.MaybeMany) {
677 // If transition might occur from EnumAttrs to WithinContent, then states-end might be WithinContent, which
678 // means states-begin needs to also include WithinContent.
679 if (this.xstates == PossibleXmlStates.EnumAttrs && MaybeContent(typ))
680 info.BeginLoopStates = this.xstates = PossibleXmlStates.Any;
684 /// <summary>
685 /// Calculate ending xml states that will result when iterating over and constructing an expression of the specified type.
686 /// </summary>
687 private void EndLoop(XmlQueryType typ, XmlILConstructInfo info) {
688 Debug.Assert(!typ.IsSingleton);
690 // Save ending loop states
691 info.EndLoopStates = this.xstates;
693 // If it's possible to loop zero times, then states-final needs to include states-initial
694 if (typ.MaybeEmpty && info.InitialStates != this.xstates)
695 this.xstates = PossibleXmlStates.Any;
698 /// <summary>
699 /// Return true if an instance of the specified type might be an attribute or a namespace node.
700 /// </summary>
701 private bool MaybeAttrNmsp(XmlQueryType typ) {
702 return (typ.NodeKinds & (XmlNodeKindFlags.Attribute | XmlNodeKindFlags.Namespace)) != XmlNodeKindFlags.None;
705 /// <summary>
706 /// Return true if an instance of the specified type might be a non-empty content type (attr/nsmp don't count).
707 /// </summary>
708 private bool MaybeContent(XmlQueryType typ) {
709 return !typ.IsNode || (typ.NodeKinds & ~(XmlNodeKindFlags.Attribute | XmlNodeKindFlags.Namespace)) != XmlNodeKindFlags.None;
714 /// <summary>
715 /// Scans the content of an ElementCtor and tries to minimize the number of well-formed checks that will have
716 /// to be made at runtime when constructing content.
717 /// </summary>
718 internal class XmlILElementAnalyzer : XmlILStateAnalyzer {
719 private NameTable attrNames = new NameTable();
720 private ArrayList dupAttrs = new ArrayList();
722 /// <summary>
723 /// Constructor.
724 /// </summary>
725 public XmlILElementAnalyzer(QilFactory fac) : base(fac) {
728 /// <summary>
729 /// Analyze the content argument of the ElementCtor. Try to eliminate as many runtime checks as possible,
730 /// both for the ElementCtor and for content constructors.
731 /// </summary>
732 public override QilNode Analyze(QilNode ndElem, QilNode ndContent) {
733 Debug.Assert(ndElem.NodeType == QilNodeType.ElementCtor);
734 this.parentInfo = XmlILConstructInfo.Write(ndElem);
736 // Start by assuming that these properties are false (they default to true, but analyzer might be able to
737 // prove they are really false).
738 this.parentInfo.MightHaveNamespacesAfterAttributes = false;
739 this.parentInfo.MightHaveAttributes = false;
740 this.parentInfo.MightHaveDuplicateAttributes = false;
742 // The element's namespace might need to be declared
743 this.parentInfo.MightHaveNamespaces = !this.parentInfo.IsNamespaceInScope;
745 // Clear list of duplicate attributes
746 this.dupAttrs.Clear();
748 return base.Analyze(ndElem, ndContent);
751 /// <summary>
752 /// Analyze loop.
753 /// </summary>
754 protected override void AnalyzeLoop(QilLoop ndLoop, XmlILConstructInfo info) {
755 // Constructing attributes/namespaces in a loop can cause duplicates, namespaces after attributes, etc.
756 if (ndLoop.XmlType.MaybeMany)
757 CheckAttributeNamespaceConstruct(ndLoop.XmlType);
759 base.AnalyzeLoop(ndLoop, info);
762 /// <summary>
763 /// Analyze copying items.
764 /// </summary>
765 protected override void AnalyzeCopy(QilNode ndCopy, XmlILConstructInfo info) {
766 if (ndCopy.NodeType == QilNodeType.AttributeCtor) {
767 AnalyzeAttributeCtor(ndCopy as QilBinary, info);
769 else {
770 CheckAttributeNamespaceConstruct(ndCopy.XmlType);
773 base.AnalyzeCopy(ndCopy, info);
776 /// <summary>
777 /// Analyze attribute constructor.
778 /// </summary>
779 private void AnalyzeAttributeCtor(QilBinary ndAttr, XmlILConstructInfo info) {
780 if (ndAttr.Left.NodeType == QilNodeType.LiteralQName) {
781 QilName ndName = ndAttr.Left as QilName;
782 XmlQualifiedName qname;
783 int idx;
785 // This attribute might be constructed on the parent element
786 this.parentInfo.MightHaveAttributes = true;
788 // Check to see whether this attribute is a duplicate of a previous attribute
789 if (!this.parentInfo.MightHaveDuplicateAttributes) {
790 qname = new XmlQualifiedName(this.attrNames.Add(ndName.LocalName), this.attrNames.Add(ndName.NamespaceUri));
792 for (idx = 0; idx < this.dupAttrs.Count; idx++) {
793 XmlQualifiedName qnameDup = (XmlQualifiedName) this.dupAttrs[idx];
795 if ((object) qnameDup.Name == (object) qname.Name && (object) qnameDup.Namespace == (object) qname.Namespace) {
796 // A duplicate attribute has been encountered
797 this.parentInfo.MightHaveDuplicateAttributes = true;
801 if (idx >= this.dupAttrs.Count) {
802 // This is not a duplicate attribute, so add it to the set
803 this.dupAttrs.Add(qname);
807 // The attribute's namespace might need to be declared
808 if (!info.IsNamespaceInScope)
809 this.parentInfo.MightHaveNamespaces = true;
811 else {
812 // Attribute prefix and namespace are not known at compile-time
813 CheckAttributeNamespaceConstruct(ndAttr.XmlType);
817 /// <summary>
818 /// If type might contain attributes or namespaces, set appropriate parent element flags.
819 /// </summary>
820 private void CheckAttributeNamespaceConstruct(XmlQueryType typ) {
821 // If content might contain attributes,
822 if ((typ.NodeKinds & XmlNodeKindFlags.Attribute) != XmlNodeKindFlags.None) {
823 // Mark element as possibly having attributes and duplicate attributes (since we don't know the names)
824 this.parentInfo.MightHaveAttributes = true;
825 this.parentInfo.MightHaveDuplicateAttributes = true;
827 // Attribute namespaces might be declared
828 this.parentInfo.MightHaveNamespaces = true;
831 // If content might contain namespaces,
832 if ((typ.NodeKinds & XmlNodeKindFlags.Namespace) != XmlNodeKindFlags.None) {
833 // Then element might have namespaces,
834 this.parentInfo.MightHaveNamespaces = true;
836 // If attributes might already have been constructed,
837 if (this.parentInfo.MightHaveAttributes) {
838 // Then attributes might precede namespace declarations
839 this.parentInfo.MightHaveNamespacesAfterAttributes = true;
846 /// <summary>
847 /// Scans constructed content, looking for redundant namespace declarations. If any are found, then they are marked
848 /// and removed later.
849 /// </summary>
850 internal class XmlILNamespaceAnalyzer {
851 private XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
852 private bool addInScopeNmsp;
853 private int cntNmsp;
855 /// <summary>
856 /// Perform scan.
857 /// </summary>
858 public void Analyze(QilNode nd, bool defaultNmspInScope) {
859 this.addInScopeNmsp = false;
860 this.cntNmsp = 0;
862 // If xmlns="" is in-scope, push it onto the namespace stack
863 if (defaultNmspInScope) {
864 this.nsmgr.PushScope();
865 this.nsmgr.AddNamespace(string.Empty, string.Empty);
866 this.cntNmsp++;
869 AnalyzeContent(nd);
871 if (defaultNmspInScope)
872 this.nsmgr.PopScope();
875 /// <summary>
876 /// Recursively analyze content. Return "nd" or a replacement for it.
877 /// </summary>
878 private void AnalyzeContent(QilNode nd) {
879 int cntNmspSave;
881 switch (nd.NodeType) {
882 case QilNodeType.Loop:
883 this.addInScopeNmsp = false;
884 AnalyzeContent((nd as QilLoop).Body);
885 break;
887 case QilNodeType.Sequence:
888 foreach (QilNode ndContent in nd)
889 AnalyzeContent(ndContent);
890 break;
892 case QilNodeType.Conditional:
893 this.addInScopeNmsp = false;
894 AnalyzeContent((nd as QilTernary).Center);
895 AnalyzeContent((nd as QilTernary).Right);
896 break;
898 case QilNodeType.Choice:
899 this.addInScopeNmsp = false;
900 QilList ndBranches = (nd as QilChoice).Branches;
901 for (int idx = 0; idx < ndBranches.Count; idx++)
902 AnalyzeContent(ndBranches[idx]);
904 break;
906 case QilNodeType.ElementCtor:
907 // Start a new namespace scope
908 this.addInScopeNmsp = true;
909 this.nsmgr.PushScope();
910 cntNmspSave = this.cntNmsp;
912 if (CheckNamespaceInScope(nd as QilBinary))
913 AnalyzeContent((nd as QilBinary).Right);
915 this.nsmgr.PopScope();
916 this.addInScopeNmsp = false;
917 this.cntNmsp = cntNmspSave;
918 break;
920 case QilNodeType.AttributeCtor:
921 this.addInScopeNmsp = false;
922 CheckNamespaceInScope(nd as QilBinary);
923 break;
925 case QilNodeType.NamespaceDecl:
926 CheckNamespaceInScope(nd as QilBinary);
927 break;
929 case QilNodeType.Nop:
930 AnalyzeContent((nd as QilUnary).Child);
931 break;
933 default:
934 this.addInScopeNmsp = false;
935 break;
939 /// <summary>
940 /// Determine whether an ElementCtor, AttributeCtor, or NamespaceDecl's namespace is already declared. If it is,
941 /// set the IsNamespaceInScope property to True. Otherwise, add the namespace to the set of in-scope namespaces if
942 /// addInScopeNmsp is True. Return false if the name is computed or is invalid.
943 /// </summary>
944 private bool CheckNamespaceInScope(QilBinary nd) {
945 QilName ndName;
946 string prefix, ns, prefixExisting, nsExisting;
947 XPathNodeType nodeType;
949 switch (nd.NodeType) {
950 case QilNodeType.ElementCtor:
951 case QilNodeType.AttributeCtor:
952 ndName = nd.Left as QilName;
953 if (ndName != null) {
954 prefix = ndName.Prefix;
955 ns = ndName.NamespaceUri;
956 nodeType = (nd.NodeType == QilNodeType.ElementCtor) ? XPathNodeType.Element : XPathNodeType.Attribute;
957 break;
960 // Not a literal name, so return false
961 return false;
963 default:
964 Debug.Assert(nd.NodeType == QilNodeType.NamespaceDecl);
965 prefix = (string) (QilLiteral) nd.Left;
966 ns = (string) (QilLiteral) nd.Right;
967 nodeType = XPathNodeType.Namespace;
968 break;
971 // Attribute with null namespace and xmlns:xml are always in-scope
972 if (nd.NodeType == QilNodeType.AttributeCtor && ns.Length == 0 ||
973 prefix == "xml" && ns == XmlReservedNs.NsXml) {
974 XmlILConstructInfo.Write(nd).IsNamespaceInScope = true;
975 return true;
978 // Don't process names that are invalid
979 if (!ValidateNames.ValidateName(prefix, string.Empty, ns, nodeType, ValidateNames.Flags.CheckPrefixMapping))
980 return false;
982 // Atomize names
983 prefix = this.nsmgr.NameTable.Add(prefix);
984 ns = this.nsmgr.NameTable.Add(ns);
986 // Determine whether namespace is already in-scope
987 for (int iNmsp = 0; iNmsp < this.cntNmsp; iNmsp++) {
988 this.nsmgr.GetNamespaceDeclaration(iNmsp, out prefixExisting, out nsExisting);
990 // If prefix is already declared,
991 if ((object) prefix == (object) prefixExisting) {
992 // Then if the namespace is the same, this namespace is redundant
993 if ((object) ns == (object) nsExisting)
994 XmlILConstructInfo.Write(nd).IsNamespaceInScope = true;
996 // Else quit searching, because any further matching prefixes will be hidden (not in-scope)
997 Debug.Assert(nd.NodeType != QilNodeType.NamespaceDecl || !this.nsmgr.HasNamespace(prefix) || this.nsmgr.LookupNamespace(prefix) == ns,
998 "Compilers must ensure that namespace declarations do not conflict with the namespace used by the element constructor.");
999 break;
1003 // If not in-scope, then add if it's allowed
1004 if (this.addInScopeNmsp) {
1005 this.nsmgr.AddNamespace(prefix, ns);
1006 this.cntNmsp++;
1009 return true;