1 //------------------------------------------------------------------------------
2 // <copyright file="QilGenerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 // <spec>http://www.w3.org/TR/xslt.html</spec>
7 // <spec>http://www.w3.org/TR/xslt20/</spec>
8 //------------------------------------------------------------------------------
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.Collections.Specialized;
13 using System.Diagnostics;
14 using System.Reflection;
16 using System.Xml.Xsl.Qil;
17 using System.Xml.Xsl.Runtime;
18 using System.Xml.Xsl.XPath;
20 namespace System.Xml.Xsl.Xslt {
21 using Res = System.Xml.Utils.Res;
22 using ScopeRecord = CompilerScopeManager<QilIterator>.ScopeRecord;
23 using T = XmlQueryTypeFactory;
25 // Everywhere in this code in case of error in the stylesheet we should call ReportError or ReportWarning
27 internal class ReferenceReplacer : QilReplaceVisitor {
28 private QilReference lookFor, replaceBy;
30 public ReferenceReplacer(QilFactory f) : base(f) {
33 public QilNode Replace(QilNode expr, QilReference lookFor, QilReference replaceBy) {
34 QilDepthChecker.Check(expr);
35 this.lookFor = lookFor;
36 this.replaceBy = replaceBy;
37 return VisitAssumeReference(expr);
40 protected override QilNode VisitReference(QilNode n) {
41 return (n == lookFor) ? replaceBy : n;
45 internal partial class QilGenerator : IErrorHelper {
46 private CompilerScopeManager<QilIterator> scope;
47 private OutputScopeManager outputScope;
48 private HybridDictionary prefixesInUse;
50 private XsltQilFactory f;
51 private XPathBuilder xpathBuilder;
52 private XPathParser<QilNode> xpathParser;
53 private XPathPatternBuilder ptrnBuilder;
54 private XPathPatternParser ptrnParser;
55 private ReferenceReplacer refReplacer;
56 private KeyMatchBuilder keyMatchBuilder;
57 private InvokeGenerator invkGen;
58 private MatcherBuilder matcherBuilder;
59 private QilStrConcatenator strConcat;
60 private VariableHelper varHelper;
62 private Compiler compiler;
63 private QilList functions;
64 private QilFunction generalKey;
65 private bool formatNumberDynamicUsed;
66 private QilList extPars;
67 private QilList gloVars;
68 private QilList nsVars;
70 private XmlQueryType elementOrDocumentType;
71 private XmlQueryType textOrAttributeType;
72 private XslNode lastScope;
73 private XslVersion xslVersion;
75 private QilName nameCurrent;
76 private QilName namePosition;
77 private QilName nameLast;
78 private QilName nameNamespaces;
79 private QilName nameInit;
81 private SingletonFocus singlFocus;
82 private FunctionFocus funcFocus;
83 private LoopFocus curLoop;
85 private int formatterCnt;
87 public static QilExpression CompileStylesheet(Compiler compiler) {
88 return new QilGenerator(compiler.IsDebug).Compile(compiler);
91 private QilGenerator(bool debug) {
92 scope = new CompilerScopeManager<QilIterator>();
93 outputScope = new OutputScopeManager();
94 prefixesInUse = new HybridDictionary();
95 f = new XsltQilFactory(new QilFactory(), debug);
96 xpathBuilder = new XPathBuilder((IXPathEnvironment) this);
97 xpathParser = new XPathParser<QilNode>();
98 ptrnBuilder = new XPathPatternBuilder((IXPathEnvironment) this);
99 ptrnParser = new XPathPatternParser();
100 refReplacer = new ReferenceReplacer(f.BaseFactory);
101 invkGen = new InvokeGenerator(f, debug);
102 matcherBuilder = new MatcherBuilder(f, refReplacer, invkGen);
103 singlFocus = new SingletonFocus(f);
104 funcFocus = new FunctionFocus();
105 curLoop = new LoopFocus(f);
106 strConcat = new QilStrConcatenator(f);
107 varHelper = new VariableHelper(f);
109 elementOrDocumentType = T.DocumentOrElement;
110 textOrAttributeType = T.NodeChoice(XmlNodeKindFlags.Text | XmlNodeKindFlags.Attribute);
112 nameCurrent = f.QName("current" , XmlReservedNs.NsXslDebug);
113 namePosition = f.QName("position" , XmlReservedNs.NsXslDebug);
114 nameLast = f.QName("last" , XmlReservedNs.NsXslDebug);
115 nameNamespaces = f.QName("namespaces", XmlReservedNs.NsXslDebug);
116 nameInit = f.QName("init" , XmlReservedNs.NsXslDebug);
121 private bool IsDebug {
122 get { return compiler.IsDebug; }
125 private bool EvaluateFuncCalls { get { return !IsDebug; } }
126 private bool InferXPathTypes { get { return !IsDebug; } }
128 private QilExpression Compile(Compiler compiler) {
129 Debug.Assert(compiler != null);
130 this.compiler = compiler;
131 this.functions = f.FunctionList();
132 this.extPars = f.GlobalParameterList();
133 this.gloVars = f.GlobalVariableList();
134 this.nsVars = f.GlobalVariableList();
136 compiler.Scripts.CompileScripts();
138 // Refactor huge templates into smaller ones (more JIT friendly)
139 (new XslAstRewriter()).Rewrite(compiler);
142 (new XslAstAnalyzer()).Analyze(compiler);
145 // Global variables and external params are visible from everywhere, so we have
146 // to prepopulate the scope with all of them before starting compilation
147 CreateGlobalVarPars();
151 CompileAndSortMatches(compiler.Root.Imports[0]);
152 PrecompileProtoTemplatesHeaders();
153 CompileGlobalVariables();
155 foreach (ProtoTemplate tmpl in compiler.AllTemplates) {
156 CompileProtoTemplate(tmpl);
158 varHelper.CheckEmpty();
160 catch (XslLoadException e) {
161 e.SetSourceLineInfo(lastScope.SourceLine);
164 catch (Exception e) {
165 if (!XmlException.IsCatchableException(e)) {
168 throw new XslLoadException(e, lastScope.SourceLine);
171 CompileInitializationCode();
172 QilNode root = CompileRootExpression(compiler.StartApplyTemplates);
174 // Clean default values which we calculate in caller context
175 foreach (ProtoTemplate tmpl in compiler.AllTemplates) {
176 foreach (QilParameter par in tmpl.Function.Arguments) {
177 if (!IsDebug || par.Name.Equals(nameNamespaces)) {
178 par.DefaultValue = null;
183 // Create list of all early bound objects
184 Dictionary<string, Type> scriptClasses = compiler.Scripts.ScriptClasses;
185 List<EarlyBoundInfo> ebTypes = new List<EarlyBoundInfo>(scriptClasses.Count);
186 foreach (KeyValuePair<string, Type> pair in scriptClasses) {
187 if (pair.Value != null) {
188 ebTypes.Add(new EarlyBoundInfo(pair.Key, pair.Value));
192 QilExpression qil = f.QilExpression(root, f.BaseFactory); {
193 qil.EarlyBoundTypes = ebTypes;
194 qil.FunctionList = functions;
195 qil.GlobalParameterList = extPars;
196 qil.GlobalVariableList = gloVars;
197 qil.WhitespaceRules = compiler.WhitespaceRules;
198 qil.IsDebug = IsDebug;
199 qil.DefaultWriterSettings = compiler.Output.Settings;
202 QilDepthChecker.Check(qil);
207 private QilNode InvokeOnCurrentNodeChanged() {
208 Debug.Assert(IsDebug && curLoop.IsFocusSet);
210 return f.Loop(i = f.Let(f.InvokeOnCurrentNodeChanged(curLoop.GetCurrent())), f.Sequence());
213 [Conditional("DEBUG")]
214 private void CheckSingletonFocus() {
215 Debug.Assert(!curLoop.IsFocusSet && !funcFocus.IsFocusSet, "Must be compiled using singleton focus");
218 private void CompileInitializationCode() {
219 // Initialization code should be executed before any other code (global variables/parameters or root expression)
220 // For this purpose we insert it as THE FIRST global variable $init (global variables are calculated before global parameters)
221 // and put all initalization code in it.
222 // In retail mode global variables are calculated lasely if they don't have side effects.
223 // To mark $init as variable with side effect we put all code to function and set SideEffect flag on this function.
224 // ILGen expects that all library functions are sideeffect free. To prevent calls to RegisterDecimalFormat() to be optimized out
225 // we add results returned from these calls and return them as a result of initialization function.
226 QilNode init = f.Int32(0);
228 // Register all decimal formats, they are needed for format-number()
229 if (formatNumberDynamicUsed || IsDebug) {
230 bool defaultDefined = false;
231 foreach (DecimalFormatDecl format in compiler.DecimalFormats) {
232 init = f.Add(init, f.InvokeRegisterDecimalFormat(format));
233 defaultDefined |= (format.Name == DecimalFormatDecl.Default.Name);
235 if (!defaultDefined) {
236 init = f.Add(init, f.InvokeRegisterDecimalFormat(DecimalFormatDecl.Default));
240 // Register all script namespaces
241 foreach (string scriptNs in compiler.Scripts.ScriptClasses.Keys) {
242 init = f.Add(init, f.InvokeCheckScriptNamespace(scriptNs));
245 if (init.NodeType == QilNodeType.Add) {
246 QilFunction initFunction = f.Function(f.FormalParameterList(), init, /*sideEffects:*/f.True());
247 initFunction.DebugName = "Init";
248 this.functions.Add(initFunction);
250 QilNode initBinding = f.Invoke(initFunction, f.ActualParameterList());
252 // In debug mode all variables must have type item*
253 initBinding = f.TypeAssert(initBinding, T.ItemS);
255 QilIterator initVar = f.Let(initBinding);
256 initVar.DebugName = nameInit.ToString();
257 gloVars.Insert(0, initVar);
261 private QilNode CompileRootExpression(XslNode applyTmpls) {
262 // Compile start apply-templates call
263 CheckSingletonFocus();
264 singlFocus.SetFocus(SingletonFocusType.InitialContextNode);
265 QilNode result = GenerateApply(compiler.Root, applyTmpls);
266 singlFocus.SetFocus(null);
268 return f.DocumentCtor(result);
271 private QilList EnterScope(XslNode node) {
272 // This is the only place where lastScope is changed
274 xslVersion = node.XslVersion;
275 if (this.scope.EnterScope(node.Namespaces)) {
276 return BuildDebuggerNamespaces();
281 private void ExitScope() {
282 this.scope.ExitScope();
285 private QilList BuildDebuggerNamespaces() {
287 QilList nsDecls = f.BaseFactory.Sequence();
288 foreach (ScopeRecord rec in this.scope) {
289 nsDecls.Add(f.NamespaceDecl(f.String(rec.ncName), f.String(rec.nsUri)));
296 // For each call instruction - call-template, use-attribute-sets, apply-template, apply-imports - we have
297 // to pass the current execution context which may be represented as three additional implicit arguments:
298 // current, position, last. In most cases the last two ones are never used, so for the purpose
299 // of optimization in non-debug mode we bind them only if they are actually needed.
301 // Strictly speaking, a (proto)template function is supplied with the additional position argument if both
302 // the following conditions are true:
303 // 1. At least one template within the given stylesheet contains a "----" position() function invocation
304 // (needPositionArgs == true). ---- here means "not within any of for-each instructions".
305 // 2. THIS template contains a ---- position() invocation or a ---- call-template, use-attribute-sets,
306 // or apply-imports instruction. Note: apply-template's are not taken into account because in that
307 // case the call will be actually wrapped in a tuple.
309 // The same is true for additional last arguments.
311 // There are 3 cases when context methods may be called:
312 // 1. In context of for-each expression
313 // 2. In context of template
314 // 3. In context of global variable
315 // We treating this methods differentely when they are called to create implicit arguments.
316 // Implicite argument (position, last) are rare and lead to uneficiant code. So we treating them
317 // specialy to be able eliminate them later, wen we compiled everithing and can detect was they used or not.
319 // Returns context node
320 private QilNode GetCurrentNode() {
321 if (curLoop.IsFocusSet) {
322 return curLoop.GetCurrent();
323 } else if (funcFocus.IsFocusSet) {
324 return funcFocus.GetCurrent();
326 return singlFocus.GetCurrent();
330 // Returns context position
331 private QilNode GetCurrentPosition() {
332 if (curLoop.IsFocusSet) {
333 return curLoop.GetPosition();
334 } else if (funcFocus.IsFocusSet) {
335 return funcFocus.GetPosition();
337 return singlFocus.GetPosition();
341 // Returns context size
342 private QilNode GetLastPosition() {
343 if (curLoop.IsFocusSet) {
344 return curLoop.GetLast();
345 } else if (funcFocus.IsFocusSet) {
346 return funcFocus.GetLast();
348 return singlFocus.GetLast();
352 private XmlQueryType ChooseBestType(VarPar var) {
353 if (IsDebug || !InferXPathTypes) {
357 switch (var.Flags & XslFlags.TypeFilter) {
358 case XslFlags.String : return T.StringX;;
359 case XslFlags.Number : return T.DoubleX;
360 case XslFlags.Boolean : return T.BooleanX;
361 case XslFlags.Node : return T.NodeNotRtf;
362 case XslFlags.Nodeset : return T.NodeNotRtfS;
363 case XslFlags.Rtf : return T.Node;
364 case XslFlags.Node | XslFlags.Rtf : return T.Node;
365 case XslFlags.Node | XslFlags.Nodeset : return T.NodeNotRtfS;
366 case XslFlags.Nodeset | XslFlags.Rtf : return T.NodeS;
367 case XslFlags.Node | XslFlags.Nodeset | XslFlags.Rtf : return T.NodeS;
368 default : return T.ItemS;
372 // In debugger we need to pass to each (almost) template $namespace parameter with list of namespaces that
373 // are defined on stylesheet and this template. In most cases this will be only xmlns:xsl="..."
374 // To prevent creating these list with each call-template/apply-template we create one global variable for each unique set of namespaces
375 // This function looks through list of existent global variables for suitable ns list and add one if none was found.
376 private QilIterator GetNsVar(QilList nsList) {
377 Debug.Assert(IsDebug, "This is debug only logic");
378 // All global vars at this point are nsList like one we are looking now.
379 foreach (QilIterator var in this.nsVars) {
380 Debug.Assert(var.XmlType.IsSubtypeOf(T.NamespaceS));
381 Debug.Assert(var.Binding is QilList);
382 QilList varList = (QilList)var.Binding;
383 if (varList.Count != nsList.Count) {
387 for (int i = 0; i < nsList.Count; i ++) {
388 Debug.Assert(nsList[i].NodeType == QilNodeType.NamespaceDecl);
389 Debug.Assert(varList[i].NodeType == QilNodeType.NamespaceDecl);
391 ((QilLiteral)((QilBinary)nsList[i]).Right).Value != ((QilLiteral)((QilBinary)varList[i]).Right).Value ||
392 ((QilLiteral)((QilBinary)nsList[i]).Left ).Value != ((QilLiteral)((QilBinary)varList[i]).Left ).Value
399 return var; // Found!
402 QilIterator newVar = f.Let(nsList);
403 newVar.DebugName = f.QName("ns" + this.nsVars.Count, XmlReservedNs.NsXslDebug).ToString();
404 this.gloVars.Add(newVar);
405 this.nsVars.Add(newVar);
409 private void PrecompileProtoTemplatesHeaders() {
410 // All global variables should be in scoupe here.
411 List<VarPar> paramWithCalls = null;
412 Dictionary<VarPar, Template > paramToTemplate = null;
413 Dictionary<VarPar, QilFunction> paramToFunction = null;
415 foreach (ProtoTemplate tmpl in compiler.AllTemplates) {
416 Debug.Assert(tmpl != null && tmpl.Function == null);
417 Debug.Assert(tmpl.NodeType == XslNodeType.AttributeSet || tmpl.NodeType == XslNodeType.Template);
418 QilList args = f.FormalParameterList();
419 XslFlags flags = !IsDebug ? tmpl.Flags : XslFlags.FullFocus;
421 QilList nsList = EnterScope(tmpl);
422 if ((flags & XslFlags.Current) != 0) {
423 args.Add(CreateXslParam(CloneName(nameCurrent), T.NodeNotRtf));
425 if ((flags & XslFlags.Position) != 0) {
426 args.Add(CreateXslParam(CloneName(namePosition), T.DoubleX));
428 if ((flags & XslFlags.Last) != 0) {
429 args.Add(CreateXslParam(CloneName(nameLast), T.DoubleX));
431 if (IsDebug && nsList != null) {
432 // AttributeSet doesn't need this logic because: 1) it doesn't have args; 2) we merge them.
433 // SimplifiedStylesheet has nsList == null as well.
434 QilParameter ns = CreateXslParam(CloneName(nameNamespaces), T.NamespaceS);
435 ns.DefaultValue = GetNsVar(nsList);
439 Template template = tmpl as Template;
440 if (template != null) {
441 Debug.Assert(tmpl.NodeType == XslNodeType.Template);
443 CheckSingletonFocus();
444 funcFocus.StartFocus(args, flags);
445 for (int i = 0; i < tmpl.Content.Count; i++) {
446 XslNode node = tmpl.Content[i];
447 if (node.NodeType == XslNodeType.Text) {
448 // NOTE: We should take care of a bizarre case when xsl:param comes after TextCtor:
449 // <xsl:template match="/" xml:space="preserve"> <xsl:param name="par"/>
452 if (node.NodeType == XslNodeType.Param) {
453 VarPar xslPar = (VarPar)node;
455 if (scope.IsLocalVariable(xslPar.Name.LocalName, xslPar.Name.NamespaceUri)) {
456 ReportError(/*[XT0580]*/Res.Xslt_DupLocalVariable, xslPar.Name.QualifiedName);
458 QilParameter param = CreateXslParam(xslPar.Name, ChooseBestType(xslPar));
460 param.Annotation = xslPar;
461 // Actual compilation will happen in CompileProtoTemplate()
463 if ((xslPar.DefValueFlags & XslFlags.HasCalls) == 0) {
464 param.DefaultValue = CompileVarParValue(xslPar);
466 // We can't compile param default value here because it contains xsl:call-template and
467 // we will not be able to compile any calls befor we finish with all headers
468 // So we compile this default value as a call to helper function. Now we create header for this function
469 // and preserve this param in paramWithCall list. Later in this function we finaly compile all preserved
470 // parameters and set resulted default values as helper function definition.
471 QilList paramFormal = f.FormalParameterList();
472 QilList paramActual = f.ActualParameterList();
473 for (int j = 0; j < args.Count; j ++) {
474 QilParameter formal = f.Parameter(args[j].XmlType); {
475 formal.DebugName = ((QilParameter) args[j]).DebugName;
476 formal.Name = CloneName(((QilParameter)args[j]).Name);
477 SetLineInfo(formal, args[j].SourceLine);
479 paramFormal.Add(formal);
480 paramActual.Add(args[j]);
482 // Param doesn't know what implicit args it needs, so we pass all implicit args that was passed to its template.
483 // let's reflect this fact in parans FocusFlags:
484 xslPar.Flags |= (template.Flags & XslFlags.FocusFilter);
485 QilFunction paramFunc = f.Function(paramFormal,
486 f.Boolean((xslPar.DefValueFlags & XslFlags.SideEffects) != 0),
487 ChooseBestType(xslPar)
489 paramFunc.SourceLine = SourceLineInfo.NoSource;
490 paramFunc.DebugName = "<xsl:param name=\"" + xslPar.Name.QualifiedName + "\">";
491 param.DefaultValue = f.Invoke(paramFunc, paramActual);
492 // store VarPar here to compile it on next pass:
493 if (paramWithCalls == null) {
494 paramWithCalls = new List<VarPar>();
495 paramToTemplate = new Dictionary<VarPar, Template>();
496 paramToFunction = new Dictionary<VarPar, QilFunction>();
498 paramWithCalls.Add(xslPar);
499 paramToTemplate.Add(xslPar, template );
500 paramToFunction.Add(xslPar, paramFunc);
503 SetLineInfo(param, xslPar.SourceLine);
505 scope.AddVariable(xslPar.Name, param);
511 funcFocus.StopFocus();
515 tmpl.Function = f.Function(args,
516 f.Boolean((tmpl.Flags & XslFlags.SideEffects) != 0),
517 tmpl is AttributeSet ? T.AttributeS : T.NodeNotRtfS
519 tmpl.Function.DebugName = tmpl.GetDebugName();
520 Debug.Assert((template != null) == (tmpl.SourceLine != null), "Templates must have line information, and attribute sets must not");
521 SetLineInfo(tmpl.Function, tmpl.SourceLine ?? SourceLineInfo.NoSource);
522 this.functions.Add(tmpl.Function);
523 } // foreach (ProtoTemplate tmpl in compiler.AllTemplates)
525 // Finish compiling postponed parameters (those having calls in their default values)
526 if (paramWithCalls != null) {
527 Debug.Assert(! IsDebug, "In debug mode we don't generate parumWithCalls functions. Otherwise focus flags should be adjusted");
528 foreach (VarPar par in paramWithCalls) {
529 Template tmpl = paramToTemplate[par];
530 QilFunction func = paramToFunction[par];
531 CheckSingletonFocus();
532 funcFocus.StartFocus(func.Arguments, par.Flags);
535 foreach (QilParameter arg in func.Arguments) {
536 scope.AddVariable(arg.Name, arg);
538 func.Definition = CompileVarParValue(par);
539 SetLineInfo(func.Definition, par.SourceLine);
542 funcFocus.StopFocus();
543 this.functions.Add(func);
548 private QilParameter CreateXslParam(QilName name, XmlQueryType xt) {
549 QilParameter arg = f.Parameter(xt);
550 arg.DebugName = name.ToString();
555 private void CompileProtoTemplate(ProtoTemplate tmpl) {
556 Debug.Assert(tmpl != null && tmpl.Function != null && tmpl.Function.Definition.NodeType == QilNodeType.Unknown);
560 CheckSingletonFocus();
561 funcFocus.StartFocus(tmpl.Function.Arguments, !IsDebug ? tmpl.Flags : XslFlags.FullFocus);
562 foreach (QilParameter arg in tmpl.Function.Arguments) {
563 if (arg.Name.NamespaceUri != XmlReservedNs.NsXslDebug) {
564 Debug.Assert(tmpl is Template, "Only templates can have explicit arguments");
566 Debug.Assert(arg.DefaultValue == null, "Argument must not be compiled yet");
567 VarPar xslParam = (VarPar)arg.Annotation;
568 QilList nsListParam = EnterScope(xslParam);
569 arg.DefaultValue = CompileVarParValue(xslParam);
571 arg.DefaultValue = SetDebugNs(arg.DefaultValue, nsListParam);
573 // in !IsDebug we compile argument default value in PrecompileProtoTemplatesHeaders()
575 scope.AddVariable(arg.Name, arg);
578 tmpl.Function.Definition = CompileInstructions(tmpl.Content);
579 // tmpl.Function.Definition = AddCurrentPositionLast(tmpl.Function.Definition); We don't mask Cur,Pos,Last parameters with Cur,Pos,Last wariables any more
580 // tmpl.Function.Definition = SetDebugNs(tmpl.Function.Definition, nsList); We add it as parameter now.
581 funcFocus.StopFocus();
586 private QilList InstructionList() {
587 return f.BaseFactory.Sequence();
590 private QilNode CompileInstructions(IList<XslNode> instructions) {
591 return CompileInstructions(instructions, 0, InstructionList());
594 private QilNode CompileInstructions(IList<XslNode> instructions, int from) {
595 return CompileInstructions(instructions, from, InstructionList());
598 private QilNode CompileInstructions(IList<XslNode> instructions, QilList content) {
599 return CompileInstructions(instructions, 0, content);
602 private QilNode CompileInstructions(IList<XslNode> instructions, int from, QilList content) {
603 Debug.Assert(instructions != null);
604 for (int i = from; i < instructions.Count; i++) {
605 XslNode node = instructions[i];
606 XslNodeType nodeType = node.NodeType;
607 if (nodeType == XslNodeType.Param) {
608 continue; // already compiled by CompileProtoTemplate()
610 QilList nsList = EnterScope(node);
614 case XslNodeType.ApplyImports: result = CompileApplyImports (node); break;
615 case XslNodeType.ApplyTemplates: result = CompileApplyTemplates ((XslNodeEx)node); break;
616 case XslNodeType.Attribute: result = CompileAttribute ((NodeCtor)node); break;
617 case XslNodeType.CallTemplate: result = CompileCallTemplate ((XslNodeEx)node); break;
618 case XslNodeType.Choose: result = CompileChoose (node); break;
619 case XslNodeType.Comment: result = CompileComment (node); break;
620 case XslNodeType.Copy: result = CompileCopy (node); break;
621 case XslNodeType.CopyOf: result = CompileCopyOf (node); break;
622 case XslNodeType.Element: result = CompileElement ((NodeCtor)node); break;
623 case XslNodeType.Error: result = CompileError (node); break;
624 case XslNodeType.ForEach: result = CompileForEach ((XslNodeEx)node); break;
625 case XslNodeType.If: result = CompileIf (node); break;
626 case XslNodeType.List: result = CompileList (node); break;
627 case XslNodeType.LiteralAttribute: result = CompileLiteralAttribute(node); break;
628 case XslNodeType.LiteralElement: result = CompileLiteralElement (node); break;
629 case XslNodeType.Message: result = CompileMessage (node); break;
630 case XslNodeType.Nop: result = CompileNop (node); break;
631 case XslNodeType.Number: result = CompileNumber ((Number)node); break;
632 // case XslNodeType.Otherwise: wrapped by Choose
633 // case XslNodeType.Param: already compiled by CompileProtoTemplate()
634 case XslNodeType.PI: result = CompilePI (node); break;
635 // case XslNodeType.Sort: wrapped by ForEach or ApplyTemplates, see CompileSorts()
636 // case XslNodeType.Template: global level element
637 case XslNodeType.Text: result = CompileText ((Text)node); break;
638 case XslNodeType.UseAttributeSet: result = CompileUseAttributeSet (node); break;
639 case XslNodeType.ValueOf: result = CompileValueOf (node); break;
640 case XslNodeType.ValueOfDoe: result = CompileValueOfDoe (node); break;
641 case XslNodeType.Variable: result = CompileVariable (node); break;
642 // case XslNodeType.WithParam: wrapped by CallTemplate or ApplyTemplates, see CompileWithParam()
643 default: Debug.Fail("Unexpected type of AST node: " + nodeType.ToString()); result = null; break;
647 Debug.Assert(result != null, "Result of compilation should not be null");
648 if (result.NodeType == QilNodeType.Sequence && result.Count == 0) {
652 // Do not create sequence points for literal attributes and use-attribute-sets
653 if (nodeType != XslNodeType.LiteralAttribute && nodeType != XslNodeType.UseAttributeSet) {
654 SetLineInfoCheck(result, node.SourceLine);
657 result = SetDebugNs(result, nsList);
658 if (nodeType == XslNodeType.Variable) {
659 QilIterator var = f.Let(result);
660 var.DebugName = node.Name.ToString();
661 scope.AddVariable(node.Name, var);
662 // Process all remaining instructions in the recursive call
663 result = f.Loop(var, CompileInstructions(instructions, i + 1));
664 i = instructions.Count;
669 if (!IsDebug && content.Count == 1) {
675 private QilNode CompileList(XslNode node) {
676 return CompileInstructions(node.Content);
679 private QilNode CompileNop(XslNode node) {
680 return f.Nop(f.Sequence());
683 private void AddNsDecl(QilList content, string prefix, string nsUri) {
684 if (this.outputScope.LookupNamespace(prefix) == nsUri) {
685 return; // This prefix is already bound to required namespace. Nothing to do.
687 this.outputScope.AddNamespace(prefix, nsUri);
688 content.Add(f.NamespaceDecl(f.String(prefix), f.String(nsUri)));
691 private QilNode CompileLiteralElement(XslNode node) {
692 // IlGen requires that namespace declarations do not conflict with the namespace used by the element
693 // constructor, see XmlILNamespaceAnalyzer.CheckNamespaceInScope() and SQLBUDT 389481, 389482. First
694 // we try to replace all prefixes bound to aliases by result-prefixes of the corresponding
695 // xsl:namespace-alias instructions. If there is at least one conflict, we leave all prefixes
696 // untouched, changing only namespace URIs.
697 bool changePrefixes = true;
700 prefixesInUse.Clear();
702 QilName qname = node.Name;
703 string prefix = qname.Prefix;
704 string nsUri = qname.NamespaceUri;
706 compiler.ApplyNsAliases(ref prefix, ref nsUri);
707 if (changePrefixes) {
708 prefixesInUse.Add(prefix, nsUri);
710 prefix = qname.Prefix;
713 outputScope.PushScope();
715 // Declare all namespaces that should be declared
716 // <spec>http://www.w3.org/TR/xslt.html#literal-result-element</spec>
717 QilList nsList = InstructionList();
718 foreach (ScopeRecord rec in this.scope) {
719 string recPrefix = rec.ncName;
720 string recNsUri = rec.nsUri;
721 if (recNsUri != XmlReservedNs.NsXslt && !scope.IsExNamespace(recNsUri)) {
722 compiler.ApplyNsAliases(ref recPrefix, ref recNsUri);
723 if (changePrefixes) {
724 if (prefixesInUse.Contains(recPrefix)) {
725 if ((string)prefixesInUse[recPrefix] != recNsUri) {
726 // Found a prefix conflict. Start again from the beginning leaving all prefixes untouched.
727 outputScope.PopScope();
728 changePrefixes = false;
732 prefixesInUse.Add(recPrefix, recNsUri);
735 recPrefix = rec.ncName;
737 AddNsDecl(nsList, recPrefix, recNsUri);
741 QilNode content = CompileInstructions(node.Content, nsList);
742 outputScope.PopScope();
744 qname.Prefix = prefix;
745 qname.NamespaceUri = nsUri;
746 return f.ElementCtor(qname, content);
749 private QilNode CompileElement(NodeCtor node) {
750 QilNode qilNs = CompileStringAvt(node.NsAvt);
751 QilNode qilName = CompileStringAvt(node.NameAvt);
754 if (qilName.NodeType == QilNodeType.LiteralString && (qilNs == null || qilNs.NodeType == QilNodeType.LiteralString)) {
755 string name = (string)(QilLiteral)qilName;
756 string prefix, local, nsUri;
758 bool isValid = compiler.ParseQName(name, out prefix, out local, (IErrorHelper)this);
761 nsUri = isValid ? ResolvePrefix(/*ignoreDefaultNs:*/false, prefix) : compiler.CreatePhantomNamespace();
763 nsUri = (string)(QilLiteral)qilNs;
765 qname = f.QName(local, nsUri, prefix);
766 } else { // Process AVT
768 qname = f.StrParseQName(qilName, qilNs);
770 qname = ResolveQNameDynamic(/*ignoreDefaultNs:*/false, qilName);
774 outputScope.PushScope();
775 // ToDo if we don't have AVT we shouldn't do this:
776 this.outputScope.InvalidateAllPrefixes();
777 QilNode content = CompileInstructions(node.Content);
778 outputScope.PopScope();
780 return f.ElementCtor(qname, content);
783 private QilNode CompileLiteralAttribute(XslNode node) {
784 QilName qname = node.Name;
785 string prefix = qname.Prefix;
786 string nsUri = qname.NamespaceUri;
787 // The default namespace do not apply directly to attributes
788 if (prefix.Length != 0) {
789 compiler.ApplyNsAliases(ref prefix, ref nsUri);
791 qname.Prefix = prefix;
792 qname.NamespaceUri = nsUri;
793 return f.AttributeCtor(qname, CompileTextAvt(node.Select));
796 private QilNode CompileAttribute(NodeCtor node) {
797 QilNode qilNs = CompileStringAvt(node.NsAvt);
798 QilNode qilName = CompileStringAvt(node.NameAvt);
800 bool explicitNamespace = false;
802 if (qilName.NodeType == QilNodeType.LiteralString && (qilNs == null || qilNs.NodeType == QilNodeType.LiteralString)) {
803 string name = (string)(QilLiteral)qilName;
804 string prefix, local, nsUri;
806 bool isValid = compiler.ParseQName(name, out prefix, out local, (IErrorHelper)this);
809 nsUri = isValid ? ResolvePrefix(/*ignoreDefaultNs:*/true, prefix) : compiler.CreatePhantomNamespace();
811 nsUri = (string)(QilLiteral)qilNs;
812 // if both name and ns are non AVT and this ns is already bind to the same prefix we can avoid reseting ns management
813 explicitNamespace = true;
815 // Check the case <xsl:attribute name="foo:xmlns" namespace=""/>
816 if (name == "xmlns" || local == "xmlns" && nsUri.Length == 0) {
817 ReportError(/*[XT_031]*/Res.Xslt_XmlnsAttr, "name", name);
819 qname = f.QName(local, nsUri, prefix);
823 qname = f.StrParseQName(qilName, qilNs);
825 qname = ResolveQNameDynamic(/*ignoreDefaultNs:*/true, qilName);
828 if (explicitNamespace) {
829 // Optimization: attribute cannot change the default namespace
830 this.outputScope.InvalidateNonDefaultPrefixes();
832 return f.AttributeCtor(qname, CompileInstructions(node.Content));
835 private readonly StringBuilder unescapedText = new StringBuilder();
837 private QilNode ExtractText(string source, ref int pos) {
838 Debug.Assert(pos < source.Length);
841 unescapedText.Length = 0;
842 for (i = pos; i < source.Length; i++) {
845 if (ch == '{' || ch == '}') {
846 if (i + 1 < source.Length && source[i + 1] == ch) { // "{{" or "}}"
847 // Double curly brace outside an expression is replaced by a single one
849 unescapedText.Append(source, start, i - start);
851 } else if (ch == '{') { // single '{'
852 // Expression encountered, returning
854 } else { // single '}'
855 // Single '}' outside an expression is an error
857 if (xslVersion != XslVersion.ForwardsCompatible) {
858 ReportError(/*[XT0370]*/Res.Xslt_SingleRightBraceInAvt, source);
861 return f.Error(lastScope.SourceLine, Res.Xslt_SingleRightBraceInAvt, source);
866 Debug.Assert(i == source.Length || source[i] == '{');
868 if (unescapedText.Length == 0) {
869 return i > start ? f.String(source.Substring(start, i - start)) : null;
871 unescapedText.Append(source, start, i - start);
872 return f.String(unescapedText.ToString());
876 private QilNode CompileAvt(string source) {
877 QilList result = f.BaseFactory.Sequence();
879 while (pos < source.Length) {
880 QilNode fixedPart = ExtractText(source, ref pos);
881 if (fixedPart != null) {
882 result.Add(fixedPart);
884 if (pos < source.Length) { // '{' encountered, parse an expression
886 QilNode exp = CompileXPathExpressionWithinAvt(source, ref pos);
887 result.Add(f.ConvertToString(exp));
890 if (result.Count == 1) {
896 static readonly char[] curlyBraces = {'{', '}'};
898 private QilNode CompileStringAvt(string avt) {
902 if (avt.IndexOfAny(curlyBraces) == -1) {
903 return f.String(avt);
905 return f.StrConcat(CompileAvt(avt));
908 private QilNode CompileTextAvt(string avt) {
909 Debug.Assert(avt != null);
910 if (avt.IndexOfAny(curlyBraces) == -1) {
911 return f.TextCtor(f.String(avt));
913 QilNode avtParts = CompileAvt(avt);
914 if (avtParts.NodeType == QilNodeType.Sequence) {
915 QilList result = InstructionList();
916 foreach (QilNode node in avtParts) {
917 result.Add(f.TextCtor(node));
921 return f.TextCtor(avtParts);
925 private QilNode CompileText(Text node) {
926 if (node.Hints == SerializationHints.None)
927 return f.TextCtor(f.String(node.Select));
929 return f.RawTextCtor(f.String(node.Select));
932 private QilNode CompilePI(XslNode node) {
933 QilNode qilName = CompileStringAvt(node.Select);
934 if (qilName.NodeType == QilNodeType.LiteralString) {
935 string name = (string)(QilLiteral)qilName;
936 compiler.ValidatePiName(name, (IErrorHelper)this);
938 return f.PICtor(qilName, CompileInstructions(node.Content));
941 private QilNode CompileComment(XslNode node) {
942 return f.CommentCtor(CompileInstructions(node.Content));
945 private QilNode CompileError(XslNode node) {
946 return f.Error(f.String(node.Select));
949 private QilNode WrapLoopBody(ISourceLineInfo before, QilNode expr, ISourceLineInfo after) {
950 Debug.Assert(curLoop.IsFocusSet);
953 SetLineInfo(InvokeOnCurrentNodeChanged(), before),
955 SetLineInfo(f.Nop(f.Sequence()), after)
961 private QilNode CompileForEach(XslNodeEx node) {
963 IList<XslNode> content = node.Content;
965 // Push new loop frame on the stack
966 LoopFocus curLoopSaved = curLoop;
967 QilIterator it = f.For(CompileNodeSetExpression(node.Select));
968 curLoop.SetFocus(it);
970 // Compile sort keys and body
971 int varScope = varHelper.StartVariables();
972 curLoop.Sort(CompileSorts(content, ref curLoopSaved));
973 result = CompileInstructions(content);
974 result = WrapLoopBody(node.ElemNameLi, result, node.EndTagLi);
975 result = AddCurrentPositionLast(result);
976 result = curLoop.ConstructLoop(result);
977 result = varHelper.FinishVariables(result, varScope);
980 curLoop = curLoopSaved;
984 private QilNode CompileApplyTemplates(XslNodeEx node) {
986 IList<XslNode> content = node.Content;
988 // Calculate select expression
989 int varScope = varHelper.StartVariables();
991 QilIterator select = f.Let(CompileNodeSetExpression(node.Select));
992 varHelper.AddVariable(select);
994 // Compile with-param's, they must be calculated outside the loop and
995 // if they are neither constant nor reference we need to cache them in Let's
996 for (int i = 0; i < content.Count; i++) {
997 VarPar withParam = content[i] as VarPar;
998 if (withParam != null) {
999 Debug.Assert(withParam.NodeType == XslNodeType.WithParam);
1000 CompileWithParam(withParam);
1001 QilNode val = withParam.Value;
1002 if (IsDebug || !(val is QilIterator || val is QilLiteral)) {
1003 QilIterator let = f.Let(val);
1004 let.DebugName = f.QName("with-param " + withParam.Name.QualifiedName, XmlReservedNs.NsXslDebug).ToString();
1005 varHelper.AddVariable(let);
1006 withParam.Value = let;
1011 // Push new loop frame on the stack
1012 LoopFocus curLoopSaved = curLoop;
1013 QilIterator it = f.For(select);
1014 curLoop.SetFocus(it);
1016 // Compile sort keys and body
1017 curLoop.Sort(CompileSorts(content, ref curLoopSaved));
1019 result = GenerateApply(compiler.Root, node);
1021 result = WrapLoopBody(node.ElemNameLi, result, node.EndTagLi);
1022 result = AddCurrentPositionLast(result);
1023 result = curLoop.ConstructLoop(result);
1026 curLoop = curLoopSaved;
1028 result = varHelper.FinishVariables(result, varScope);
1032 private QilNode CompileApplyImports(XslNode node) {
1033 Debug.Assert(node.NodeType == XslNodeType.ApplyImports);
1034 Debug.Assert(!curLoop.IsFocusSet, "xsl:apply-imports cannot be inside of xsl:for-each");
1036 return GenerateApply((StylesheetLevel)node.Arg, node);
1039 private QilNode CompileCallTemplate(XslNodeEx node) {
1040 VerifyXPathQName(node.Name);
1041 int varScope = varHelper.StartVariables();
1043 IList<XslNode> content = node.Content;
1044 foreach (VarPar withParam in content) {
1045 CompileWithParam(withParam);
1046 // In debug mode precalculate all with-param's
1048 QilNode val = withParam.Value;
1049 QilIterator let = f.Let(val);
1050 let.DebugName = f.QName("with-param " + withParam.Name.QualifiedName, XmlReservedNs.NsXslDebug).ToString();
1051 varHelper.AddVariable(let);
1052 withParam.Value = let;
1058 if (compiler.NamedTemplates.TryGetValue(node.Name, out tmpl)) {
1059 Debug.Assert(tmpl.Function != null, "All templates should be already compiled");
1060 result = invkGen.GenerateInvoke(tmpl.Function, AddRemoveImplicitArgs(node.Content, tmpl.Flags));
1062 if (! compiler.IsPhantomName(node.Name)) {
1063 compiler.ReportError(/*[XT0710]*/node.SourceLine, Res.Xslt_InvalidCallTemplate, node.Name.QualifiedName);
1065 result = f.Sequence();
1069 // Do not create an additional sequence point if there are no parameters
1070 if (content.Count > 0) {
1071 result = SetLineInfo(result, node.ElemNameLi);
1073 result = varHelper.FinishVariables(result, varScope);
1075 return f.Nop(result);
1080 private QilNode CompileUseAttributeSet(XslNode node) {
1081 VerifyXPathQName(node.Name);
1083 outputScope.InvalidateAllPrefixes();
1085 AttributeSet attSet;
1086 if (compiler.AttributeSets.TryGetValue(node.Name, out attSet)) {
1087 Debug.Assert(attSet.Function != null, "All templates should be already compiled");
1088 return invkGen.GenerateInvoke(attSet.Function, AddRemoveImplicitArgs(node.Content, attSet.Flags));
1090 if (! compiler.IsPhantomName(node.Name)) {
1091 compiler.ReportError(/*[XT0710]*/node.SourceLine, Res.Xslt_NoAttributeSet, node.Name.QualifiedName);
1093 return f.Sequence();
1097 private const XmlNodeKindFlags InvalidatingNodes = (XmlNodeKindFlags.Attribute | XmlNodeKindFlags.Namespace);
1099 private QilNode CompileCopy(XslNode copy) {
1100 QilNode node = GetCurrentNode();
1101 f.CheckNodeNotRtf(node);
1102 if ((node.XmlType.NodeKinds & InvalidatingNodes) != XmlNodeKindFlags.None) {
1103 outputScope.InvalidateAllPrefixes();
1105 if (node.XmlType.NodeKinds == XmlNodeKindFlags.Element) {
1106 // Context node is always an element
1107 // The namespace nodes of the current node are automatically copied
1108 QilList content = InstructionList();
1109 content.Add(f.XPathNamespace(node));
1110 outputScope.PushScope();
1111 outputScope.InvalidateAllPrefixes();
1112 QilNode result = CompileInstructions(copy.Content, content);
1113 outputScope.PopScope();
1114 return f.ElementCtor(f.NameOf(node), result);
1115 } else if (node.XmlType.NodeKinds == XmlNodeKindFlags.Document) {
1116 // Context node is always a document
1117 // xsl:copy will not create a document node, but will just use the content template
1118 return CompileInstructions(copy.Content);
1119 } else if ((node.XmlType.NodeKinds & (XmlNodeKindFlags.Element | XmlNodeKindFlags.Document)) == XmlNodeKindFlags.None) {
1120 // Context node is neither an element, nor a document
1121 // The content of xsl:copy is not instantiated
1124 // Static classifying of the context node is not possible
1125 return f.XsltCopy(node, CompileInstructions(copy.Content));
1129 private QilNode CompileCopyOf(XslNode node) {
1130 QilNode selectExpr = CompileXPathExpression(node.Select);
1131 if (selectExpr.XmlType.IsNode) {
1132 if ((selectExpr.XmlType.NodeKinds & InvalidatingNodes) != XmlNodeKindFlags.None) {
1133 outputScope.InvalidateAllPrefixes();
1136 if (selectExpr.XmlType.IsNotRtf && (selectExpr.XmlType.NodeKinds & XmlNodeKindFlags.Document) == XmlNodeKindFlags.None) {
1137 // Expression returns non-document nodes only
1141 // May be an Rtf or may return Document nodes, so use XsltCopyOf operator
1142 if (selectExpr.XmlType.IsSingleton) {
1143 return f.XsltCopyOf(selectExpr);
1146 return f.Loop(it = f.For(selectExpr), f.XsltCopyOf(it));
1149 else if (selectExpr.XmlType.IsAtomicValue) {
1150 // Expression returns non-nodes only
1151 // When the result is neither a node-set nor a result tree fragment, the result is converted
1152 // to a string and then inserted into the result tree, as with xsl:value-of.
1153 return f.TextCtor(f.ConvertToString(selectExpr));
1156 // Static classifying is not possible
1158 outputScope.InvalidateAllPrefixes();
1160 it = f.For(selectExpr),
1161 f.Conditional(f.IsType(it, T.Node),
1162 f.XsltCopyOf(f.TypeAssert(it, T.Node)),
1163 f.TextCtor(f.XsltConvert(it, T.StringX))
1169 private QilNode CompileValueOf(XslNode valueOf) {
1170 return f.TextCtor(f.ConvertToString(CompileXPathExpression(/*select:*/valueOf.Select)));
1173 private QilNode CompileValueOfDoe(XslNode valueOf) {
1174 return f.RawTextCtor(f.ConvertToString(CompileXPathExpression(/*select:*/valueOf.Select)));
1177 private QilNode CompileWhen(XslNode whenNode, QilNode otherwise) {
1178 return f.Conditional(
1179 f.ConvertToBoolean(CompileXPathExpression(/*test:*/whenNode.Select)),
1180 CompileInstructions(whenNode.Content),
1185 private QilNode CompileIf(XslNode ifNode) {
1186 return CompileWhen(ifNode, InstructionList());
1189 private QilNode CompileChoose(XslNode node) {
1190 IList<XslNode> cases = node.Content;
1191 QilNode result = null;
1193 // It's easier to compile xsl:choose from bottom to top
1194 for (int i = cases.Count - 1; 0 <= i; i--) {
1195 XslNode when = cases[i];
1196 Debug.Assert(when.NodeType == XslNodeType.If || when.NodeType == XslNodeType.Otherwise);
1197 QilList nsList = EnterScope(when);
1198 if (when.NodeType == XslNodeType.Otherwise) {
1199 Debug.Assert(result == null, "xsl:otherwise must be the last child of xsl:choose");
1200 result = CompileInstructions(when.Content);
1202 result = CompileWhen(when, /*otherwise:*/result ?? InstructionList());
1205 SetLineInfoCheck(result, when.SourceLine);
1206 result = SetDebugNs(result, nsList);
1208 if (result == null) {
1209 return f.Sequence();
1211 return IsDebug ? f.Sequence(result) : result;
1214 private QilNode CompileMessage(XslNode node) {
1215 string baseUri = lastScope.SourceLine.Uri;
1216 QilNode content = f.RtfCtor(CompileInstructions(node.Content), f.String(baseUri));
1218 //content = f.ConvertToString(content);
1219 content = f.InvokeOuterXml(content);
1221 // If terminate="no", then create QilNodeType.Warning
1222 if (!(bool)node.Arg) {
1223 return f.Warning(content);
1226 // Otherwise create both QilNodeType.Warning and QilNodeType.Error
1228 return f.Loop(i = f.Let(content), f.Sequence(f.Warning(i), f.Error(i)));
1231 private QilNode CompileVariable(XslNode node) {
1232 Debug.Assert(node.NodeType == XslNodeType.Variable);
1233 if (scope.IsLocalVariable(node.Name.LocalName, node.Name.NamespaceUri)) {
1234 ReportError(/*[XT_030]*/Res.Xslt_DupLocalVariable, node.Name.QualifiedName);
1236 return CompileVarParValue(node);
1239 private QilNode CompileVarParValue(XslNode node) {
1240 Debug.Assert(node.NodeType == XslNodeType.Variable || node.NodeType == XslNodeType.Param || node.NodeType == XslNodeType.WithParam);
1241 VerifyXPathQName(node.Name);
1243 string baseUri = lastScope.SourceLine.Uri;
1244 IList<XslNode> content = node.Content;
1245 string select = node.Select;
1248 if (select != null) {
1249 // In case of incorrect stylesheet, variable or parameter may have both a 'select' attribute and non-empty content
1250 QilList list = InstructionList();
1251 list.Add(CompileXPathExpression(select));
1252 varValue = CompileInstructions(content, list);
1253 } else if (content.Count != 0) {
1254 this.outputScope.PushScope();
1255 // Rtf will be instantiated in an unknown namespace context, so we should not assume anything here
1256 this.outputScope.InvalidateAllPrefixes();
1257 varValue = f.RtfCtor(CompileInstructions(content), f.String(baseUri));
1258 this.outputScope.PopScope();
1260 varValue = f.String(string.Empty);
1263 // In debug mode every variable/param must be of type 'any'
1264 varValue = f.TypeAssert(varValue, T.ItemS);
1266 Debug.Assert(varValue.SourceLine == null);
1270 private void CompileWithParam(VarPar withParam) {
1271 Debug.Assert(withParam.NodeType == XslNodeType.WithParam);
1272 QilList nsList = EnterScope(withParam);
1273 QilNode paramValue = CompileVarParValue(withParam);
1275 SetLineInfo(paramValue, withParam.SourceLine);
1276 paramValue = SetDebugNs(paramValue, nsList);
1277 withParam.Value = paramValue;
1282 private QilNode CompileSorts(IList<XslNode> content, ref LoopFocus parentLoop) {
1283 QilList keyList = f.BaseFactory.SortKeyList();
1287 while (i < content.Count) {
1288 Sort sort = content[i] as Sort;
1290 CompileSort(sort, keyList, ref parentLoop);
1291 content.RemoveAt(i);
1297 if (keyList.Count == 0)
1303 private QilNode CompileLangAttribute(string attValue, bool fwdCompat) {
1304 QilNode result = CompileStringAvt(attValue);
1306 if (result == null) {
1308 } else if (result.NodeType == QilNodeType.LiteralString) {
1309 string lang = (string)(QilLiteral)result;
1310 int lcid = XsltLibrary.LangToLcidInternal(lang, fwdCompat, (IErrorHelper)this);
1311 if (lcid == XsltLibrary.InvariantCultureLcid) {
1315 // NOTE: We should have the same checks for both compile time and execution time
1317 result = f.Loop(i = f.Let(result),
1318 f.Conditional(f.Eq(f.InvokeLangToLcid(i, fwdCompat), f.Int32(XsltLibrary.InvariantCultureLcid)),
1319 f.String(string.Empty),
1327 private QilNode CompileLangAttributeToLcid(string attValue, bool fwdCompat) {
1328 return CompileLangToLcid(CompileStringAvt(attValue), fwdCompat);
1331 private QilNode CompileLangToLcid(QilNode lang, bool fwdCompat) {
1333 return f.Double(XsltLibrary.InvariantCultureLcid);
1334 } else if (lang.NodeType == QilNodeType.LiteralString) {
1335 return f.Double(XsltLibrary.LangToLcidInternal((string)(QilLiteral)lang, fwdCompat, (IErrorHelper)this));
1337 return f.XsltConvert(f.InvokeLangToLcid(lang, fwdCompat), T.DoubleX);
1341 private void CompileDataTypeAttribute(string attValue, bool fwdCompat, ref QilNode select, out QilNode select2) {
1342 const string DtText = "text";
1343 const string DtNumber = "number";
1344 QilNode result = CompileStringAvt(attValue);
1345 if (result != null) {
1346 if (result.NodeType == QilNodeType.LiteralString) {
1347 string dataType = (string)(QilLiteral)result;
1348 if (dataType == DtNumber) {
1349 select = f.ConvertToNumber(select);
1352 } else if (dataType == DtText) {
1353 // fall through to default case
1356 // check for qname-but-not-ncname
1357 string prefix, local, nsUri;
1358 bool isValid = compiler.ParseQName(dataType, out prefix, out local, (IErrorHelper)this);
1359 nsUri = isValid ? ResolvePrefix(/*ignoreDefaultNs:*/true, prefix) : compiler.CreatePhantomNamespace();
1361 if (nsUri.Length == 0) {
1362 // this is a ncname; we might report Res.Xslt_InvalidAttrValue,
1363 // but the following error message is more user friendly
1365 ReportError(/*[XT_034]*/Res.Xslt_BistateAttribute, "data-type", DtText, DtNumber);
1367 // fall through to default case
1370 // Precalculate its value outside of for-each loop
1371 QilIterator dt, qname;
1373 result = f.Loop(dt = f.Let(result),
1374 f.Conditional(f.Eq(dt, f.String(DtNumber)), f.False(),
1375 f.Conditional(f.Eq(dt, f.String(DtText)), f.True(),
1376 fwdCompat ? f.True() :
1377 f.Loop(qname = f.Let(ResolveQNameDynamic(/*ignoreDefaultNs:*/true, dt)),
1378 f.Error(lastScope.SourceLine,
1379 Res.Xslt_BistateAttribute, "data-type", DtText, DtNumber
1384 QilIterator text = f.Let(result);
1385 varHelper.AddVariable(text);
1387 // Make two sort keys since heterogenous sort keys are not allowed
1388 select2 = select.DeepClone(f.BaseFactory);
1389 select = f.Conditional(text, f.ConvertToString(select), f.String(string.Empty) );
1390 select2 = f.Conditional(text, f.Double(0), f.ConvertToNumber(select2));
1396 select = f.ConvertToString(select);
1401 /// Compiles AVT with two possible values
1403 /// <param name="attName" >NodeCtor name (used for constructing an error message)</param>
1404 /// <param name="attValue" >NodeCtor value</param>
1405 /// <param name="value0" >First possible value of attribute</param>
1406 /// <param name="value1" >Second possible value of attribute</param>
1407 /// <param name="fwdCompat">If true, unrecognized value does not report an error</param>
1409 /// If AVT is null (i.e. the attribute is omitted), null is returned. Otherwise, QilExpression
1410 /// returning "1" if AVT evaluates to value1, or "0" if AVT evaluates to value0 or any other value.
1411 /// If AVT evaluates to neither value0 nor value1 and fwdCompat == false, an error is reported.
1413 private QilNode CompileOrderAttribute(string attName, string attValue, string value0, string value1, bool fwdCompat) {
1414 QilNode result = CompileStringAvt(attValue);
1415 if (result != null) {
1416 if (result.NodeType == QilNodeType.LiteralString) {
1417 string value = (string)(QilLiteral)result;
1418 if (value == value1) {
1419 result = f.String("1");
1421 if (value != value0 && !fwdCompat) {
1422 ReportError(/*[XT_034]*/Res.Xslt_BistateAttribute, attName, value0, value1);
1424 result = f.String("0");
1428 result = f.Loop(i = f.Let(result),
1429 f.Conditional(f.Eq(i, f.String(value1)), f.String("1"),
1430 fwdCompat ? f.String("0") :
1431 f.Conditional(f.Eq(i, f.String(value0)), f.String("0"),
1432 f.Error(lastScope.SourceLine,
1433 Res.Xslt_BistateAttribute, attName, value0, value1
1437 Debug.Assert(result.XmlType == T.StringX);
1442 private void CompileSort(Sort sort, QilList keyList, ref LoopFocus parentLoop) {
1443 Debug.Assert(sort.NodeType == XslNodeType.Sort);
1444 QilNode select, select2, lang, order, caseOrder;
1448 fwdCompat = sort.ForwardsCompatible;
1450 select = CompileXPathExpression(sort.Select);
1452 if (sort.Lang != null || sort.DataType != null || sort.Order != null || sort.CaseOrder != null) {
1453 // Calculate these attributes in the context of the parent loop
1454 LoopFocus curLoopSaved = curLoop;
1455 curLoop = parentLoop;
1457 lang = CompileLangAttribute(sort.Lang, fwdCompat);
1459 CompileDataTypeAttribute(sort.DataType, fwdCompat, ref select, out select2);
1461 order = CompileOrderAttribute(
1462 /*attName: */ "order",
1463 /*attValue: */ sort.Order,
1464 /*value0: */ "ascending",
1465 /*value1: */ "descending",
1466 /*fwdCompat:*/ fwdCompat
1469 caseOrder = CompileOrderAttribute(
1470 /*attName: */ "case-order",
1471 /*attValue: */ sort.CaseOrder,
1472 /*value0: */ "lower-first",
1473 /*value1: */ "upper-first",
1474 /*fwdCompat:*/ fwdCompat
1477 // Restore loop context
1478 curLoop = curLoopSaved;
1480 select = f.ConvertToString(select);
1481 select2 = lang = order = caseOrder = null;
1485 strConcat.Append(XmlReservedNs.NsCollationBase);
1486 strConcat.Append('/');
1487 strConcat.Append(lang);
1489 char separator = '?';
1490 if (order != null) {
1491 strConcat.Append(separator);
1492 strConcat.Append("descendingOrder=");
1493 strConcat.Append(order);
1496 if (caseOrder != null) {
1497 strConcat.Append(separator);
1498 strConcat.Append("upperFirst=");
1499 strConcat.Append(caseOrder);
1503 QilNode collation = strConcat.ToQil();
1505 QilSortKey result = f.SortKey(select, collation);
1506 // Line information is not copied
1507 keyList.Add(result);
1509 if (select2 != null) {
1510 result = f.SortKey(select2, collation.DeepClone(f.BaseFactory));
1511 // Line information is not copied
1512 keyList.Add(result);
1518 private QilNode MatchPattern(QilNode pattern, QilIterator testNode) {
1520 if (pattern.NodeType == QilNodeType.Error) {
1523 } else if (pattern.NodeType == QilNodeType.Sequence) {
1524 list = (QilList)pattern;
1525 Debug.Assert(0 < list.Count, "Pattern should have at least one filter");
1527 list = f.BaseFactory.Sequence();
1531 QilNode result = f.False();
1532 for (int i = list.Count - 1; 0 <= i; i --) {
1533 QilLoop filter = (QilLoop) list[i];
1534 ptrnBuilder.AssertFilter(filter);
1536 refReplacer.Replace(filter.Body, filter.Variable, testNode),
1543 private QilNode MatchCountPattern(QilNode countPattern, QilIterator testNode) {
1545 If the 'count' attribute is not specified, then it defaults to the pattern that matches any node
1546 with the same node kind as the context node and, if the context node has an expanded-QName, with
1547 the same expanded-QName as the context node.
1549 if (countPattern != null) {
1550 return MatchPattern(countPattern, testNode);
1552 QilNode current = GetCurrentNode();
1554 XmlNodeKindFlags nodeKinds = current.XmlType.NodeKinds;
1556 // If node kind is not known, invoke a runtime function
1557 if ((nodeKinds & (nodeKinds - 1)) != 0) {
1558 return f.InvokeIsSameNodeSort(testNode, current);
1561 // Otherwise generate IsType check along with expanded QName check
1562 switch (nodeKinds) {
1563 case XmlNodeKindFlags.Document : return f.IsType(testNode, T.Document);
1564 case XmlNodeKindFlags.Element : result = f.IsType(testNode, T.Element); break;
1565 case XmlNodeKindFlags.Attribute : result = f.IsType(testNode, T.Attribute); break;
1566 case XmlNodeKindFlags.Text : return f.IsType(testNode, T.Text);
1567 case XmlNodeKindFlags.Comment : return f.IsType(testNode, T.Comment);
1568 case XmlNodeKindFlags.PI : return f.And(f.IsType(testNode, T.PI) , f.Eq(f.LocalNameOf(testNode), f.LocalNameOf(current)));
1569 case XmlNodeKindFlags.Namespace : return f.And(f.IsType(testNode, T.Namespace), f.Eq(f.LocalNameOf(testNode), f.LocalNameOf(current)));
1571 Debug.Fail("Unexpected NodeKind: " + nodeKinds.ToString());
1575 // Elements and attributes have both local name and namespace URI
1576 return f.And(result, f.And(
1577 f.Eq(f.LocalNameOf(testNode) , f.LocalNameOf(current)),
1578 f.Eq(f.NamespaceUriOf(testNode), f.NamespaceUriOf(GetCurrentNode()))
1583 private QilNode PlaceMarker(QilNode countPattern, QilNode fromPattern, bool multiple) {
1585 Quotation from XSLT 2.0 spec:
1586 * Let $A be the node sequence selected by the expression
1587 ancestor-or-self::node()[matches-count(.)] (level = "multiple")
1588 ancestor-or-self::node()[matches-count(.)][1] (level = "single")
1589 * Let $F be the node sequence selected by the expression
1590 ancestor-or-self::node()[matches-from(.)][1]
1591 * Let $AF be the value of
1592 $A intersect ($F/descendant-or-self::node())
1593 * Return the result of the expression
1594 for $af in $AF return 1+count($af/preceding-sibling::node()[matches-count(.)])
1596 NOTE: There are some distinctions between XSLT 1.0 and XSLT 2.0 specs. In our 1.0 implementation we:
1597 1) Assume that the 'matches-from()' function does not match root nodes by default.
1598 2) Instead of '$A intersect ($F/descendant-or-self::node())' (which, by the way,
1599 would filter out attribute and namespace nodes from $A) we calculate
1600 '$A' if the 'from' attribute is omitted,
1601 '$A[. >> $F]' if the 'from' attribute is present.
1604 QilNode countPattern2, countMatches, fromMatches, A, F, AF;
1607 countPattern2 = (countPattern != null) ? countPattern.DeepClone(f.BaseFactory) : null;
1608 countMatches = f.Filter(i = f.For(f.AncestorOrSelf(GetCurrentNode())), MatchCountPattern(countPattern, i));
1610 A = f.DocOrderDistinct(countMatches);
1612 A = f.Filter(i = f.For(countMatches), f.Eq(f.PositionOf(i), f.Int32(1)));
1615 if (fromPattern == null) {
1618 fromMatches = f.Filter(i = f.For(f.AncestorOrSelf(GetCurrentNode())), MatchPattern(fromPattern, i));
1619 F = f.Filter(i = f.For(fromMatches), f.Eq(f.PositionOf(i), f.Int32(1)));
1620 AF = f.Loop(i = f.For(F), f.Filter(j = f.For(A), f.Before(i, j)));
1623 return f.Loop(j = f.For(AF),
1624 f.Add(f.Int32(1), f.Length(f.Filter(i = f.For(f.PrecedingSibling(j)), MatchCountPattern(countPattern2, i))))
1628 private QilNode PlaceMarkerAny(QilNode countPattern, QilNode fromPattern) {
1630 Quotation from XSLT 2.0 spec:
1631 * If the context node is a document node, return the empty sequence, ()
1632 * Let $A be the node sequence selected by the expression
1633 (preceding::node()|ancestor-or-self::node())[matches-count(.)]
1634 * Let $F be the node sequence selected by the expression
1635 (preceding::node()|ancestor::node())[matches-from(.)][last()]
1636 * Let $AF be the node sequence $A[. is $F or . >> $F].
1637 * If $AF is empty, return the empty sequence, ()
1638 * Otherwise return the value of the expression count($AF)
1640 NOTE: There are some distinctions between XSLT 1.0 and XSLT 2.0 specs. In our 1.0 implementation we:
1641 1) Assume that the 'matches-from()' function does not match root nodes by default.
1642 2) Instead of '$A[. is $F or . >> $F]' we calculate
1643 '$A' if the 'from' attribute is omitted,
1644 '$A[. >> $F]' if the 'from' attribute is present.
1647 QilNode range, fromMatches, F, AF;
1648 QilIterator i, j, k;
1650 if (fromPattern == null) {
1651 // According to XSLT 2.0 spec, if the 'from' attribute is omitted, matches-from() returns true
1652 // only for the root node. It means $F is a sequence of length one containing the root node,
1653 // and $AF = $A. XSLT 1.0 spec rules lead to the same result $AF = $A, so two specs are compliant here.
1654 range = f.NodeRange(f.Root(GetCurrentNode()), GetCurrentNode());
1655 AF = f.Filter(i = f.For(range), MatchCountPattern(countPattern, i));
1657 fromMatches = f.Filter(i = f.For(f.Preceding(GetCurrentNode())), MatchPattern(fromPattern, i));
1658 F = f.Filter(i = f.For(fromMatches), f.Eq(f.PositionOf(i), f.Int32(1)));
1659 AF = f.Loop(i = f.For(F),
1660 f.Filter(j = f.For(f.Filter(k = f.For(f.NodeRange(i, GetCurrentNode())), MatchCountPattern(countPattern, k))),
1666 return f.Loop(k = f.Let(f.Length(AF)),
1667 f.Conditional(f.Eq(k, f.Int32(0)), f.Sequence(),
1672 // Returns one of XsltLibrary.LetterValue enum values
1673 private QilNode CompileLetterValueAttribute(string attValue, bool fwdCompat) {
1674 const string Default = "default";
1675 const string Alphabetic = "alphabetic";
1676 const string Traditional = "traditional";
1679 QilNode result = CompileStringAvt(attValue);
1681 if (result != null) {
1682 if (result.NodeType == QilNodeType.LiteralString) {
1683 letterValue = (string)(QilLiteral)result;
1684 if (letterValue != Alphabetic && letterValue != Traditional) {
1686 ReportError(/*[XT_034]*/Res.Xslt_BistateAttribute, "letter-value", Alphabetic, Traditional);
1689 // Use default value
1690 return f.String(Default);
1695 QilIterator i = f.Let(result);
1698 f.Or(f.Eq(i, f.String(Alphabetic)), f.Eq(i, f.String(Traditional))),
1700 fwdCompat ? f.String(Default) :
1701 f.Error(lastScope.SourceLine, Res.Xslt_BistateAttribute, "letter-value", Alphabetic, Traditional)
1705 return f.String(Default);
1708 private QilNode CompileGroupingSeparatorAttribute(string attValue, bool fwdCompat) {
1709 QilNode result = CompileStringAvt(attValue);
1711 if (result == null) {
1712 // NOTE: string.Empty value denotes unspecified attribute
1713 result = f.String(string.Empty);
1714 } else if (result.NodeType == QilNodeType.LiteralString) {
1715 string value = (string)(QilLiteral)result;
1716 if (value.Length != 1) {
1718 ReportError(/*[XT_035]*/Res.Xslt_CharAttribute, "grouping-separator");
1720 // See the comment above
1721 result = f.String(string.Empty);
1724 QilIterator i = f.Let(result);
1726 f.Conditional(f.Eq(f.StrLength(i), f.Int32(1)), i,
1727 fwdCompat ? f.String(string.Empty) :
1728 f.Error(lastScope.SourceLine, Res.Xslt_CharAttribute, "grouping-separator")
1734 private QilNode CompileGroupingSizeAttribute(string attValue, bool fwdCompat) {
1735 QilNode result = CompileStringAvt(attValue);
1737 if (result == null) {
1739 } else if (result.NodeType == QilNodeType.LiteralString) {
1740 string groupingSize = (string)(QilLiteral)result;
1742 // NOTE: It is unclear from the spec what we should do with float numbers here.
1743 // Let's apply XPath number and round functions as usual, suppressing any conversion errors.
1744 double dblGroupingSize = XsltFunctions.Round(XPathConvert.StringToDouble(groupingSize));
1745 if (0 <= dblGroupingSize && dblGroupingSize <= int.MaxValue) {
1746 return f.Double(dblGroupingSize);
1748 // NaN goes here as well
1751 // NOTE: We should have the same checks for both compile time and execution time
1752 QilIterator i = f.Let(f.ConvertToNumber(result));
1754 f.Conditional(f.And(f.Lt(f.Double(0), i), f.Lt(i, f.Double(int.MaxValue))),
1762 private QilNode CompileNumber(Number num) {
1765 if (num.Value != null) {
1768 value = f.ConvertToNumber(CompileXPathExpression(num.Value));
1770 QilNode countPattern = (num.Count != null) ? CompileNumberPattern(num.Count) : null;
1771 QilNode fromPattern = (num.From != null) ? CompileNumberPattern(num.From ) : null;
1773 switch (num.Level) {
1774 case NumberLevel.Single : value = PlaceMarker(countPattern, fromPattern, false); break;
1775 case NumberLevel.Multiple : value = PlaceMarker(countPattern, fromPattern, true ); break;
1777 Debug.Assert(num.Level == NumberLevel.Any);
1778 value = PlaceMarkerAny(countPattern, fromPattern);
1783 bool fwdCompat = num.ForwardsCompatible;
1784 return f.TextCtor(f.InvokeNumberFormat(
1785 value, CompileStringAvt(num.Format),
1786 CompileLangAttributeToLcid (num.Lang, fwdCompat),
1787 CompileLetterValueAttribute (num.LetterValue, fwdCompat),
1788 CompileGroupingSeparatorAttribute(num.GroupingSeparator, fwdCompat),
1789 CompileGroupingSizeAttribute (num.GroupingSize, fwdCompat)
1793 // ------------- CompileAndSortMatchPatterns() -------------
1795 private void CompileAndSortMatches(Stylesheet sheet) {
1796 Debug.Assert(sheet.TemplateMatches.Count == 0);
1798 foreach (Template template in sheet.Templates) {
1799 if (template.Match != null) {
1800 EnterScope(template);
1801 QilNode result = CompileMatchPattern(template.Match);
1802 if (result.NodeType == QilNodeType.Sequence) {
1803 QilList filters = (QilList)result;
1804 for (int idx = 0; idx < filters.Count; idx++) {
1805 sheet.AddTemplateMatch(template, (QilLoop)filters[idx]);
1808 sheet.AddTemplateMatch(template, (QilLoop)result);
1814 sheet.SortTemplateMatches();
1816 foreach (Stylesheet import in sheet.Imports) {
1817 CompileAndSortMatches(import);
1821 // ------------- CompileKeys() -------------
1823 private void CompileKeys() {
1824 CheckSingletonFocus();
1825 for (int idx = 0; idx < compiler.Keys.Count; idx++) {
1826 foreach (Key key in compiler.Keys[idx]) {
1828 QilParameter context = f.Parameter(T.NodeNotRtf);
1829 singlFocus.SetFocus(context);
1830 QilIterator values = f.For(f.OptimizeBarrier(CompileKeyMatch(key.Match)));
1831 singlFocus.SetFocus(values);
1832 QilIterator keys = f.For(CompileKeyUse(key));
1833 keys = f.For(f.OptimizeBarrier(f.Loop(keys, f.ConvertToString(keys))));
1835 QilParameter value = f.Parameter(T.StringX);
1837 QilFunction func = f.Function(f.FormalParameterList(context, value),
1839 f.Not(f.IsEmpty(f.Filter(keys, f.Eq(keys, value))))
1844 func.DebugName = key.GetDebugName();
1845 SetLineInfo(func, key.SourceLine);
1846 key.Function = func;
1847 this.functions.Add(func);
1851 singlFocus.SetFocus(null);
1854 // ---------------------- Global variables and parameters -----------------------
1856 private void CreateGlobalVarPars() {
1857 foreach (VarPar par in compiler.ExternalPars) {
1858 CreateGlobalVarPar(par);
1860 foreach (VarPar var in compiler.GlobalVars) {
1861 CreateGlobalVarPar(var);
1865 private void CreateGlobalVarPar(VarPar varPar) {
1866 Debug.Assert(varPar.NodeType == XslNodeType.Variable || varPar.NodeType == XslNodeType.Param);
1867 XmlQueryType xt = ChooseBestType(varPar);
1869 if (varPar.NodeType == XslNodeType.Variable) {
1870 it = f.Let(f.Unknown(xt));
1872 it = f.Parameter(null, varPar.Name, xt);
1874 it.DebugName = varPar.Name.ToString();
1876 SetLineInfo(it, varPar.SourceLine);
1877 scope.AddVariable(varPar.Name, it);
1880 private void CompileGlobalVariables() {
1881 CheckSingletonFocus();
1882 singlFocus.SetFocus(SingletonFocusType.InitialDocumentNode);
1884 foreach (VarPar par in compiler.ExternalPars) {
1885 extPars.Add(CompileGlobalVarPar(par));
1887 foreach (VarPar var in compiler.GlobalVars) {
1888 gloVars.Add(CompileGlobalVarPar(var));
1891 singlFocus.SetFocus(null);
1894 private QilIterator CompileGlobalVarPar(VarPar varPar) {
1895 Debug.Assert(varPar.NodeType == XslNodeType.Variable || varPar.NodeType == XslNodeType.Param);
1896 QilIterator it = (QilIterator)varPar.Value;
1898 QilList nsList = EnterScope(varPar);
1899 QilNode content = CompileVarParValue(varPar);
1900 SetLineInfo(content, it.SourceLine);
1901 content = AddCurrentPositionLast(content);
1902 content = SetDebugNs(content, nsList);
1903 it.SourceLine = SourceLineInfo.NoSource;
1904 it.Binding = content;
1909 // ------------- CompileXPathExpression() / CompileMatchPattern() / CompileKeyPattern() -----------
1911 private void ReportErrorInXPath(XslLoadException e) {
1912 XPathCompileException ex = e as XPathCompileException;
1913 string errorText = (ex != null) ? ex.FormatDetailedMessage() : e.Message;
1914 compiler.ReportError(lastScope.SourceLine, Res.Xml_UserException, errorText);
1917 private QilNode PhantomXPathExpression() {
1918 return f.TypeAssert(f.Sequence(), T.ItemS);
1921 private QilNode PhantomKeyMatch() {
1922 return f.TypeAssert(f.Sequence(), T.NodeNotRtfS);
1925 // Calls to CompileXPathExpression() can't be nested in the XSLT. So we can reuse the same instance of xpathBuilder.
1926 // The only thing we need to do before its use is adjustment of IXPathEnvironment to have correct context tuple.
1927 private QilNode CompileXPathExpression(string expr) {
1928 XPathScanner scanner;
1931 SetEnvironmentFlags(/*allowVariables:*/true, /*allowCurrent:*/true, /*allowKey:*/true);
1933 result = PhantomXPathExpression();
1936 // Note that the constructor may throw an exception, for example, in case of the expression "'"
1937 scanner = new XPathScanner(expr);
1938 result = xpathParser.Parse(scanner, xpathBuilder, LexKind.Eof);
1939 } catch (XslLoadException e) {
1940 if (xslVersion != XslVersion.ForwardsCompatible) {
1941 ReportErrorInXPath(/*[XT0300]*/e);
1943 result = f.Error(f.String(e.Message));
1946 if (result is QilIterator) {
1947 result = f.Nop(result);
1952 private QilNode CompileNodeSetExpression(string expr) {
1953 QilNode result = f.TryEnsureNodeSet(CompileXPathExpression(expr));
1954 if (result == null) {
1955 // The expression is never a node-set
1956 XPathCompileException e = new XPathCompileException(expr, 0, expr.Length, Res.XPath_NodeSetExpected, null);
1957 if (xslVersion != XslVersion.ForwardsCompatible) {
1958 ReportErrorInXPath(/*[XTTE_101]*/e);
1960 result = f.Error(f.String(e.Message));
1965 private QilNode CompileXPathExpressionWithinAvt(string expr, ref int pos) {
1966 Debug.Assert(expr != null);
1967 XPathScanner scanner;
1971 SetEnvironmentFlags(/*allowVariables:*/true, /*allowCurrent:*/true, /*allowKey:*/true);
1973 scanner = new XPathScanner(expr, pos);
1974 result = xpathParser.Parse(scanner, xpathBuilder, LexKind.RBrace);
1975 pos = scanner.LexStart + 1;
1976 } catch (XslLoadException e) {
1977 if (xslVersion != XslVersion.ForwardsCompatible) {
1978 ReportErrorInXPath(/*[XT0350][XT0360]*/e);
1980 result = f.Error(f.String(e.Message));
1983 if (result is QilIterator) {
1984 result = f.Nop(result);
1989 private QilNode CompileMatchPattern(string pttrn) {
1990 Debug.Assert(pttrn != null);
1991 XPathScanner scanner;
1994 SetEnvironmentFlags(/*allowVariables:*/false, /*allowCurrent:*/false, /*allowKey:*/true);
1996 scanner = new XPathScanner(pttrn);
1997 result = ptrnParser.Parse(scanner, ptrnBuilder);
1998 } catch (XslLoadException e) {
1999 if (xslVersion != XslVersion.ForwardsCompatible) {
2000 ReportErrorInXPath(/*[XT0340]*/e);
2002 result = f.Loop(f.For(ptrnBuilder.FixupNode),
2003 f.Error(f.String(e.Message))
2005 XPathPatternBuilder.SetPriority(result, 0.5);
2010 private QilNode CompileNumberPattern(string pttrn) {
2011 Debug.Assert(pttrn != null);
2012 XPathScanner scanner;
2015 SetEnvironmentFlags(/*allowVariables:*/true, /*allowCurrent:*/false, /*allowKey:*/true);
2017 scanner = new XPathScanner(pttrn);
2018 result = ptrnParser.Parse(scanner, ptrnBuilder);
2019 } catch (XslLoadException e) {
2020 if (xslVersion != XslVersion.ForwardsCompatible) {
2021 ReportErrorInXPath(/*[XT0340]*/e);
2023 result = f.Error(f.String(e.Message));
2028 private QilNode CompileKeyMatch(string pttrn) {
2029 XPathScanner scanner;
2032 if (keyMatchBuilder == null) {
2033 keyMatchBuilder = new KeyMatchBuilder((IXPathEnvironment) this);
2035 SetEnvironmentFlags(/*allowVariables:*/false, /*allowCurrent:*/false, /*allowKey:*/false);
2036 if (pttrn == null) {
2037 result = PhantomKeyMatch();
2040 scanner = new XPathScanner(pttrn);
2041 result = ptrnParser.Parse(scanner, keyMatchBuilder);
2042 } catch (XslLoadException e) {
2043 if (xslVersion != XslVersion.ForwardsCompatible) {
2044 ReportErrorInXPath(/*[XT0340]*/e);
2046 result = f.Error(f.String(e.Message));
2052 private QilNode CompileKeyUse(Key key) {
2053 string expr = key.Use;
2054 XPathScanner scanner;
2057 SetEnvironmentFlags(/*allowVariables:*/false, /*allowCurrent:*/true, /*allowKey:*/false);
2059 result = f.Error(f.String(XslLoadException.CreateMessage(key.SourceLine, Res.Xslt_MissingAttribute, "use")));
2062 scanner = new XPathScanner(expr);
2063 result = xpathParser.Parse(scanner, xpathBuilder, LexKind.Eof);
2064 } catch (XslLoadException e) {
2065 if (xslVersion != XslVersion.ForwardsCompatible) {
2066 ReportErrorInXPath(/*[XT0300]*/e);
2068 result = f.Error(f.String(e.Message));
2071 if (result is QilIterator) {
2072 result = f.Nop(result);
2077 private QilNode ResolveQNameDynamic(bool ignoreDefaultNs, QilNode qilName) {
2078 f.CheckString(qilName);
2079 QilList nsDecls = f.BaseFactory.Sequence();
2080 if (ignoreDefaultNs) {
2081 nsDecls.Add(f.NamespaceDecl(f.String(string.Empty), f.String(string.Empty)));
2083 foreach (ScopeRecord rec in this.scope) {
2084 string recPrefix = rec.ncName;
2085 string recNsUri = rec.nsUri;
2087 if (ignoreDefaultNs && recPrefix.Length == 0) {
2088 // Do not take into account the default namespace
2090 nsDecls.Add(f.NamespaceDecl(f.String(recPrefix), f.String(recNsUri)));
2093 return f.StrParseQName(qilName, nsDecls);
2096 // ----------------- apply-templates, apply-imports ----------------------------- //
2098 private QilNode GenerateApply(StylesheetLevel sheet, XslNode node) {
2100 node.NodeType == XslNodeType.ApplyTemplates && sheet is RootLevel ||
2101 node.NodeType == XslNodeType.ApplyImports && sheet is Stylesheet
2104 if (compiler.Settings.CheckOnly) {
2105 return f.Sequence();
2107 return InvokeApplyFunction(sheet, /*mode:*/node.Name, node.Content);
2110 private void SetArg(IList<XslNode> args, int pos, QilName name, QilNode value) {
2112 if (args.Count <= pos || args[pos].Name != name) {
2113 varPar = AstFactory.WithParam(name);
2114 args.Insert(pos, varPar);
2116 varPar = (VarPar) args[pos];
2118 varPar.Value = value;
2120 private IList<XslNode> AddRemoveImplicitArgs(IList<XslNode> args, XslFlags flags) {
2121 //We currently don't reuse the same argument list. So remove is not needed and will not work in this code
2123 flags = XslFlags.FullFocus;
2125 if ((flags & XslFlags.FocusFilter) != 0) {
2126 if (args == null || args.IsReadOnly) {
2127 args = new List<XslNode>(3);
2130 if ((flags & XslFlags.Current ) != 0) { SetArg(args, pos ++, nameCurrent , GetCurrentNode ()); }
2131 if ((flags & XslFlags.Position) != 0) { SetArg(args, pos ++, namePosition, GetCurrentPosition()); }
2132 if ((flags & XslFlags.Last ) != 0) { SetArg(args, pos ++, nameLast , GetLastPosition ()); }
2137 // Fills invokeArgs with values from actualArgs in order given by formalArgs
2138 // Returns true if formalArgs maps 1:1 with actual args.
2139 // Formaly this is n*n algorithm. We can optimize it by calculationg "signature"
2140 // of the function as sum of all hashes of its args names.
2141 private bool FillupInvokeArgs(IList<QilNode> formalArgs, IList<XslNode> actualArgs, QilList invokeArgs) {
2142 if (actualArgs.Count != formalArgs.Count) {
2146 for (int invArg = 0; invArg < formalArgs.Count; invArg++) {
2147 QilName formalArgName = ((QilParameter)formalArgs[invArg]).Name;
2148 XmlQueryType paramType = formalArgs[invArg].XmlType;
2149 QilNode arg = null; {
2150 for (int actArg = 0; actArg < actualArgs.Count; actArg++) {
2151 Debug.Assert(actualArgs[actArg].NodeType == XslNodeType.WithParam, "All Sorts was removed in CompileSorts()");
2152 VarPar withParam = (VarPar)actualArgs[actArg];
2153 if (formalArgName.Equals(withParam.Name)) {
2154 QilNode value = withParam.Value;
2155 XmlQueryType valueType = value.XmlType;
2156 if (valueType != paramType) {
2157 if (valueType.IsNode && paramType.IsNode && valueType.IsSubtypeOf(paramType)) {
2160 // Formal argument has the same name but a different type
2170 // Formal argument has not been found among actual arguments
2173 invokeArgs.Add(arg);
2175 // All arguments have been found
2179 private QilNode InvokeApplyFunction(StylesheetLevel sheet, QilName mode, IList<XslNode> actualArgs) {
2180 // Here we create function that has one argument for each with-param in apply-templates
2181 // We have actualArgs -- list of xsl:with-param(name, value)
2182 // From it we create:
2183 // invokeArgs -- values to use with QilInvoke
2184 // formalArgs -- list of iterators to use with QilFunction
2185 // actualArgs -- modify it to hold iterators (formalArgs) instead of values to ise in invoke generator inside function budy
2188 if (! sheet.ModeFlags.TryGetValue(mode, out flags)) {
2191 flags |= XslFlags.Current; // Due to recursive nature of Apply(Templates/Imports) we will need current node any way
2193 actualArgs = AddRemoveImplicitArgs(actualArgs, flags);
2195 QilList invokeArgs = f.ActualParameterList();
2196 QilFunction applyFunction = null;
2198 // Look at the list of all functions that have been already built. If a suitable one is found, reuse it.
2199 List<QilFunction> functionsForMode;
2200 if (!sheet.ApplyFunctions.TryGetValue(mode, out functionsForMode)) {
2201 functionsForMode = sheet.ApplyFunctions[mode] = new List<QilFunction>();
2204 foreach (QilFunction func in functionsForMode) {
2205 if (FillupInvokeArgs(func.Arguments, actualArgs, /*ref*/invokeArgs)) {
2206 applyFunction = func;
2211 // If a suitable function has not been found, create it
2212 if (applyFunction == null) {
2214 // We wasn't able to find suitable function. Let's build new:
2215 // 1. Function arguments
2216 QilList formalArgs = f.FormalParameterList();
2217 for (int i = 0; i < actualArgs.Count; i++) {
2218 Debug.Assert(actualArgs[i].NodeType == XslNodeType.WithParam, "All Sorts was removed in CompileSorts()");
2219 VarPar withParam = (VarPar)actualArgs[i] ;
2221 // Add actual arg to 'invokeArgs' array. No need to clone it since it must be
2222 // a literal or a reference.
2223 invokeArgs.Add(withParam.Value);
2225 // Create correspondent formal arg
2226 QilParameter formalArg = f.Parameter(i == 0 ? T.NodeNotRtf : withParam.Value.XmlType);
2227 formalArg.Name = CloneName(withParam.Name);
2228 formalArgs.Add(formalArg);
2230 // Change actual arg value to formalArg for reuse in calling built-in templates rules
2231 withParam.Value = formalArg;
2234 // 2. Function header
2235 applyFunction = f.Function(formalArgs,
2236 f.Boolean((flags & XslFlags.SideEffects) != 0),
2239 string attMode = (mode.LocalName.Length == 0) ? string.Empty : " mode=\"" + mode.QualifiedName + '"';
2240 applyFunction.DebugName = (sheet is RootLevel ? "<xsl:apply-templates" : "<xsl:apply-imports") + attMode + '>';
2241 functionsForMode.Add(applyFunction);
2242 this.functions.Add(applyFunction);
2245 Debug.Assert(actualArgs[0].Name == nameCurrent, "Caller should always pass $current as a first argument to apply-* calls.");
2246 QilIterator current = (QilIterator)formalArgs[0];
2248 // 3.1 Built-in templates:
2249 // 3.1.1 loop over content of current element
2250 QilLoop loopOnContent; {
2251 QilIterator iChild = f.For(f.Content(current));
2252 QilNode filter = f.Filter(iChild, f.IsType(iChild, T.Content));
2253 filter.XmlType = T.ContentS; // not attribute
2255 LoopFocus curLoopSaved = curLoop;
2256 curLoop.SetFocus(f.For(filter));
2258 /* Prepare actual arguments */
2259 // At XSLT 1.0, if a built-in template rule is invoked with parameters, the parameters are not
2260 // passed on to any templates invoked by the built-in rule. At XSLT 2.0, these parameters are
2261 // passed through the built-in template rule unchanged.
2263 // we can't just modify current/position/last of actualArgs in XSLT 2.0 as we tried before,
2264 // becuase flags for apply-import amy now be different then flags for apply-templates, so
2265 // we may need to add some space for additional position/last arguments
2266 QilNode body = InvokeApplyFunction(compiler.Root, mode, /*actualArgs:*/null);
2268 body = f.Sequence(InvokeOnCurrentNodeChanged(), body);
2270 loopOnContent = curLoop.ConstructLoop(body);
2271 curLoop = curLoopSaved;
2274 // 3.1.2 switch on type of current node
2275 QilTernary builtinTemplates = f.BaseFactory.Conditional(f.IsType(current, elementOrDocumentType),
2277 f.Conditional(f.IsType(current, textOrAttributeType),
2278 f.TextCtor(f.XPathNodeValue(current)),
2283 // 3.2 Stylesheet templates
2284 matcherBuilder.CollectPatterns(sheet, mode);
2285 applyFunction.Definition = matcherBuilder.BuildMatcher(current, actualArgs, /*otherwise:*/builtinTemplates);
2287 return f.Invoke(applyFunction, invokeArgs);
2290 // -------------------------------- IErrorHelper --------------------------------
2292 public void ReportError(string res, params string[] args) {
2293 compiler.ReportError(lastScope.SourceLine, res, args);
2296 public void ReportWarning(string res, params string[] args) {
2297 compiler.ReportWarning(lastScope.SourceLine, res, args);
2300 // ------------------------------------------------------------------------------
2302 [Conditional("DEBUG")]
2303 private void VerifyXPathQName(QilName qname) {
2305 compiler.IsPhantomName(qname) ||
2306 qname.NamespaceUri == ResolvePrefix(/*ignoreDefaultNs:*/true, qname.Prefix),
2307 "QilGenerator must resolve the prefix to the same namespace as XsltLoader"
2311 private string ResolvePrefix(bool ignoreDefaultNs, string prefix) {
2312 if (ignoreDefaultNs && prefix.Length == 0) {
2313 return string.Empty;
2315 string ns = scope.LookupNamespace(prefix);
2317 if (prefix.Length == 0) {
2320 ReportError(/*[XT0280]*/Res.Xslt_InvalidPrefix, prefix);
2321 ns = compiler.CreatePhantomNamespace();
2328 private void SetLineInfoCheck(QilNode n, ISourceLineInfo lineInfo) {
2329 // Prevent xsl:choose override xsl:when, etc.
2330 if (n.SourceLine == null) {
2331 SetLineInfo(n, lineInfo);
2333 Debug.Assert(!IsDebug, "Attempt to override SourceLineInfo in debug mode");
2337 private static QilNode SetLineInfo(QilNode n, ISourceLineInfo lineInfo) {
2338 Debug.Assert(n.SourceLine == null);
2339 if (lineInfo != null) {
2340 SourceLineInfo.Validate(lineInfo);
2341 if (0 < lineInfo.Start.Line && lineInfo.Start.LessOrEqual(lineInfo.End)) {
2342 n.SourceLine = lineInfo;
2348 private QilNode AddDebugVariable(QilName name, QilNode value, QilNode content) {
2349 QilIterator var = f.Let(value);
2350 var.DebugName = name.ToString();
2351 return f.Loop(var, content);
2354 private QilNode SetDebugNs(QilNode n, QilList nsList) {
2355 if (n != null && nsList != null) {
2356 QilNode nsVar = GetNsVar(nsList);
2357 Debug.Assert(nsVar.XmlType.IsSubtypeOf(T.NamespaceS));
2358 if (nsVar.XmlType.Cardinality == XmlQueryCardinality.One) {
2359 // We want CLR type to be XmlQuerySequence instead of XPathNavigator
2360 nsVar = f.TypeAssert(nsVar, T.NamespaceS);
2362 n = AddDebugVariable(CloneName(nameNamespaces), nsVar, n);
2367 private QilNode AddCurrentPositionLast(QilNode content) {
2369 content = AddDebugVariable(CloneName(nameLast) , GetLastPosition (), content);
2370 content = AddDebugVariable(CloneName(namePosition), GetCurrentPosition(), content);
2371 content = AddDebugVariable(CloneName(nameCurrent) , GetCurrentNode (), content);
2376 private QilName CloneName(QilName name) {
2377 return (QilName)name.ShallowClone(f.BaseFactory);
2380 // This helper internal class is used for compiling sort's and with-param's
2381 private class VariableHelper {
2382 private Stack<QilIterator> vars = new Stack<QilIterator>();
2383 private XPathQilFactory f;
2385 public VariableHelper(XPathQilFactory f) {
2389 public int StartVariables() {
2393 public void AddVariable(QilIterator let) {
2394 Debug.Assert(let.NodeType == QilNodeType.Let);
2398 public QilNode FinishVariables(QilNode node, int varScope) {
2399 Debug.Assert(0 <= varScope && varScope <= vars.Count);
2400 for (int i = vars.Count - varScope; i-- != 0; ) {
2401 node = f.Loop(vars.Pop(), node);
2406 [Conditional("DEBUG")]
2407 public void CheckEmpty() {
2408 Debug.Assert(vars.Count == 0, "Accumulated variables left unclaimed");