The Labs \ Source Viewer \ SSCLI \ System.Xml.Xsl.Xslt \ Cloner

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XPathPatternBuilder.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. using System.Collections;
  16. using System.Collections.Generic;
  17. using System.Diagnostics;
  18. using System.Globalization;
  19. using System.Xml.XPath;
  20. using System.Xml.Schema;
  21. using System.Xml.Xsl.Qil;
  22. using System.Xml.Xsl.XPath;
  23. namespace System.Xml.Xsl.Xslt
  24. {
  25.     using T = XmlQueryTypeFactory;
  26.    
  27.     internal class XPathPatternBuilder : XPathPatternParser.IPatternBuilder
  28.     {
  29.         private XPathPredicateEnvironment predicateEnvironment;
  30.         private XPathBuilder predicateBuilder;
  31.         private bool inTheBuild;
  32.         private XPathQilFactory f;
  33.         private QilNode fixupNode;
  34.         private IXPathEnvironment environment;
  35.        
  36.         public XPathPatternBuilder(IXPathEnvironment environment)
  37.         {
  38.             Debug.Assert(environment != null);
  39.             this.environment = environment;
  40.             this.f = environment.Factory;
  41.             this.predicateEnvironment = new XPathPredicateEnvironment(environment);
  42.             this.predicateBuilder = new XPathBuilder(predicateEnvironment);
  43.            
  44.             this.fixupNode = f.Unknown(T.NodeNotRtfS);
  45.         }
  46.        
  47.         public QilNode FixupNode {
  48.             get { return fixupNode; }
  49.         }
  50.        
  51.         public virtual void StartBuild()
  52.         {
  53.             Debug.Assert(!inTheBuild, "XPathBuilder is buisy!");
  54.             inTheBuild = true;
  55.             return;
  56.         }
  57.        
  58.         [Conditional("DEBUG")]
  59.         public void AssertFilter(QilLoop filter)
  60.         {
  61.             Debug.Assert(filter.NodeType == QilNodeType.Filter, "XPathPatternBuilder expected to generate list of Filters on top level");
  62.             Debug.Assert(filter.Variable.XmlType.IsSubtypeOf(T.NodeNotRtf));
  63.             Debug.Assert(filter.Variable.Binding.NodeType == QilNodeType.Unknown);
  64.             // fixupNode
  65.             Debug.Assert(filter.Body.XmlType.IsSubtypeOf(T.Boolean));
  66.         }
  67.        
  68.         private void FixupFilterBinding(QilLoop filter, QilNode newBinding)
  69.         {
  70.             AssertFilter(filter);
  71.             filter.Variable.Binding = newBinding;
  72.         }
  73.        
  74.         public virtual QilNode EndBuild(QilNode result)
  75.         {
  76.             Debug.Assert(inTheBuild, "StartBuild() wasn't called");
  77.             if (result == null) {
  78.                 // Special door to clean builder state in exception handlers
  79.             }
  80.             inTheBuild = false;
  81.             return result;
  82.         }
  83.        
  84.         public QilNode Operator(XPathOperator op, QilNode left, QilNode right)
  85.         {
  86.             Debug.Assert(op == XPathOperator.Union);
  87.             Debug.Assert(left != null);
  88.             Debug.Assert(right != null);
  89.             // It is important to not create nested lists here
  90.             Debug.Assert(right.NodeType == QilNodeType.Filter, "LocationPathPattern must be compiled into a filter");
  91.             if (left.NodeType == QilNodeType.Sequence) {
  92.                 ((QilList)left).Add(right);
  93.                 return left;
  94.             }
  95.             else {
  96.                 Debug.Assert(left.NodeType == QilNodeType.Filter, "LocationPathPattern must be compiled into a filter");
  97.                 return f.Sequence(left, right);
  98.             }
  99.         }
  100.        
  101.         private static QilLoop BuildAxisFilter(QilPatternFactory f, QilIterator itr, XPathAxis xpathAxis, XPathNodeType nodeType, string name, string nsUri)
  102.         {
  103.                 // ns:bar || bar
  104.                 // ns:*
  105.                 // *:foo
  106.                 /*name  == nsUri == null*/                // *
  107.             QilNode nameTest = (name != null && nsUri != null ? f.Eq(f.NameOf(itr), f.QName(name, nsUri)) : nsUri != null ? f.Eq(f.NamespaceUriOf(itr), f.String(nsUri)) : name != null ? f.Eq(f.LocalNameOf(itr), f.String(name)) : f.True());
  108.            
  109.             XmlNodeKindFlags intersection = XPathBuilder.AxisTypeMask(itr.XmlType.NodeKinds, nodeType, xpathAxis);
  110.            
  111.                 // input & required doesn't intersect
  112.                 // input is subset of required
  113.                 /*else*/            QilNode typeTest = (intersection == 0 ? f.False() : intersection == itr.XmlType.NodeKinds ? f.True() : f.IsType(itr, T.NodeChoice(intersection)));
  114.            
  115.             QilLoop filter = f.BaseFactory.Filter(itr, f.And(typeTest, nameTest));
  116.             filter.XmlType = T.PrimeProduct(T.NodeChoice(intersection), filter.XmlType.Cardinality);
  117.            
  118.             return filter;
  119.         }
  120.        
  121.         public QilNode Axis(XPathAxis xpathAxis, XPathNodeType nodeType, string prefix, string name)
  122.         {
  123.             Debug.Assert(xpathAxis == XPathAxis.Child || xpathAxis == XPathAxis.Attribute || xpathAxis == XPathAxis.DescendantOrSelf || xpathAxis == XPathAxis.Root);
  124.             QilLoop result;
  125.             double priority;
  126.             switch (xpathAxis) {
  127.                 case XPathAxis.DescendantOrSelf:
  128.                     Debug.Assert(nodeType == XPathNodeType.All && prefix == null && name == null, " // is the only d-o-s axes that we can have in pattern");
  129.                     return f.Nop(this.fixupNode);
  130.                 case XPathAxis.Root:
  131.                     // We using Nop as a flag that DescendantOrSelf exis was used between steps.
  132.                     QilIterator i;
  133.                     result = f.BaseFactory.Filter(i = f.For(fixupNode), f.IsType(i, T.Document));
  134.                     priority = 0.5;
  135.                     break;
  136.                 default:
  137.                     string nsUri = prefix == null ? null : this.environment.ResolvePrefix(prefix);
  138.                     result = BuildAxisFilter(f, f.For(fixupNode), xpathAxis, nodeType, name, nsUri);
  139.                     switch (nodeType) {
  140.                         case XPathNodeType.Element:
  141.                         case XPathNodeType.Attribute:
  142.                             if (name != null) {
  143.                                 priority = 0;
  144.                             }
  145.                             else {
  146.                                 if (prefix != null) {
  147.                                     priority = -0.25;
  148.                                 }
  149.                                 else {
  150.                                     priority = -0.5;
  151.                                 }
  152.                             }
  153.                             break;
  154.                         case XPathNodeType.ProcessingInstruction:
  155.                             priority = name != null ? 0 : -0.5;
  156.                             break;
  157.                         default:
  158.                             priority = -0.5;
  159.                             break;
  160.                     }
  161.                     break;
  162.             }
  163.            
  164.             SetPriority(result, priority);
  165.             SetLastParent(result, result);
  166.             return result;
  167.         }
  168.        
  169.         // a/b/c -> self::c[parent::b[parent::a]]
  170.         // a/b//c -> self::c[ancestor::b[parent::a]]
  171.         // a/b -> self::b[parent::a]
  172.         // -> JoinStep(Axis('a'), Axis('b'))
  173.         // -> Filter('b' & Parent(Filter('a')))
  174.         // a//b
  175.         // -> JoinStep(Axis('a'), JoingStep(Axis(DescendantOrSelf), Axis('b')))
  176.         // -> JoinStep(Filter('a'), JoingStep(Nop(null), Filter('b')))
  177.         // -> JoinStep(Filter('a'), Nop(Filter('b')))
  178.         // -> Filter('b' & Ancestor(Filter('a')))
  179.         public QilNode JoinStep(QilNode left, QilNode right)
  180.         {
  181.             Debug.Assert(left != null);
  182.             Debug.Assert(right != null);
  183.             if (left.NodeType == QilNodeType.Nop) {
  184.                 QilUnary nop = (QilUnary)left;
  185.                 Debug.Assert(nop.Child == this.fixupNode);
  186.                 nop.Child = right;
  187.                 // We use Nop as a flag that DescendantOrSelf axis was used between steps.
  188.                 return nop;
  189.             }
  190.             Debug.Assert(GetLastParent(left) == left, "Left is always single axis and never the step");
  191.             Debug.Assert(left.NodeType == QilNodeType.Filter);
  192.             CleanAnnotation(left);
  193.             QilLoop parentFilter = (QilLoop)left;
  194.             bool ancestor = false;
  195.             {
  196.                 if (right.NodeType == QilNodeType.Nop) {
  197.                     ancestor = true;
  198.                     QilUnary nop = (QilUnary)right;
  199.                     Debug.Assert(nop.Child != null);
  200.                     right = nop.Child;
  201.                 }
  202.             }
  203.             Debug.Assert(right.NodeType == QilNodeType.Filter);
  204.             QilLoop lastParent = GetLastParent(right);
  205.             FixupFilterBinding(parentFilter, ancestor ? f.Ancestor(lastParent.Variable) : f.Parent(lastParent.Variable));
  206.             lastParent.Body = f.And(lastParent.Body, f.Not(f.IsEmpty(parentFilter)));
  207.             SetPriority(right, 0.5);
  208.             SetLastParent(right, parentFilter);
  209.             return right;
  210.         }
  211.        
  212.         public QilNode Predicate(QilNode node, QilNode condition, bool isReverseStep)
  213.         {
  214.             Debug.Assert(isReverseStep == false, "patterns can't have reverse axe");
  215.             QilLoop nodeFilter = (QilLoop)node;
  216.             if (condition.XmlType.TypeCode == XmlTypeCode.Double) {
  217.                 predicateEnvironment.SetContext(nodeFilter);
  218.                 condition = f.Eq(condition, predicateEnvironment.GetPosition());
  219.             }
  220.             else {
  221.                 condition = f.ConvertToBoolean(condition);
  222.             }
  223.            
  224.             nodeFilter.Body = f.And(nodeFilter.Body, condition);
  225.             SetPriority(node, 0.5);
  226.             return node;
  227.         }
  228.        
  229.         public QilNode Function(string prefix, string name, IList<QilNode> args)
  230.         {
  231.             Debug.Assert(prefix.Length == 0);
  232.             QilIterator i = f.For(fixupNode);
  233.             QilNode matches;
  234.            
  235.             if (name == "id") {
  236.                 Debug.Assert(args.Count == 1 && args[0].NodeType == QilNodeType.LiteralString, "Function id() must have one literal string argument");
  237.                 matches = f.Id(i, args[0]);
  238.             }
  239.             else {
  240.                 Debug.Assert(name == "key", "Unexpected function");
  241.                 Debug.Assert(args.Count == 2 && args[0].NodeType == QilNodeType.LiteralString && args[1].NodeType == QilNodeType.LiteralString, "Function key() must have two literal string arguments");
  242.                 matches = environment.ResolveFunction(prefix, name, args, new XsltFunctionFocus(i));
  243.             }
  244.            
  245.             QilIterator j;
  246.             QilLoop result = f.BaseFactory.Filter(i, f.Not(f.IsEmpty(f.Filter(j = f.For(matches), f.Is(j, i)))));
  247.             SetPriority(result, 0.5);
  248.             SetLastParent(result, result);
  249.             return result;
  250.         }
  251.        
  252.         public QilNode String(string value)
  253.         {
  254.             return f.String(value);
  255.         }
  256.         // As argument of id() or key() function
  257.         public QilNode Number(double value)
  258.         {
  259.             return UnexpectedToken("Literal number");
  260.         }
  261.         public QilNode Variable(string prefix, string name)
  262.         {
  263.             return UnexpectedToken("Variable");
  264.         }
  265.        
  266.         private QilNode UnexpectedToken(string tokenName)
  267.         {
  268.             string prompt = string.Format(CultureInfo.InvariantCulture, "Internal Error: {0} is not allowed in XSLT pattern outside of predicate.", tokenName);
  269.             Debug.Assert(false, prompt);
  270.             throw new Exception(prompt);
  271.         }
  272.        
  273.         // -------------------------------------- Priority / Parent ---------------------------------------
  274.        
  275.         private class Annotation
  276.         {
  277.             public double Priority;
  278.             public QilLoop Parent;
  279.         }
  280.        
  281.         public static void SetPriority(QilNode node, double priority)
  282.         {
  283.             Annotation ann = (Annotation)node.Annotation ?? new Annotation();
  284.             ann.Priority = priority;
  285.             node.Annotation = ann;
  286.         }
  287.        
  288.         public static double GetPriority(QilNode node)
  289.         {
  290.             return ((Annotation)node.Annotation).Priority;
  291.         }
  292.        
  293.         private static void SetLastParent(QilNode node, QilLoop parent)
  294.         {
  295.             Debug.Assert(parent.NodeType == QilNodeType.Filter);
  296.             Annotation ann = (Annotation)node.Annotation ?? new Annotation();
  297.             ann.Parent = parent;
  298.             node.Annotation = ann;
  299.         }
  300.        
  301.         private static QilLoop GetLastParent(QilNode node)
  302.         {
  303.             return ((Annotation)node.Annotation).Parent;
  304.         }
  305.        
  306.         public static void CleanAnnotation(QilNode node)
  307.         {
  308.             node.Annotation = null;
  309.         }
  310.        
  311.         // -------------------------------------- GetPredicateBuilder() ---------------------------------------
  312.        
  313.         public IXPathBuilder<QilNode> GetPredicateBuilder(QilNode ctx)
  314.         {
  315.             QilLoop context = (QilLoop)ctx;
  316.             Debug.Assert(context != null, "Predicate always has step so it can't have context == null");
  317.             Debug.Assert(context.Variable.NodeType == QilNodeType.For, "It shouldn't be Let, becaus predicates in PatternBuilder don't produce cached tuples.");
  318.             predicateEnvironment.SetContext(context);
  319.             return predicateBuilder;
  320.         }
  321.        
  322.         private class XPathPredicateEnvironment : IXPathEnvironment
  323.         {
  324.             private IXPathEnvironment baseEnvironment;
  325.             private QilLoop baseContext;
  326.             private XPathQilFactory f;
  327.             private Cloner cloner;
  328.            
  329.             public XPathPredicateEnvironment(IXPathEnvironment baseEnvironment)
  330.             {
  331.                 this.baseEnvironment = baseEnvironment;
  332.                 this.f = baseEnvironment.Factory;
  333.                 cloner = new Cloner(f.BaseFactory);
  334.             }
  335.            
  336.             public void SetContext(QilLoop filter)
  337.             {
  338.                 this.baseContext = filter;
  339.             }
  340.            
  341. /*  ----------------------------------------------------------------------------
  342.                 IXPathEnvironment interface
  343.             */           
  344.             public XPathQilFactory Factory {
  345.                 get { return f; }
  346.             }
  347.            
  348.             public QilNode ResolveVariable(string prefix, string name)
  349.             {
  350.                 return baseEnvironment.ResolveVariable(prefix, name);
  351.             }
  352.             public QilNode ResolveFunction(string prefix, string name, IList<QilNode> args, IFocus env)
  353.             {
  354.                 return baseEnvironment.ResolveFunction(prefix, name, args, env);
  355.             }
  356.             public string ResolvePrefix(string prefix)
  357.             {
  358.                 return baseEnvironment.ResolvePrefix(prefix);
  359.             }
  360.            
  361.             public QilNode GetCurrent()
  362.             {
  363.                 return baseContext.Variable;
  364.             }
  365.             public QilNode GetPosition()
  366.             {
  367.                 QilLoop clone = (QilLoop)cloner.Clone(baseContext);
  368.                 XmlNodeKindFlags nodeKinds = baseContext.XmlType.NodeKinds;
  369.                 // baseContext either always returns attributes (attribute::), or never returns attributes or namespaces (child::)
  370.                 if (nodeKinds == XmlNodeKindFlags.Attribute) {
  371.                     QilIterator i = f.For(f.Parent(GetCurrent()));
  372.                     clone.Variable.Binding = f.Content(i);
  373.                     clone.Body = f.And(clone.Body, f.Before(clone.Variable, GetCurrent()));
  374.                     clone = f.BaseFactory.Loop(i, clone);
  375.                 }
  376.                 else {
  377.                     Debug.Assert((nodeKinds & (XmlNodeKindFlags.Attribute | XmlNodeKindFlags.Namespace)) == XmlNodeKindFlags.None);
  378.                     clone.Variable.Binding = f.PrecedingSibling(GetCurrent());
  379.                 }
  380.                 return f.Add(f.Double(1), f.XsltConvert(f.Length(clone), T.DoubleX));
  381.             }
  382.             public QilNode GetLast()
  383.             {
  384.                 QilLoop clone = (QilLoop)cloner.Clone(baseContext);
  385.                 QilIterator i = f.For(f.Parent(GetCurrent()));
  386.                 clone.Variable.Binding = f.Content(i);
  387.                 return f.XsltConvert(f.Length(f.Loop(i, clone)), T.DoubleX);
  388.             }
  389.            
  390.             internal class Cloner : QilCloneVisitor
  391.             {
  392.                 public Cloner(QilFactory f) : base(f)
  393.                 {
  394.                 }
  395.                 // Expressions we are cloning have fixup node of type QilNodeType.Unknown
  396.                 // This node will be replaced anyway so we leave it as it is
  397.                 protected override QilNode VisitUnknown(QilNode n)
  398.                 {
  399.                     return n;
  400.                 }
  401.             }
  402.         }
  403.        
  404.         private class XsltFunctionFocus : IFocus
  405.         {
  406.             private QilIterator current;
  407.            
  408.             public XsltFunctionFocus(QilIterator current)
  409.             {
  410.                 Debug.Assert(current != null);
  411.                 this.current = current;
  412.             }
  413.            
  414. /*  ----------------------------------------------------------------------------
  415.                 IFocus interface
  416.             */           
  417.             public QilNode GetCurrent()
  418.             {
  419.                 return current;
  420.             }
  421.            
  422.             public QilNode GetPosition()
  423.             {
  424.                 Debug.Fail("GetPosition() must not be called");
  425.                 return null;
  426.             }
  427.            
  428.             public QilNode GetLast()
  429.             {
  430.                 Debug.Fail("GetLast() must not be called");
  431.                 return null;
  432.             }
  433.         }
  434.     }
  435. }

Developer Fusion