1 //------------------------------------------------------------------------------
2 // <copyright file="XmlILConstructAnalyzer.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
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
{
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.
24 internal enum PossibleXmlStates
{
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).
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
50 /// Every node is annotated with information about how it will be constructed by ILGen.
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
;
64 /// Get ConstructInfo annotation for the specified node. Lazily create if necessary.
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;
86 /// Create and initialize XmlILConstructInfo annotation for the specified node.
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
;
101 /// Default to worst possible construction information.
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;
117 /// Xml states that are possible as construction of the annotated expression begins.
119 public PossibleXmlStates InitialStates
{
120 get { return this.xstatesInitial; }
122 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
123 this.xstatesInitial
= value;
128 /// Xml states that are possible as construction of the annotated expression ends.
130 public PossibleXmlStates FinalStates
{
131 get { return this.xstatesFinal; }
133 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
134 this.xstatesFinal
= value;
139 /// Xml states that are possible as looping begins. This is None if the annotated expression does not loop.
141 public PossibleXmlStates BeginLoopStates
{
142 //get { return this.xstatesBeginLoop; }
144 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
145 this.xstatesBeginLoop
= value;
150 /// Xml states that are possible as looping ends. This is None if the annotated expression does not loop.
152 public PossibleXmlStates EndLoopStates
{
153 //get { return this.xstatesEndLoop; }
155 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
156 this.xstatesEndLoop
= value;
161 /// Return the method that will be used to construct the annotated node.
163 public XmlILConstructMethod ConstructMethod
{
164 get { return this.constrMeth; }
166 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
167 this.constrMeth
= value;
172 /// Returns true if construction method is Writer or WriterThenIterator.
174 public bool PushToWriterFirst
{
175 get { return this.constrMeth == XmlILConstructMethod.Writer || this.constrMeth == XmlILConstructMethod.WriterThenIterator; }
177 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
180 switch (this.constrMeth
) {
181 case XmlILConstructMethod
.Iterator
:
182 this.constrMeth
= XmlILConstructMethod
.WriterThenIterator
;
185 case XmlILConstructMethod
.IteratorThenWriter
:
186 this.constrMeth
= XmlILConstructMethod
.Writer
;
193 /// Returns true if construction method is Writer or IteratorThenWriter.
195 public bool PushToWriterLast
{
196 get { return this.constrMeth == XmlILConstructMethod.Writer || this.constrMeth == XmlILConstructMethod.IteratorThenWriter; }
198 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
201 switch (this.constrMeth
) {
202 case XmlILConstructMethod
.Iterator
:
203 this.constrMeth
= XmlILConstructMethod
.IteratorThenWriter
;
206 case XmlILConstructMethod
.WriterThenIterator
:
207 this.constrMeth
= XmlILConstructMethod
.Writer
;
214 /// Returns true if construction method is IteratorThenWriter or Iterator.
216 public bool PullFromIteratorFirst
{
217 get { return this.constrMeth == XmlILConstructMethod.IteratorThenWriter || this.constrMeth == XmlILConstructMethod.Iterator; }
219 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
222 switch (this.constrMeth
) {
223 case XmlILConstructMethod
.Writer
:
224 this.constrMeth
= XmlILConstructMethod
.IteratorThenWriter
;
227 case XmlILConstructMethod
.WriterThenIterator
:
228 this.constrMeth
= XmlILConstructMethod
.Iterator
;
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.
238 public XmlILConstructInfo ParentInfo
{
239 //get { return this.parentInfo; }
241 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
242 this.parentInfo
= value;
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.
250 public XmlILConstructInfo ParentElementInfo
{
252 if (this.parentInfo
!= null && this.parentInfo
.nodeType
== QilNodeType
.ElementCtor
)
253 return this.parentInfo
;
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.
263 public bool IsNamespaceInScope
{
264 get { return this.isNmspInScope; }
266 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
267 this.isNmspInScope
= value;
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.
275 public bool MightHaveNamespaces
{
276 get { return this.mightHaveNmsp; }
278 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
279 this.mightHaveNmsp
= value;
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.
287 public bool MightHaveNamespacesAfterAttributes
{
288 get { return this.mightHaveNmspAfterAttrs; }
290 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
291 this.mightHaveNmspAfterAttrs
= value;
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.
299 public bool MightHaveAttributes
{
300 get { return this.mightHaveAttrs; }
302 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
303 this.mightHaveAttrs
= value;
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.
311 public bool MightHaveDuplicateAttributes
{
312 get { return this.mightHaveDupAttrs; }
314 Debug
.Assert(!this.isReadOnly
, "This XmlILConstructInfo instance is read-only.");
315 this.mightHaveDupAttrs
= value;
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.
323 public ArrayList CallersInfo
{
325 if (this.callersInfo
== null)
326 this.callersInfo
= new ArrayList();
328 return this.callersInfo
;
333 /// Return name of this annotation.
335 public virtual string Name
{
336 get { return "ConstructInfo"; }
340 /// Return string representation of this annotation.
342 public override string ToString() {
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
)
359 if (!MightHaveDuplicateAttributes
)
362 if (!MightHaveNamespaces
)
365 if (!MightHaveNamespacesAfterAttributes
)
366 s
+= ", NoNmspAfterAttrs";
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.
378 internal class XmlILStateAnalyzer
{
379 protected XmlILConstructInfo parentInfo
;
380 protected QilFactory fac
;
381 protected PossibleXmlStates xstates
;
382 protected bool withinElem
;
387 public XmlILStateAnalyzer(QilFactory fac
) {
392 /// Perform analysis on the specified constructor and its content. Return the ndContent that was passed in,
393 /// or a replacement.
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
);
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
;
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
;
472 /// Recursively analyze content. Return "nd" or a replacement for it.
474 protected virtual QilNode
AnalyzeContent(QilNode nd
) {
475 XmlILConstructInfo info
;
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
);
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
;
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
);
524 // Ensure that construct method is Writer and recursively analyze content
525 info
.ConstructMethod
= XmlILConstructMethod
.Writer
;
526 AnalyzeContent(ndChild
);
532 AnalyzeCopy(nd
, info
);
536 // Set states that are possible after expression is constructed
537 info
.FinalStates
= this.xstates
;
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
)
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
]);
574 /// Analyze conditional.
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
;
598 protected virtual void AnalyzeChoice(QilChoice ndChoice
, XmlILConstructInfo info
) {
599 PossibleXmlStates xstatesChoice
;
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
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
;
622 /// Analyze copying items.
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
;
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
)
651 /// Calculate starting xml states that will result when iterating over and constructing an expression of the specified type.
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
663 // | states-begin-loop <--+
665 // | +--------------+ |
666 // | | Construction | |
667 // | +--------------+ |
669 // | states-end-loop ----+
673 // Save starting loop states
674 info
.BeginLoopStates
= this.xstates
;
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
;
685 /// Calculate ending xml states that will result when iterating over and constructing an expression of the specified type.
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
;
699 /// Return true if an instance of the specified type might be an attribute or a namespace node.
701 private bool MaybeAttrNmsp(XmlQueryType typ
) {
702 return (typ
.NodeKinds
& (XmlNodeKindFlags
.Attribute
| XmlNodeKindFlags
.Namespace
)) != XmlNodeKindFlags
.None
;
706 /// Return true if an instance of the specified type might be a non-empty content type (attr/nsmp don't count).
708 private bool MaybeContent(XmlQueryType typ
) {
709 return !typ
.IsNode
|| (typ
.NodeKinds
& ~
(XmlNodeKindFlags
.Attribute
| XmlNodeKindFlags
.Namespace
)) != XmlNodeKindFlags
.None
;
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.
718 internal class XmlILElementAnalyzer
: XmlILStateAnalyzer
{
719 private NameTable attrNames
= new NameTable();
720 private ArrayList dupAttrs
= new ArrayList();
725 public XmlILElementAnalyzer(QilFactory fac
) : base(fac
) {
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.
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
);
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
);
763 /// Analyze copying items.
765 protected override void AnalyzeCopy(QilNode ndCopy
, XmlILConstructInfo info
) {
766 if (ndCopy
.NodeType
== QilNodeType
.AttributeCtor
) {
767 AnalyzeAttributeCtor(ndCopy
as QilBinary
, info
);
770 CheckAttributeNamespaceConstruct(ndCopy
.XmlType
);
773 base.AnalyzeCopy(ndCopy
, info
);
777 /// Analyze attribute constructor.
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
;
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;
812 // Attribute prefix and namespace are not known at compile-time
813 CheckAttributeNamespaceConstruct(ndAttr
.XmlType
);
818 /// If type might contain attributes or namespaces, set appropriate parent element flags.
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;
847 /// Scans constructed content, looking for redundant namespace declarations. If any are found, then they are marked
848 /// and removed later.
850 internal class XmlILNamespaceAnalyzer
{
851 private XmlNamespaceManager nsmgr
= new XmlNamespaceManager(new NameTable());
852 private bool addInScopeNmsp
;
858 public void Analyze(QilNode nd
, bool defaultNmspInScope
) {
859 this.addInScopeNmsp
= false;
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
);
871 if (defaultNmspInScope
)
872 this.nsmgr
.PopScope();
876 /// Recursively analyze content. Return "nd" or a replacement for it.
878 private void AnalyzeContent(QilNode nd
) {
881 switch (nd
.NodeType
) {
882 case QilNodeType
.Loop
:
883 this.addInScopeNmsp
= false;
884 AnalyzeContent((nd
as QilLoop
).Body
);
887 case QilNodeType
.Sequence
:
888 foreach (QilNode ndContent
in nd
)
889 AnalyzeContent(ndContent
);
892 case QilNodeType
.Conditional
:
893 this.addInScopeNmsp
= false;
894 AnalyzeContent((nd
as QilTernary
).Center
);
895 AnalyzeContent((nd
as QilTernary
).Right
);
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
]);
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
;
920 case QilNodeType
.AttributeCtor
:
921 this.addInScopeNmsp
= false;
922 CheckNamespaceInScope(nd
as QilBinary
);
925 case QilNodeType
.NamespaceDecl
:
926 CheckNamespaceInScope(nd
as QilBinary
);
929 case QilNodeType
.Nop
:
930 AnalyzeContent((nd
as QilUnary
).Child
);
934 this.addInScopeNmsp
= false;
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.
944 private bool CheckNamespaceInScope(QilBinary nd
) {
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
;
960 // Not a literal name, so return false
964 Debug
.Assert(nd
.NodeType
== QilNodeType
.NamespaceDecl
);
965 prefix
= (string) (QilLiteral
) nd
.Left
;
966 ns
= (string) (QilLiteral
) nd
.Right
;
967 nodeType
= XPathNodeType
.Namespace
;
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;
978 // Don't process names that are invalid
979 if (!ValidateNames
.ValidateName(prefix
, string.Empty
, ns
, nodeType
, ValidateNames
.Flags
.CheckPrefixMapping
))
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.");
1003 // If not in-scope, then add if it's allowed
1004 if (this.addInScopeNmsp
) {
1005 this.nsmgr
.AddNamespace(prefix
, ns
);