/// <summary> /// Initializes a new instance. /// </summary> /// <param name="xml"></param> /// <param name="context"></param> /// <param name="xpath"></param> internal Binding(XObject xml, EvaluationContext context, string xpath) : this(xml, context, context.CompileXPath(xml, xpath)) { Contract.Requires<ArgumentNullException>(xml != null); Contract.Requires<ArgumentNullException>(context != null); Contract.Requires<ArgumentNullException>(xpath != null); }
public RepeatExtensionProperties( XElement element, EvaluationContext context) { Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(context != null); this.attributes = element.AnnotationOrCreate(() => new RepeatExtensionAttributes(element)); this.context = context; this.ref_ = new Lazy<XPathExpression>(() => !string.IsNullOrEmpty(attributes.Ref) ? context.CompileXPath(element, attributes.Ref) : null); this.nodeSet = new Lazy<XPathExpression>(() => !string.IsNullOrEmpty(attributes.NodeSet) ? context.CompileXPath(element, attributes.NodeSet) : null); this.indexRef = new Lazy<XPathExpression>(() => !string.IsNullOrEmpty(attributes.IndexRef) ? context.CompileXPath(element, attributes.IndexRef) : null); }
/// <summary> /// Initializes a new instance. /// </summary> /// <param name="xml"></param> /// <param name="evaluationContext"></param> public EvaluationXsltContext( XObject xml, EvaluationContext evaluationContext) : this(xml.Exports().GetExportedValue<IXsltContextFunctionProvider>(), xml, evaluationContext) { Contract.Requires<ArgumentNullException>(xml != null); Contract.Requires<ArgumentNullException>(evaluationContext != null); }
/// <summary> /// Initializes a new instance. /// </summary> /// <param name="xml"></param> /// <param name="evaluationContext"></param> public EvaluationXsltContext( IXsltContextFunctionProvider functionProvider, XObject xml, EvaluationContext evaluationContext) : base(functionProvider, xml) { Contract.Requires<ArgumentNullException>(xml != null); Contract.Requires<ArgumentNullException>(evaluationContext != null); this.evaluationContext = evaluationContext; }
/// <summary> /// Initializes a new instance. /// </summary> /// <param name="xml"></param> /// <param name="context"></param> /// <param name="xpath"></param> internal Binding(XObject xml, EvaluationContext context, XPathExpression xpath) { Contract.Requires<ArgumentNullException>(xml != null); Contract.Requires<ArgumentNullException>(context != null); Contract.Requires<ArgumentNullException>(xpath != null); this.xml = xml; this.context = context; this.xpath = xpath; // initial load of values Recalculate(); }
XObject[] GetSequenceBindingNodeSequence(EvaluationContext insertContext) { Contract.Requires<ArgumentNullException>(insertContext != null); Contract.Ensures(Contract.Result<XObject[]>() != null); var bindId = bindingProperties.Bind; if (bindId != null) { var element = Element.ResolveId(bindId); if (element != null) { var bind = element.InterfaceOrDefault<Bind>(); if (bind == null) throw new DOMTargetEventException(Element, Events.BindingException); return bind.GetBoundNodes() .Select(i => i.Xml) .ToArray(); } } var ref_ = bindingProperties.Ref ?? bindingProperties.NodeSet; if (ref_ != null) return new Binding(Element, insertContext, ref_).ModelItems .Select(i => i.Xml) .ToArray(); return new XObject[0]; }
/// <summary> /// Gets the 'target location'. /// </summary> /// <param name="insertContext"></param> /// <param name="sequenceBindingNodeSequence"></param> /// <param name="insertLocationNode"></param> /// <param name="insertNode"></param> /// <returns></returns> TargetLocation GetTargetLocation(EvaluationContext insertContext, XObject[] sequenceBindingNodeSequence, XObject insertLocationNode, XObject insertNode) { Contract.Requires<ArgumentNullException>(insertContext != null); Contract.Requires<ArgumentNullException>(sequenceBindingNodeSequence != null); Contract.Requires<ArgumentNullException>(insertLocationNode != null); Contract.Requires<ArgumentNullException>(insertNode != null); // f the Sequence Binding node-sequence is not specified or empty, then the insert location node provided // by the context attribute is intended to be the parent of the cloned node. The target location is // dependent on the types of the cloned node and the insert location node as follows: if (sequenceBindingNodeSequence.Length == 0) { // If the insert location node is not an element node or root node, then it cannot be the parent of the // cloned node, so the target location is undefined. if (insertLocationNode.NodeType != XmlNodeType.Element && insertLocationNode.Parent != null) return new TargetLocation(insertNode, TargetPosition.Undefined); // If the insert location node is the root node of an instance (which is the parent of the root element), // and the cloned node is an element, then the target location is the root element of the instance. if (insertLocationNode == insertLocationNode.Document.Root && insertNode.NodeType == XmlNodeType.Element) { var target = insertLocationNode.Document.Root; if (target == null) throw new InvalidOperationException(); if (target.IsEmpty) return new TargetLocation(insertNode, TargetPosition.Node, target); else return new TargetLocation(insertNode, TargetPosition.After, target.LastNode); } // If the insert location node is the root node of an instance (which is the parent of the root element), // and the cloned node is not an element, then the target location is before the first child of the insert // location node. if (insertLocationNode == insertLocationNode.Document.Root && insertNode.NodeType != XmlNodeType.Element) if (((XElement)insertLocationNode).IsEmpty) return new TargetLocation(insertNode, TargetPosition.Node, insertLocationNode); else return new TargetLocation(insertNode, TargetPosition.Before, ((XElement)insertLocationNode).FirstNode); // If the insert location node is an element, and the cloned node is an attribute, then the target // location is the attribute list of the insert location node. if (insertLocationNode.NodeType == XmlNodeType.Element && insertNode.NodeType == XmlNodeType.Attribute) return new TargetLocation(insertNode, TargetPosition.Node, insertLocationNode); // If the insert location node is an element, and the cloned node is not an attribute, then the target // location is before the first child of the insert location node, or the child list of the insert // location node if it is empty. if (insertLocationNode.NodeType == XmlNodeType.Element && insertNode.NodeType != XmlNodeType.Attribute) if (((XElement)insertLocationNode).IsEmpty) return new TargetLocation(insertNode, TargetPosition.Node, insertLocationNode); else return new TargetLocation(insertNode, TargetPosition.Before, ((XElement)insertLocationNode).FirstNode); } else { // Otherwise, the Sequence Binding node-sequence is specified and non-empty, so the insert location // node provided by the Sequence Binding and author-optional at attribute is intended to be the sibling // of the cloned node. // If the insert location node is an attribute or root node, then the target location is undefined. if (insertLocationNode == insertLocationNode.Document.Root || insertLocationNode.NodeType == XmlNodeType.Attribute) return new TargetLocation(insertNode, TargetPosition.Undefined); // If the insert location node is not an attribute or root node, then the // target location is immediately before or after the insert location node, based on the position // attribute setting or its default. else if (insertProperties.Position == InsertPosition.After) return new TargetLocation(insertNode, TargetPosition.After, insertLocationNode); else if (insertProperties.Position == InsertPosition.Before) return new TargetLocation(insertNode, TargetPosition.Before, insertLocationNode); } // should never reach this point throw new InvalidOperationException(); }
/// <summary> /// Gets the 'Sequence Binding node-sequence'. /// </summary> /// <param name="insertContext"></param> /// <returns></returns> XObject[] GetSequenceBindingNodeSequence(EvaluationContext insertContext) { Contract.Requires<ArgumentNullException>(insertContext != null); Contract.Ensures(Contract.Result<XObject[]>() != null); // If a bind attribute is present, it directly determines the Sequence Binding node-sequence. var bindId = bindingProperties.Bind; if (bindId != null) { var element = Element.ResolveId(bindId); if (element != null) { var bind = element.InterfaceOrDefault<Bind>(); if (bind == null) throw new DOMTargetEventException(Element, Events.BindingException); return bind.GetBoundNodes() .Select(i => i.Xml) .ToArray(); } } // If a ref (or deprecated nodeset) attribute is present, it is evaluated within the insert context to // determine the Sequence Binding node-sequence. var ref_ = bindingProperties.Ref ?? bindingProperties.NodeSet; if (ref_ != null) return new Binding(Element, insertContext, ref_).ModelItems .Select(i => i.Xml) .ToArray(); // If the Sequence Binding attributes are not present, then the Sequence Binding node-sequence is the // empty sequence. return new XObject[0]; }
/// <summary> /// Gets the 'origin node-sequence'. /// </summary> /// <param name="insertContext"></param> /// <param name="sequenceBindingNodeSequence"></param> /// <returns></returns> XObject[] GetOriginNodeSequence(EvaluationContext insertContext, XObject[] sequenceBindingNodeSequence) { Contract.Requires<ArgumentNullException>(insertContext != null); Contract.Requires<ArgumentNullException>(sequenceBindingNodeSequence != null); Contract.Ensures(Contract.Result<XObject[]>() != null); XObject[] result = null; // If the origin attribute is not given and the Sequence Binding sequence is empty, then the origin // node-sequence is the empty sequence. if (insertProperties.Origin == null && sequenceBindingNodeSequence.Length == 0) result = new XObject[0]; // Otherwise, if the origin attribute is not given, then the origin node-sequence consists of the last // node of the Sequence Binding node-sequence. else if (insertProperties.Origin == null) result = new[] { sequenceBindingNodeSequence[sequenceBindingNodeSequence.Length - 1] }; // If the origin attribute is given, the origin node-sequence is the result of the evaluation of the origin // attribute in the insert context. else if (insertProperties.Origin != null) result = new Binding(Element, insertContext, insertProperties.Origin).ModelItems .Select(i => i.Xml) .ToArray(); else result = new XObject[0]; // preempt empty result set if (result.Length == 0) return result; // Namespace nodes and root nodes (parents of document elements) are removed from the origin node-sequence. return result .Where(i => i.NodeType != XmlNodeType.Attribute || !((XAttribute)i).IsNamespaceDeclaration) .Where(i => i.Parent != null || i == i.Document.Root) .ToArray(); }
/// <summary> /// Gets the 'insert location node'. /// </summary> /// <param name="insertContext"></param> /// <param name="sequenceBindingNodeSequence"></param> /// <returns></returns> XObject GetInsertLocationNode(EvaluationContext insertContext, XObject[] sequenceBindingNodeSequence) { Contract.Requires<ArgumentNullException>(insertContext != null); Contract.Requires<ArgumentNullException>(sequenceBindingNodeSequence != null); Contract.Ensures(Contract.Result<XObject>() != null); // If the Sequence Binding node-sequence is not specified or empty, the insert location node is the insert // context node. if (sequenceBindingNodeSequence.Length == 0) return insertContext.ModelItem.Xml; // Otherwise, if the at attribute is not given, then the insert location node is the last node of the // Sequence Binding sequence. else if (insertProperties.At == null) return sequenceBindingNodeSequence[sequenceBindingNodeSequence.Length - 1]; // Otherwise, an insert location node is determined from the at attribute as follows: else { // 1. The evaluation context node is the first node in document order from the Sequence Binding // node-sequence, the context size is the size of the Sequence Binding node-sequence, and the context // position is 1. var at = new Binding( Element, new EvaluationContext(ModelItem.Get(sequenceBindingNodeSequence[0]), 1, sequenceBindingNodeSequence.Length), insertProperties.At).Value; // 2. The return value is processed according to the rules of the XPath function round(). For example, // the literal 1.5 becomes 2, and the literal 'string' becomes NaN. double location; if (double.TryParse(at, out location)) location = Math.Round(location, 0, MidpointRounding.AwayFromZero); else location = double.NaN; // 3. If the result is in the range 1 to the Sequence Binding node-sequence size, then the insert // location is equal to the result. If the result is non-positive, then the insert location is 1. // Otherwise, the result is NaN or exceeds the Sequence Binding sequence size, so the insert location // is the Sequence Binding sequence size. if (location <= 0) location = 1; else if (double.IsNaN(location) || location > sequenceBindingNodeSequence.Length) location = sequenceBindingNodeSequence.Length; // 4. The insert location node is the node in the Sequence Binding sequence at the position given by // the insert location. return sequenceBindingNodeSequence[(int)location - 1]; } }
/// <summary> /// The insert context is determined. If the context attribute is not given, the insert context is the /// in-scope evaluation context. Otherwise, the expression provided by the context attribute is evaluated /// using the in-scope evaluation context, and the first-item rule is applied to obtain the insert context. /// The insert action is terminated with no effect if the insert context is the empty sequence. /// </summary> /// <returns></returns> EvaluationContext GetInsertContext() { var insertContext = context.Value.GetInScopeEvaluationContext(); if (commonProperties.Context != null) { var item = new Binding(Element, insertContext, commonProperties.Context).ModelItems.FirstOrDefault(); if (item == null) return null; insertContext = new EvaluationContext(item.Model, item.Instance, item, 1, 1); } return insertContext; }
/// <summary> /// Applies the bindings to the model item. /// </summary> public void Apply() { // TODO this is a poor implementation of nested bind elements var modelItems = GetBoundNodes(); for (int i = 1; i <= modelItems.Length; i++) { var modelItem = modelItems[i - 1]; if (modelItem == null) continue; var state = modelItem.State; if (state == null) continue; if (Type != null) if (state.Type != Type) state.Type = Type; var ec = new EvaluationContext(modelItem.Model, modelItem.Instance, modelItem, i, modelItems.Length); if (!string.IsNullOrWhiteSpace(attributes.ReadOnly)) { var readOnly = ParseBooleanValue(new Binding(Element, ec, attributes.ReadOnly)); if (readOnly != null) state.ReadOnly = readOnly; } if (!string.IsNullOrWhiteSpace(attributes.Required)) { var required = ParseBooleanValue(new Binding(Element, ec, attributes.Required)); if (required != null) state.Required = required; } if (!string.IsNullOrWhiteSpace(attributes.Relevant)) { var relevant = ParseBooleanValue(new Binding(Element, ec, attributes.Relevant)); if (relevant != null && relevant != state.Relevant) { state.Relevant = relevant; Debug.WriteLine("ModelItem relevancy changed: {0}", state.Relevant); } } if (!string.IsNullOrWhiteSpace(attributes.Constraint)) { var constraint = ParseBooleanValue(new Binding(Element, ec, attributes.Constraint)); if (constraint != null) state.Constraint = constraint; } if (!string.IsNullOrWhiteSpace(attributes.Calculate)) { var calculate = new Binding(Element, ec, attributes.Calculate).Value; if (calculate != null) { if (state.ReadOnly == false) state.ReadOnly = true; modelItem.Value = calculate; } } } }
internal ModelItem[] GetBoundNodes() { // TODO this is a poor implementation of nested bind elements var modelItems = Binding.ModelItems.ToList(); var parentBind = Element.Ancestors(Constants.XForms_1_0 + "bind") .SelectMany(i => i.Interfaces<Bind>()) .FirstOrDefault(); if (parentBind != null) { var xpath = Element.Attribute("ref") ?? Element.Attribute("nodeset"); if (xpath != null) { var parentItems = parentBind.GetBoundNodes(); modelItems.Clear(); for (int i = 1; i <= parentItems.Length; i++) { var parentModelItem = parentItems[i - 1]; var ec = new EvaluationContext( parentModelItem.Model, parentModelItem.Instance, parentModelItem, i, parentItems.Length); modelItems.AddRange(new Binding(xpath, ec, (string)xpath).ModelItems); } } } return modelItems.ToArray(); }
/// <summary> /// Finishes a submission with instance replacement. /// </summary> /// <param name="response"></param> /// <param name="modelItem">Instance data node that was submitted.</param> void FinishWithReplaceInstance(ModelResponse response, ModelItem modelItem) { Contract.Requires <ArgumentNullException>(response != null); Contract.Requires <ArgumentException>(response.Body != null); Contract.Requires <ArgumentNullException>(modelItem != null); // When the attribute is absent, then the default is the instance that contains the submission data. var instance = modelItem != null ? modelItem.Instance : null; // Author-optional attribute specifying the instance to replace when the replace attribute value is // "instance". When the attribute is absent, then the default is the instance that contains the submission // data. An xforms-binding-exception (The xforms-binding-exception Event) occurs if this attribute does not // indicate an instance in the same model as the submission. if (properties.Instance != null) { var instanceElement = Element.ResolveId(properties.Instance); if (instanceElement != null) { instance = instanceElement.Interface <Instance>(); } } if (instance == null || instance.Element.Parent != Element.Parent) { throw new DOMTargetEventException(Element, Events.BindingException, "Submission cannot specify foreign model instance."); } var target = instance.State.Document.Root.Annotation <ModelItem>(); if (target == null) { throw new InvalidOperationException(); } // Author-optional attribute containing an expression that indicates the target node for data replacement. if (properties.TargetRef != null) { // The evaluation context for this attribute is the in-scope evaluation context for the submission // element, except the context node is modified to be the document element of the instance identified // by the instance attribute if present. var ec = new EvaluationContext( ModelItem.Get(context.Value.Context.Instance.State.Document.Root), 1, 1); // If the submission element has a targetref attribute, the attribute value is interpreted as a binding // expression to which the first-item rule is applied to obtain the replacement target node. target = new Binding(Element, ec, properties.TargetRef).ModelItem; } // final check if (target == null) { throw new DOMTargetEventException(Element, Events.BindingException, "Null submission replacement target."); } // Otherwise, those processing instructions and comments replace any processing instructions and comments // that previously appeared outside of the document element of the instance being replaced. target.Replace(response.Body); }
/// <summary> /// Gets the 'Sequence Binding node-sequence'. /// </summary> /// <param name="deleteContext"></param> /// <returns></returns> XObject[] GetSequenceBindingNodeSequence(EvaluationContext deleteContext) { Contract.Requires<ArgumentNullException>(deleteContext != null); Contract.Ensures(Contract.Result<XObject[]>() != null); // If a bind attribute is present, it directly determines the Sequence Binding node-sequence. var bindId = bindingProperties.Bind; if (bindId != null) { var element = Element.ResolveId(bindId); if (element != null) { var bind = element.InterfaceOrDefault<Bind>(); if (bind == null) throw new DOMTargetEventException(Element, Events.BindingException); return bind.GetBoundNodes() .Select(i => i.Xml) .ToArray(); } } // If a ref (or deprecated nodeset) attribute is present, it is evaluated within the insert context to // determine the Sequence Binding node-sequence. var ref_ = bindingProperties.Ref ?? bindingProperties.NodeSet; if (ref_ != null) return new Binding(Element, deleteContext, ref_).ModelItems .Select(i => i.Xml) .ToArray(); // Otherwise, the Sequence Binding is not expressed, so the Sequence Binding node-sequence is set equal to // the delete context node with a position and size of 1. return deleteContext.ModelItem != null ? new XObject[] { deleteContext.ModelItem.Xml } : new XObject[0]; }
/// <summary> /// The delete context is determined. It is set to the in-scope evaluation context, possibly overridden by the /// context attribute if that attribute is present. The delete action is terminated with no effect if the /// delete context is the empty sequence. /// </summary> /// <returns></returns> EvaluationContext GetDeleteContext() { var deleteContext = resolver.Value.GetInScopeEvaluationContext(); if (commonProperties.Context != null) { var item = new Binding(Element, deleteContext, commonProperties.Context).ModelItems.First(); if (item == null) return null; deleteContext = new EvaluationContext(item.Model, item.Instance, item, 1, 1); } return deleteContext; }
/// <summary> /// Finishes a submission with instance replacement. /// </summary> /// <param name="response"></param> /// <param name="modelItem">Instance data node that was submitted.</param> void FinishWithReplaceInstance(ModelResponse response, ModelItem modelItem) { Contract.Requires<ArgumentNullException>(response != null); Contract.Requires<ArgumentException>(response.Body != null); Contract.Requires<ArgumentNullException>(modelItem != null); // When the attribute is absent, then the default is the instance that contains the submission data. var instance = modelItem != null ? modelItem.Instance : null; // Author-optional attribute specifying the instance to replace when the replace attribute value is // "instance". When the attribute is absent, then the default is the instance that contains the submission // data. An xforms-binding-exception (The xforms-binding-exception Event) occurs if this attribute does not // indicate an instance in the same model as the submission. if (properties.Instance != null) { var instanceElement = Element.ResolveId(properties.Instance); if (instanceElement != null) instance = instanceElement.Interface<Instance>(); } if (instance == null || instance.Element.Parent != Element.Parent) throw new DOMTargetEventException(Element, Events.BindingException, "Submission cannot specify foreign model instance."); var target = instance.State.Document.Root.Annotation<ModelItem>(); if (target == null) throw new InvalidOperationException(); // Author-optional attribute containing an expression that indicates the target node for data replacement. if (properties.TargetRef != null) { // The evaluation context for this attribute is the in-scope evaluation context for the submission // element, except the context node is modified to be the document element of the instance identified // by the instance attribute if present. var ec = new EvaluationContext( ModelItem.Get(context.Value.Context.Instance.State.Document.Root), 1, 1); // If the submission element has a targetref attribute, the attribute value is interpreted as a binding // expression to which the first-item rule is applied to obtain the replacement target node. target = new Binding(Element, ec, properties.TargetRef).ModelItem; } // final check if (target == null) throw new DOMTargetEventException(Element, Events.BindingException, "Null submission replacement target."); // Otherwise, those processing instructions and comments replace any processing instructions and comments // that previously appeared outside of the document element of the instance being replaced. target.Replace(response.Body); }