|
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Dispatcher
{
using System.Runtime;
using System.Xml.XPath;
/// <summary>
/// This structure contains the criteria used to select a set of nodes from a context node
///
/// Selectors select nodes with particular a relationship to a context node. Candidate nodes are first identified by
/// traversing away from the context node along an axis of traversal. The attribute axis, for example, identifies all
/// attributes of a given context node as candidates for selection.
///
/// The candidate nodeset identified by axis traversal is then refined by applying node tests.
/// A nodeType test constructs a new nodeset by selecting only those nodes of a given type from source candidate set
/// A qname test further refines this nodeset by selecting only those that match a given qname
/// </summary>
class NodeSelectCriteria
{
protected QueryAxis axis;
protected NodeQName qname;
protected NodeQNameType qnameType;
protected QueryNodeType type;
internal NodeSelectCriteria(QueryAxisType axis, NodeQName qname, QueryNodeType nodeType)
{
this.axis = QueryDataModel.GetAxis(axis);
this.qname = qname;
this.qnameType = qname.GetQNameType();
this.type = nodeType;
}
internal QueryAxis Axis
{
get
{
return this.axis;
}
}
internal bool IsCompressable
{
get
{
// PERF, Microsoft, weaken guard?
return QueryAxisType.Self == this.axis.Type || QueryAxisType.Child == this.axis.Type;
//return ((QueryAxisType.Self == this.axis.Type) || ((this.axis.Type != QueryAxisType.DescendantOrSelf || this.axis.Type != QueryAxisType.Descendant)&& 0 != ((QueryNodeType.Element | QueryNodeType.Root) & this.type)));
}
}
internal NodeQName QName
{
get
{
return this.qname;
}
}
internal QueryNodeType Type
{
get
{
return this.type;
}
}
#if NO
internal static NodeSelectCriteria Create(QueryAxisType axis, NodeQName qname, QueryNodeType nodeType)
{
return new NodeSelectCriteria(axis, qname, nodeType);
}
#endif
public bool Equals(NodeSelectCriteria criteria)
{
return (this.axis.Type == criteria.axis.Type && this.type == criteria.type && this.qname.Equals(criteria.qname));
}
#if NO
internal bool Match(SeekableXPathNavigator node)
{
return (this.MatchType(node) && this.MatchQName(node));
}
#endif
internal bool MatchType(SeekableXPathNavigator node)
{
QueryNodeType nodeType;
switch (node.NodeType)
{
default:
return false;
case XPathNodeType.Root:
nodeType = QueryNodeType.Root;
break;
case XPathNodeType.Attribute:
nodeType = QueryNodeType.Attribute;
break;
case XPathNodeType.Element:
nodeType = QueryNodeType.Element;
break;
case XPathNodeType.Comment:
nodeType = QueryNodeType.Comment;
break;
case XPathNodeType.Text:
case XPathNodeType.Whitespace:
case XPathNodeType.SignificantWhitespace:
nodeType = QueryNodeType.Text;
break;
case XPathNodeType.ProcessingInstruction:
nodeType = QueryNodeType.Processing;
break;
}
return (nodeType == (this.type & nodeType));
}
internal bool MatchQName(SeekableXPathNavigator node)
{
// Is this a standard qname test.. with known names and namespaces
switch (this.qnameType & NodeQNameType.Standard)
{
default:
break;
case NodeQNameType.Name:
// Selection criteria did not specify a namespace. Then, if the node supplies a namespace, we know
// that the criteria cannot possibly match
return (0 == node.NamespaceURI.Length && this.qname.EqualsName(node.LocalName));
case NodeQNameType.Standard:
string str = node.LocalName;
if (this.qname.name.Length == str.Length && this.qname.name == str)
{
str = node.NamespaceURI;
return (this.qname.ns.Length == str.Length && this.qname.ns == str);
}
return false;
}
if (NodeQNameType.Empty == this.qnameType)
{
return true;
}
// Maybe a wildcard
switch (this.qnameType & NodeQNameType.Wildcard)
{
default:
break;
case NodeQNameType.NameWildcard:
return this.qname.EqualsNamespace(node.NamespaceURI);
case NodeQNameType.Wildcard:
return true;
}
return false;
}
internal void Select(SeekableXPathNavigator contextNode, NodeSequence destSequence)
{
switch (this.type)
{
default:
if (QueryAxisType.Self == this.axis.Type)
{
if (this.MatchType(contextNode) && this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
}
}
else if (QueryAxisType.Descendant == this.axis.Type)
{
SelectDescendants(contextNode, destSequence);
}
else if (QueryAxisType.DescendantOrSelf == this.axis.Type)
{
destSequence.Add(contextNode);
SelectDescendants(contextNode, destSequence);
}
else if (QueryAxisType.Child == this.axis.Type)
{
// Select children of arbitrary type off the context node
if (contextNode.MoveToFirstChild())
{
do
{
// Select the node if its type and qname matches
if (this.MatchType(contextNode) && this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
}
}
while (contextNode.MoveToNext());
}
}
else if (QueryAxisType.Attribute == this.axis.Type)
{
if (contextNode.MoveToFirstAttribute())
{
do
{
// Select the node if its type and qname matches
if (this.MatchType(contextNode) && this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
// you can't have multiple instances of an attibute with the same qname
// Stop once one was found
// UNLESS WE HAVE A WILDCARD OFCOURSE!
if (0 == (this.qnameType & NodeQNameType.Wildcard))
{
break;
}
}
}
while (contextNode.MoveToNextAttribute());
}
}
else
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperCritical(new QueryProcessingException(QueryProcessingError.Unexpected));
}
break;
case QueryNodeType.Attribute:
// Select attributes off the context Node
if (contextNode.MoveToFirstAttribute())
{
do
{
if (this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
// you can't have multiple instances of an attibute with the same qname
// Stop once one was found
// UNLESS WE HAVE A WILDCARD OFCOURSE!
if (0 == (this.qnameType & NodeQNameType.Wildcard))
{
break;
}
}
}
while (contextNode.MoveToNextAttribute());
}
break;
case QueryNodeType.ChildNodes:
if (QueryAxisType.Descendant == this.axis.Type)
{
// Select descendants of arbitrary type off the context node
SelectDescendants(contextNode, destSequence);
}
else
{
// Select children of arbitrary type off the context node
if (contextNode.MoveToFirstChild())
{
do
{
// Select the node if its type and qname matches
if (this.MatchType(contextNode) && this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
}
}
while (contextNode.MoveToNext());
}
}
break;
case QueryNodeType.Element:
if (QueryAxisType.Descendant == this.axis.Type)
{
// Select descendants of arbitrary type off the context node
SelectDescendants(contextNode, destSequence);
}
else if (QueryAxisType.DescendantOrSelf == this.axis.Type)
{
destSequence.Add(contextNode);
SelectDescendants(contextNode, destSequence);
}
else if (contextNode.MoveToFirstChild())
{
do
{
// Children could have non element nodes in line
// Select the node if it is an element and the qname matches
if (XPathNodeType.Element == contextNode.NodeType && this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
}
}
while (contextNode.MoveToNext());
}
break;
case QueryNodeType.Root:
contextNode.MoveToRoot();
destSequence.Add(contextNode);
break;
case QueryNodeType.Text:
// Select child text nodes
if (contextNode.MoveToFirstChild())
{
do
{
// Select the node if its type matches
// Can't just do a comparison to XPathNodeType.Text since whitespace nodes
// count as text
if (this.MatchType(contextNode))
{
destSequence.Add(contextNode);
}
}
while (contextNode.MoveToNext());
}
break;
}
}
internal Opcode Select(SeekableXPathNavigator contextNode, NodeSequence destSequence, SelectOpcode next)
{
Opcode returnOpcode = next.Next;
switch (this.type)
{
default:
if (QueryAxisType.Self == this.axis.Type)
{
if (this.MatchType(contextNode) && this.MatchQName(contextNode))
{
long position = contextNode.CurrentPosition;
returnOpcode = next.Eval(destSequence, contextNode);
contextNode.CurrentPosition = position;
}
}
else
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperCritical(new QueryProcessingException(QueryProcessingError.Unexpected));
}
break;
case QueryNodeType.ChildNodes:
// Select children of arbitrary type off the context node
if (contextNode.MoveToFirstChild())
{
do
{
// Select the node if its type and qname matches
if (this.MatchType(contextNode) && this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
}
}
while (contextNode.MoveToNext());
}
break;
case QueryNodeType.Element:
// Select child elements
if (contextNode.MoveToFirstChild())
{
do
{
// Children could have non element nodes in line
// Select the node if it is an element and the qname matches
if (XPathNodeType.Element == contextNode.NodeType && this.MatchQName(contextNode))
{
long position = contextNode.CurrentPosition;
returnOpcode = next.Eval(destSequence, contextNode);
contextNode.CurrentPosition = position;
}
} while (contextNode.MoveToNext());
}
break;
case QueryNodeType.Root:
contextNode.MoveToRoot();
returnOpcode = next.Eval(destSequence, contextNode);
break;
}
return returnOpcode;
}
void SelectDescendants(SeekableXPathNavigator contextNode, NodeSequence destSequence)
{
int level = 1;
if (!contextNode.MoveToFirstChild())
{
return;
}
while (level > 0)
{
// Don't need type check. All child nodes allowed.
if (this.MatchQName(contextNode))
{
destSequence.Add(contextNode);
}
if (contextNode.MoveToFirstChild())
{
++level;
}
else if (contextNode.MoveToNext())
{
}
else
{
while (level > 0)
{
contextNode.MoveToParent();
--level;
if (contextNode.MoveToNext())
{
break;
}
}
}
}
}
#if DEBUG_FILTER
public override string ToString()
{
return string.Format("{0}, {1}:{2}", this.type, this.qname.ns, this.qname.name);
}
#endif
}
// General purpose selector
// Pops all parameters from the value stack
internal class SelectOpcode : Opcode
{
protected NodeSelectCriteria criteria;
internal SelectOpcode(NodeSelectCriteria criteria)
: this(OpcodeID.Select, criteria)
{
}
internal SelectOpcode(OpcodeID id, NodeSelectCriteria criteria)
: this(id, criteria, OpcodeFlags.None)
{
}
internal SelectOpcode(OpcodeID id, NodeSelectCriteria criteria, OpcodeFlags flags)
: base(id)
{
this.criteria = criteria;
this.flags |= (flags | OpcodeFlags.Select);
if (criteria.IsCompressable && (0 == (this.flags & OpcodeFlags.InitialSelect)))
{
this.flags |= OpcodeFlags.CompressableSelect;
}
}
internal NodeSelectCriteria Criteria
{
get
{
return this.criteria;
}
}
internal override bool Equals(Opcode op)
{
if (base.Equals(op))
{
return this.criteria.Equals(((SelectOpcode)op).criteria);
}
return false;
}
internal override Opcode Eval(ProcessingContext context)
{
StackFrame topFrame = context.TopSequenceArg;
SeekableXPathNavigator node = null;
Value[] sequences = context.Sequences;
for (int i = topFrame.basePtr; i <= topFrame.endPtr; ++i)
{
// Each NodeSequence will generate a new one, but only if the source FilterSequence isn't empty
// If the source FilterSequence is empty, release it and replace it with an empty sequence
NodeSequence sourceSeq = sequences[i].Sequence;
int sourceSeqCount = sourceSeq.Count;
if (sourceSeqCount == 0)
{
context.ReplaceSequenceAt(i, NodeSequence.Empty);
context.ReleaseSequence(sourceSeq);
}
else
{
NodeSequenceItem[] items = sourceSeq.Items;
if (sourceSeq.CanReuse(context))
{
node = items[0].GetNavigator();
sourceSeq.Clear();
sourceSeq.StartNodeset();
this.criteria.Select(node, sourceSeq);
sourceSeq.StopNodeset();
}
else
{
NodeSequence newSeq = null;
for (int item = 0; item < sourceSeqCount; ++item)
{
node = items[item].GetNavigator();
Fx.Assert(null != node, "");
if (null == newSeq)
{
newSeq = context.CreateSequence();
}
newSeq.StartNodeset();
this.criteria.Select(node, newSeq);
newSeq.StopNodeset();
}
context.ReplaceSequenceAt(i, (null != newSeq) ? newSeq : NodeSequence.Empty);
context.ReleaseSequence(sourceSeq);
}
}
}
return this.next;
}
internal override Opcode Eval(NodeSequence sequence, SeekableXPathNavigator node)
{
if (this.next == null || 0 == (this.next.Flags & OpcodeFlags.CompressableSelect))
{
// The next opcode is not a compressible select. Complete the select operation and return the next opcode
sequence.StartNodeset();
this.criteria.Select(node, sequence);
sequence.StopNodeset();
return this.next;
}
return this.criteria.Select(node, sequence, (SelectOpcode)this.next);
}
#if DEBUG_FILTER
public override string ToString()
{
return string.Format("{0} {1}", base.ToString(), this.criteria.ToString());
}
#endif
}
internal class InitialSelectOpcode : SelectOpcode
{
internal InitialSelectOpcode(NodeSelectCriteria criteria)
: base(OpcodeID.InitialSelect, criteria, OpcodeFlags.InitialSelect)
{
}
internal override Opcode Eval(ProcessingContext context)
{
StackFrame topFrame = context.TopSequenceArg;
Value[] sequences = context.Sequences;
bool wasInUse = context.SequenceStackInUse;
context.PushSequenceFrame();
for (int i = topFrame.basePtr; i <= topFrame.endPtr; ++i)
{
NodeSequence sourceSeq = sequences[i].Sequence;
int count = sourceSeq.Count;
if (count == 0)
{
// Empty sequence.
// Since there are no nodes in the sequence, we will track this sequence also
// using an empty sequence
if (!wasInUse)
context.PushSequence(NodeSequence.Empty);
}
else
{
NodeSequenceItem[] items = sourceSeq.Items;
for (int item = 0; item < sourceSeq.Count; ++item)
{
SeekableXPathNavigator node = items[item].GetNavigator();
Fx.Assert(null != node, "");
NodeSequence newSeq = context.CreateSequence();
newSeq.StartNodeset();
this.criteria.Select(node, newSeq);
newSeq.StopNodeset();
context.PushSequence(newSeq);
}
}
}
return this.next;
}
}
internal class SelectRootOpcode : Opcode
{
internal SelectRootOpcode()
: base(OpcodeID.SelectRoot)
{
}
internal override Opcode Eval(ProcessingContext context)
{
// The query processor object also serves as the query document root
int iterationCount = context.IterationCount;
Opcode returnOpcode = this.next;
// A root is always an initial step
context.PushSequenceFrame();
NodeSequence seq = context.CreateSequence();
if (this.next != null && 0 != (this.next.Flags & OpcodeFlags.CompressableSelect))
{
SeekableXPathNavigator node = context.Processor.ContextNode;
node.MoveToRoot();
returnOpcode = this.next.Eval(seq, node);
while (returnOpcode != null && 0 != (returnOpcode.Flags & OpcodeFlags.CompressableSelect))
{
returnOpcode = returnOpcode.Next;
}
}
else
{
// Roots do not have any qnames..
seq.StartNodeset();
SeekableXPathNavigator node = context.Processor.ContextNode;
node.MoveToRoot();
seq.Add(node);
seq.StopNodeset();
}
if (seq.Count == 0)
{
context.ReleaseSequence(seq);
seq = NodeSequence.Empty;
}
for (int i = 0; i < iterationCount; ++i)
{
context.PushSequence(seq);
}
if (iterationCount > 1)
seq.refCount += iterationCount - 1;
return returnOpcode;
}
}
}
|