public void execute( XFormsActionInterpreter actionInterpreter, Element actionElement, Scope actionScope, boolean hasOverriddenContext, Item overriddenContext) { final IndentedLogger indentedLogger = actionInterpreter.indentedLogger(); final XFormsContainingDocument containingDocument = actionInterpreter.containingDocument(); final XFormsContextStack contextStack = actionInterpreter.actionXPathContext(); final String atAttribute = actionElement.attributeValue("at"); final String originAttribute = actionElement.attributeValue("origin"); final String contextAttribute = actionElement.attributeValue(XFormsConstants.CONTEXT_QNAME); // Extension: allow position to be an AVT final String resolvedPositionAttribute = actionInterpreter.resolveAVT(actionElement, "position"); // Extension: xxf:default="true" AVT requires that recalculate apply default values on the // inserted nodes. final boolean setRequireDefaultValues = "true" .equals( actionInterpreter.resolveAVT( actionElement, XFormsConstants.XXFORMS_DEFAULTS_QNAME)); // "2. The Node Set Binding node-set is determined." final List<Item> collectionToBeUpdated; { final BindingContext currentBindingContext = contextStack.getCurrentBindingContext(); collectionToBeUpdated = currentBindingContext.newBind() ? currentBindingContext.nodeset() : XFormsConstants.EMPTY_ITEM_LIST; } final boolean isEmptyNodesetBinding = collectionToBeUpdated == null || collectionToBeUpdated.size() == 0; // "1. The insert context is determined." // "The insert action is terminated with no effect if [...] a. The context attribute is not // given and the Node // Set Binding node-set is the empty node-set." if (contextAttribute == null && isEmptyNodesetBinding) { if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug("xf:insert", "context is empty, terminating"); return; } // Handle insert context (with @context attribute) final Item insertContextItem; if (hasOverriddenContext) { // "If the result is an empty nodeset or not a nodeset, then the insert action is terminated // with no effect. " if (overriddenContext == null || !(overriddenContext instanceof NodeInfo)) { if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug( "xf:insert", "overridden context is an empty nodeset or not a nodeset, terminating"); return; } else { insertContextItem = overriddenContext; } } else { insertContextItem = contextStack.getCurrentBindingContext().getSingleItem(); } // "The insert action is terminated with no effect if [...] b. The context attribute is given, // the insert // context does not evaluate to an element node and the Node Set Binding node-set is the empty // node-set." // NOTE: In addition we support inserting into a context which is a document node if (contextAttribute != null && isEmptyNodesetBinding && !DataModel.isElement(insertContextItem) && !DataModel.isDocument(insertContextItem)) { if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug( "xf:insert", "insert context is not an element node and binding node-set is empty, terminating"); return; } // "3. The origin node-set is determined." final List<Item> originObjects; { if (originAttribute == null) { originObjects = null; } else { // There is an @origin attribute // "If the origin attribute is given, the origin node-set is the result of the evaluation of // the // origin attribute in the insert context." originObjects = actionInterpreter.evaluateKeepItems( actionElement, Collections.singletonList(insertContextItem), 1, originAttribute); // "The insert action is terminated with no effect if the origin node-set is the empty // node-set." if (originObjects.size() == 0) { if (indentedLogger.isDebugEnabled()) indentedLogger.logDebug("xf:insert", "origin node-set is empty, terminating"); return; } } } // "4. The insert location node is determined." int insertionIndex; { if (isEmptyNodesetBinding) { // "If the Node Set Binding node-set empty, then this attribute is ignored" insertionIndex = 0; } else if (atAttribute == null) { // "If the attribute is not given, then the default is the size of the Node Set Binding // node-set" insertionIndex = collectionToBeUpdated.size(); } else { // "a. The evaluation context node is the first node in document order from the Node Set // Binding // node-set, the context size is the size of the Node Set Binding node-set, and the context // position is 1." // "b. The return value is processed according to the rules of the XPath function round()" final String insertionIndexString = actionInterpreter.evaluateAsString( actionElement, collectionToBeUpdated, 1, "round(" + atAttribute + ")"); // "c. If the result is in the range 1 to the Node Set Binding node-set 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 Node Set Binding node-set size, so the // insert // location is the Node Set Binding node-set size." // Don't think we will get NaN with XPath 2.0... insertionIndex = "NaN".equals(insertionIndexString) ? collectionToBeUpdated.size() : Integer.parseInt(insertionIndexString); // Adjust index to be in range if (insertionIndex > collectionToBeUpdated.size()) insertionIndex = collectionToBeUpdated.size(); if (insertionIndex < 1) insertionIndex = 1; } } final String normalizedPosition; { if (resolvedPositionAttribute == null) { // Default value normalizedPosition = "after"; } else if ("after".equals(resolvedPositionAttribute) || "before".equals(resolvedPositionAttribute)) { // Specified value normalizedPosition = resolvedPositionAttribute; } else { // Invalid value if (indentedLogger.isInfoEnabled()) indentedLogger.logWarning( "xf:insert", "invalid position attribute, defaulting to \"after\"", "value", resolvedPositionAttribute); normalizedPosition = "after"; } } doInsert( containingDocument, indentedLogger, normalizedPosition, collectionToBeUpdated, (NodeInfo) insertContextItem, originObjects, insertionIndex, true, true, setRequireDefaultValues); }