mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-10 18:00:15 +01:00
908 lines
34 KiB
C++
908 lines
34 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is XForms code.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Novell, Inc.
|
|
* Portions created by the Initial Developer are Copyright (C) 2005
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Allan Beaufour <abeaufour@novell.com>
|
|
* Merle Sterling <msterlin@us.ibm.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include "nsIDOMAttr.h"
|
|
#include "nsIDOMEvent.h"
|
|
#include "nsIDOMNode.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIDOMNamedNodeMap.h"
|
|
#include "nsIXFormsRepeatElement.h"
|
|
#include "nsIXFormsControl.h"
|
|
#include "nsIXFormsContextInfo.h"
|
|
|
|
#include "nsString.h"
|
|
|
|
#include "nsIInstanceElementPrivate.h"
|
|
#include "nsXFormsActionModuleBase.h"
|
|
#include "nsXFormsActionElement.h"
|
|
#include "nsXFormsUtils.h"
|
|
#include "nsIDOM3Node.h"
|
|
|
|
#include "math.h"
|
|
|
|
#ifdef DEBUG
|
|
//#define DEBUG_XF_INSERTDELETE
|
|
#endif
|
|
|
|
/**
|
|
* Implementation of the XForms \<insert\> and \<delete\> elements.
|
|
*/
|
|
class nsXFormsInsertDeleteElement : public nsXFormsActionModuleBase
|
|
{
|
|
private:
|
|
PRBool mIsInsert;
|
|
|
|
enum Location {
|
|
eLocation_After,
|
|
eLocation_Before,
|
|
eLocation_FirstChild
|
|
};
|
|
|
|
// Context Info for events.
|
|
nsCOMArray<nsIXFormsContextInfo> mContextInfo;
|
|
|
|
/** Get the first node of a given type in aNodes.
|
|
*
|
|
* @param aNodes array of nodes
|
|
* @param aNodeType type of node to find
|
|
*
|
|
* @return aResult node of type aNodeType
|
|
*/
|
|
nsresult GetFirstNodeOfType(nsCOMArray<nsIDOMNode> *aNodes,
|
|
PRUint16 aNodeType,
|
|
nsIDOMNode **aResult);
|
|
|
|
/** Insert a node.
|
|
*
|
|
* @param aTargetNode target location node
|
|
* @param aNewNode node to insert
|
|
* @param aLocation insert location relative to target
|
|
*
|
|
* @return aResult result node
|
|
*/
|
|
nsresult InsertNode(nsIDOMNode *aTargetNode, nsIDOMNode *aNewNode,
|
|
Location aLocation, nsIDOMNode **aResNode);
|
|
|
|
nsresult RefreshRepeats(nsCOMArray<nsIDOMNode> *aNodes);
|
|
|
|
public:
|
|
|
|
/** Constructor */
|
|
nsXFormsInsertDeleteElement(PRBool aIsInsert) :
|
|
mIsInsert(aIsInsert)
|
|
{}
|
|
|
|
NS_IMETHOD
|
|
HandleAction(nsIDOMEvent* aEvent, nsIXFormsActionElement *aParentAction);
|
|
|
|
/**
|
|
* Normally, an action element implements the body of its action handler
|
|
* using this method; however, since an `nsXFormsInsertDeleteElement` sets
|
|
* up additional context before evaluating `if` and `while`, it overrides
|
|
* the `HandleAction` method instead to get full control over the
|
|
* execution of its action.
|
|
*/
|
|
nsresult HandleSingleAction(nsIDOMEvent* aEvent,
|
|
nsIXFormsActionElement *aParentAction)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
nsXFormsInsertDeleteElement::HandleAction(nsIDOMEvent *aEvent,
|
|
nsIXFormsActionElement *aParentAction)
|
|
{
|
|
// Keep element alive while iterating.
|
|
nsCOMPtr<nsIDOMElement> element = mElement;
|
|
if (!element)
|
|
return NS_OK;
|
|
|
|
mCurrentEvent = aEvent;
|
|
|
|
nsresult rv;
|
|
|
|
// Set the maximum run time for the loop (in microseconds).
|
|
PRTime microseconds = nsXFormsUtils::waitLimit * PR_USEC_PER_SEC;
|
|
PRTime runTime = 0, start = PR_Now();
|
|
while (PR_TRUE) {
|
|
//
|
|
// Step 1 (Insert or Delete): Determine the insert/delete context.
|
|
//
|
|
// If the bind attribute is present or if the context attribute is not given,
|
|
// the insert context is the in-scope evaluation context. Otherwise, the
|
|
// XPath expression provided by the context attribute is evaluated using the
|
|
// in-scope evaluation context, and the first node rule is applied to obtain
|
|
// the insert context.
|
|
//
|
|
nsCOMPtr<nsIDOMXPathResult> contextNodeset;
|
|
nsCOMPtr<nsIDOMNode> contextNode;
|
|
PRUint32 contextNodesetSize = 0;
|
|
PRInt32 contextPosition;
|
|
|
|
// Get the in-scope evaluation context. The last parameter to GetNodeContext
|
|
// indicates whether it should try to get the context using @bind. We want to
|
|
// ignore @bind because it should not change the in-scope evaluation context
|
|
// for insert.
|
|
nsCOMPtr<nsIModelElementPrivate> model;
|
|
nsCOMPtr<nsIDOMElement> bindElement;
|
|
nsCOMPtr<nsIXFormsControl> parentControl;
|
|
PRBool outerBind;
|
|
rv = nsXFormsUtils::GetNodeContext(element,
|
|
nsXFormsUtils::ELEMENT_WITH_MODEL_ATTR,
|
|
getter_AddRefs(model),
|
|
getter_AddRefs(bindElement),
|
|
&outerBind,
|
|
getter_AddRefs(parentControl),
|
|
getter_AddRefs(contextNode),
|
|
&contextPosition,
|
|
(PRInt32*)&contextNodesetSize,
|
|
PR_FALSE);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Determine if the context node is specified via @context.
|
|
// If @bind is present, @context is ignored.
|
|
nsAutoString bindExpr;
|
|
nsAutoString contextExpr;
|
|
element->GetAttribute(NS_LITERAL_STRING("bind"), bindExpr);
|
|
element->GetAttribute(NS_LITERAL_STRING("context"), contextExpr);
|
|
|
|
if (bindExpr.IsEmpty() && !contextExpr.IsEmpty()) {
|
|
// @context is specified and overrides the in-scope evaluation context.
|
|
// The insert context is the result of evaluating @context in the current
|
|
// in-scope evaluation context.
|
|
rv = nsXFormsUtils::EvaluateXPath(contextExpr, contextNode, element,
|
|
nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
|
getter_AddRefs(contextNodeset));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// The insert/delete action is terminated with no effect if the context
|
|
// is the empty node-set.
|
|
if (contextNodeset) {
|
|
rv = contextNodeset->GetSnapshotLength(&contextNodesetSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (contextNodesetSize < 1)
|
|
return NS_OK;
|
|
|
|
// Context node is the first node in the nodeset.
|
|
contextNodeset->SnapshotItem(0, getter_AddRefs(contextNode));
|
|
contextPosition = 1;
|
|
}
|
|
}
|
|
|
|
// The insert/delete action is terminated with no effect if the context
|
|
// is the empty node-set.
|
|
if (!contextNode)
|
|
return NS_OK;
|
|
|
|
// The insert action is terminated with no effect if the context attribute
|
|
// is given and the insert context does not evaluate to an element node.
|
|
if (mIsInsert && !contextExpr.IsEmpty()) {
|
|
PRUint16 nodeType;
|
|
contextNode->GetNodeType(&nodeType);
|
|
if (nodeType != nsIDOMNode::ELEMENT_NODE)
|
|
return NS_OK;
|
|
}
|
|
|
|
// Test the `if` and `while` attributes to determine whether this action
|
|
// can be performed and should be repeated. As the insert and delete
|
|
// actions can change their context, we do this testing here instead of
|
|
// relying on the default logic from our parent class' `HandleAction`
|
|
// (or `DoHandleAction`) definition.
|
|
PRBool usesWhile;
|
|
if (!nsXFormsActionModuleBase::CanPerformAction(element, &usesWhile,
|
|
contextNode,
|
|
contextNodesetSize,
|
|
contextPosition)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// Step 2 (Insert or Delete): Determine the node-set binding.
|
|
//
|
|
// If the bind attribute is present, it directly determines the Node Set
|
|
// Binding node-set. If a nodeset attribute is present, it is evaluated
|
|
// within the context to determine the Node Set Binding node-set.
|
|
//
|
|
// A NodeSet binding attribute (@bind or @nodeset) is required unless
|
|
// the context attribute is present.
|
|
//
|
|
nsCOMPtr<nsIDOMXPathResult> nodeset;
|
|
PRUint32 nodesetSize = 0;
|
|
nsAutoString nodesetExpr;
|
|
|
|
if (bindExpr.IsEmpty()) {
|
|
element->GetAttribute(NS_LITERAL_STRING("nodeset"), nodesetExpr);
|
|
if (!nodesetExpr.IsEmpty()) {
|
|
// Evaluate the nodeset attribute within the insert context.
|
|
rv = nsXFormsUtils::EvaluateXPath(nodesetExpr, contextNode, element,
|
|
nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
|
getter_AddRefs(nodeset));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
} else {
|
|
PRBool usesModelBinding;
|
|
rv = nsXFormsUtils::EvaluateNodeBinding(element,
|
|
nsXFormsUtils::ELEMENT_WITH_MODEL_ATTR,
|
|
EmptyString(),
|
|
EmptyString(),
|
|
nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
|
getter_AddRefs(model),
|
|
getter_AddRefs(nodeset),
|
|
&usesModelBinding);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!model)
|
|
return NS_OK;
|
|
}
|
|
|
|
if (nodeset) {
|
|
rv = nodeset->GetSnapshotLength(&nodesetSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// The insert action is terminated with no effect if the context attribute
|
|
// is not given and the Node Set Binding node-set is the empty node-set.
|
|
//
|
|
// The delete action is terminated with no effect if the Node Set Binding
|
|
// node-set is the empty node-set.
|
|
if (!nodeset || nodesetSize < 1) {
|
|
if (!mIsInsert || (mIsInsert && contextExpr.IsEmpty()))
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// Step 3 (Insert): Determine the origin node-set.
|
|
//
|
|
nsCOMPtr<nsIDOMXPathResult> originNodeset;
|
|
nsCOMPtr<nsIDOMXPathResult> originNode;
|
|
PRUint32 originNodesetSize = 0;
|
|
|
|
if (mIsInsert) {
|
|
// If the origin attribute is not given and the Node Set Binding node-set
|
|
// is empty, then the origin node-set is the empty node-set. Otherwise,
|
|
// if the origin attribute is not given, then the origin node-set consists
|
|
// of the last node of the Node Set Binding node-set (which we will obtain
|
|
// just before performing the insert).
|
|
//
|
|
// If the origin attribute is given, the origin node-set is the result of
|
|
// the evaluation of the origin attribute in the insert context.
|
|
nsAutoString origin;
|
|
element->GetAttribute(NS_LITERAL_STRING("origin"), origin);
|
|
|
|
if (!origin.IsEmpty()) {
|
|
rv = nsXFormsUtils::EvaluateXPath(origin, contextNode, element,
|
|
nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
|
getter_AddRefs(originNodeset));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// The insert action is terminated with no effect if the origin node-set
|
|
// is the empty node-set.
|
|
if (!originNodeset)
|
|
return NS_OK;
|
|
|
|
rv = originNodeset->GetSnapshotLength(&originNodesetSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// The insert action is terminated with no effect if the origin node-set
|
|
// is the empty node-set.
|
|
if (originNodesetSize < 1)
|
|
return NS_OK;
|
|
|
|
// Context Info: 'origin-nodes'
|
|
nsCOMPtr<nsXFormsContextInfo> contextInfo =
|
|
new nsXFormsContextInfo(element);
|
|
NS_ENSURE_TRUE(contextInfo, NS_ERROR_OUT_OF_MEMORY);
|
|
contextInfo->SetNodesetValue("origin-nodes", originNodeset);
|
|
mContextInfo.AppendObject(contextInfo);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Step 4 (Insert), Step 3 (Delete):
|
|
// Determine the insert/delete location node.
|
|
//
|
|
// Insert: If the Node Set Binding node-set is not specified or empty, the
|
|
// insert location node is the insert context node. Otherwise, if the at
|
|
// attribute is not given, then the insert location node is the last node
|
|
// of the Node Set Binding node-set. Otherwise, an insert location node is
|
|
// determined from the at attribute.
|
|
//
|
|
// Delete: If the at attribute is not specified, there is no delete location.
|
|
// Otherwise, the delete location is determined by evaluating the XPath
|
|
// expression specified by the at attribute.
|
|
//
|
|
|
|
nsCOMPtr<nsIDOMNode> locationNode;
|
|
PRUint32 atInt = 0;
|
|
double atDoub = 0;
|
|
|
|
nsAutoString atExpr;
|
|
element->GetAttribute(NS_LITERAL_STRING("at"), atExpr);
|
|
|
|
if (mIsInsert) {
|
|
if (!nodeset || nodesetSize < 1) {
|
|
// The insert location node is the insert context node.
|
|
locationNode = contextNode;
|
|
} else if (atExpr.IsEmpty()) {
|
|
// The insert location node is the last node of the Node Set Binding
|
|
// node-set.
|
|
nodeset->SnapshotItem(nodesetSize - 1, getter_AddRefs(locationNode));
|
|
NS_ENSURE_STATE(locationNode);
|
|
}
|
|
}
|
|
|
|
if (!locationNode) {
|
|
// For insert, we have a nodeset and got past the special cases of an empty
|
|
// nodeset or no @at expression so the insert location node is determined by
|
|
// @at.
|
|
//
|
|
// For delete, the delete location is determined by the @at expression if
|
|
// present; otherwise there is no delete location and each node in the
|
|
// Node Set Binding node-set is deleted, unless the node is the root
|
|
// document element of an instance.
|
|
if (!atExpr.IsEmpty()) {
|
|
// The evaluation context node is the first node in document order of
|
|
// the Node Set Binding node-set.
|
|
nsCOMPtr<nsIDOMNode> evalContextNode;
|
|
nodeset->SnapshotItem(0, getter_AddRefs(evalContextNode));
|
|
|
|
// The context size is the size of the Node Set Binding node-set and
|
|
// the context position is 1.
|
|
nsCOMPtr<nsIDOMXPathResult> xpRes;
|
|
rv = nsXFormsUtils::EvaluateXPath(atExpr, evalContextNode, element,
|
|
nsIDOMXPathResult::NUMBER_TYPE,
|
|
getter_AddRefs(xpRes), 1, nodesetSize);
|
|
|
|
if (xpRes) {
|
|
rv = xpRes->GetNumberValue(&atDoub);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// Determine the insert/delete location.
|
|
if (atDoub < 1) {
|
|
atInt = 1;
|
|
} else {
|
|
// If the location is greater than the nodeset size or NaN,
|
|
// the location is the end of the nodeset.
|
|
// XXX: Need to check for NaN but isnan() is not portable.
|
|
atInt = (PRInt32) floor(atDoub+0.5);
|
|
if (atInt > nodesetSize)
|
|
atInt = nodesetSize;
|
|
}
|
|
|
|
// The location node is the node in the Node Set Binding node-set at
|
|
// the position given by the location.
|
|
nodeset->SnapshotItem(atInt - 1, getter_AddRefs(locationNode));
|
|
NS_ENSURE_STATE(locationNode);
|
|
}
|
|
}
|
|
|
|
// Context Info: 'insert-location-node' or 'delete-location'
|
|
nsCOMPtr<nsXFormsContextInfo> contextInfo;
|
|
if (mIsInsert) {
|
|
contextInfo = new nsXFormsContextInfo(element);
|
|
NS_ENSURE_TRUE(contextInfo, NS_ERROR_OUT_OF_MEMORY);
|
|
contextInfo->SetNodeValue("insert-location-node", locationNode);
|
|
mContextInfo.AppendObject(contextInfo);
|
|
} else {
|
|
contextInfo = new nsXFormsContextInfo(element);
|
|
NS_ENSURE_TRUE(contextInfo, NS_ERROR_OUT_OF_MEMORY);
|
|
contextInfo->SetNumberValue("delete-location", atInt);
|
|
mContextInfo.AppendObject(contextInfo);
|
|
}
|
|
|
|
//
|
|
// Step 5 (Insert): Each node in the origin node-set is cloned in the
|
|
// order it appears in the origin node-set. If the origin node-set is empty
|
|
// (Step 3), the origin node-set consists of the last node of the Node Set
|
|
// Binding node-set.
|
|
//
|
|
// The clones are deep copies of the original nodes except the contents of
|
|
// nodes of type xsd:ID are modified to remain as unique values in the
|
|
// instance data after the clones are inserted.
|
|
//
|
|
// XXX: Need to modify the contents of nodes of type xsd:ID to remain
|
|
// unique.
|
|
|
|
nsCOMArray<nsIDOMNode> cloneNodes;
|
|
nsCOMPtr<nsIDOMXPathResult> cloneNodeset;
|
|
PRUint32 cloneNodesetSize = 0;
|
|
|
|
if (mIsInsert) {
|
|
nsCOMPtr<nsIDOMNode> prototypeNode, newNode;
|
|
PRUint32 cloneIndex;
|
|
|
|
// Get prototype node(s) and clone.
|
|
if (originNodesetSize < 1) {
|
|
// Origin nodeset is empty. Clone the last node of nodeset.
|
|
cloneNodeset = nodeset;
|
|
cloneNodesetSize = nodesetSize;
|
|
cloneIndex = nodesetSize - 1;
|
|
} else {
|
|
// Clone all the nodes in the origin node-set.
|
|
cloneNodeset = originNodeset;
|
|
cloneNodesetSize = originNodesetSize;
|
|
cloneIndex = 0;
|
|
}
|
|
// Context Info: 'inserted-nodes'
|
|
nsCOMPtr<nsXFormsContextInfo> contextInfo =
|
|
new nsXFormsContextInfo(element);
|
|
NS_ENSURE_TRUE(contextInfo, NS_ERROR_OUT_OF_MEMORY);
|
|
contextInfo->SetNodesetValue("inserted-nodes", cloneNodeset);
|
|
mContextInfo.AppendObject(contextInfo);
|
|
|
|
cloneNodeset->SnapshotItem(cloneIndex, getter_AddRefs(prototypeNode));
|
|
NS_ENSURE_STATE(prototypeNode);
|
|
|
|
// The prototypeNode (node to be cloned) and the locationNode (node to
|
|
// which the clone will be inserted) may belong to different instances.
|
|
nsCOMPtr<nsIDOMDocument> originDoc, locationDoc;
|
|
prototypeNode->GetOwnerDocument(getter_AddRefs(originDoc));
|
|
NS_ENSURE_STATE(originDoc);
|
|
locationNode->GetOwnerDocument(getter_AddRefs(locationDoc));
|
|
NS_ENSURE_STATE(locationDoc);
|
|
|
|
while ((cloneIndex < cloneNodesetSize) && prototypeNode) {
|
|
if (!SameCOMIdentity(originDoc, locationDoc)) {
|
|
locationDoc->ImportNode(prototypeNode, PR_TRUE, getter_AddRefs(newNode));
|
|
} else {
|
|
prototypeNode->CloneNode(PR_TRUE, getter_AddRefs(newNode));
|
|
}
|
|
NS_ENSURE_STATE(newNode);
|
|
cloneNodes.AppendObject(newNode);
|
|
|
|
// Get the next node in the node-set.
|
|
++cloneIndex;
|
|
cloneNodeset->SnapshotItem(cloneIndex, getter_AddRefs(prototypeNode));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Step 6 and 7 (Insert): Determine the target location (Steps 6a-d) and
|
|
// insert all of the nodes that were cloned in Step 5.
|
|
//
|
|
// Step 4 (Delete): Delete the nodes.
|
|
//
|
|
|
|
nsCOMPtr<nsIDOMDocument> locationDoc;
|
|
nsCOMPtr<nsIDOMElement> locationDocElement;
|
|
nsCOMPtr<nsIDOMNode> parentNode, newNode, resNode, instNode;
|
|
if (mIsInsert) {
|
|
// The cloned node or nodes are inserted in the order they were cloned at
|
|
// their target location depending on their node type.
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
|
|
for (PRInt32 i = 0; i < cloneNodes.Count(); ++i) {
|
|
// Node to be inserted.
|
|
newNode = cloneNodes[i];
|
|
|
|
// Get the node type of the insert node and location node.
|
|
PRUint16 newNodeType, locationNodeType;
|
|
newNode->GetNodeType(&newNodeType);
|
|
locationNode->GetNodeType(&locationNodeType);
|
|
|
|
// Step 6a - If the Node Set Binding node-set is not specified or empty
|
|
// OR Step 6b - If the Node Set Binding node-set is specified and not
|
|
// empty and the type of the cloned node is different from the type of
|
|
// the insert location node, the target location depends on the node
|
|
// type of the cloned node.
|
|
//
|
|
// If the cloned node is an attribute, then the target location is before
|
|
// the first attribute of the insert location node. If the cloned node is
|
|
// not an attribute, then the target location is before the first child
|
|
// of the insert location node.
|
|
if ((!nodeset || nodesetSize < 1) ||
|
|
(nodeset && nodesetSize > 0 && newNodeType != locationNodeType)) {
|
|
Location location = eLocation_Before;
|
|
nsCOMPtr<nsIDOMNode> targetNode;
|
|
if (newNodeType != nsIDOMNode::ATTRIBUTE_NODE) {
|
|
// Target location is before the first child of location node. If the
|
|
// location node is empty (has no children), it remains the location
|
|
// node and the new node will become the first child of the location
|
|
// node.
|
|
locationNode->GetFirstChild(getter_AddRefs(targetNode));
|
|
if (!targetNode) {
|
|
// New node will become first child of locationNode.
|
|
location = eLocation_FirstChild;
|
|
targetNode = locationNode;
|
|
}
|
|
}
|
|
InsertNode(targetNode, newNode, location, getter_AddRefs(resNode));
|
|
} else {
|
|
// Step 6c - If insert location node is the root element of an
|
|
// instance, then that instance root element location is the target
|
|
// location and the cloned node replaces the instance element. If
|
|
// there is more than one cloned node to insert, only the first node
|
|
// that does not cause a conflict is considered.
|
|
//
|
|
locationNode->GetOwnerDocument(getter_AddRefs(locationDoc));
|
|
NS_ENSURE_STATE(locationDoc);
|
|
locationDoc->GetDocumentElement(getter_AddRefs(locationDocElement));
|
|
|
|
if (SameCOMIdentity(locationNode, locationDocElement)) {
|
|
// Step 7 - Replace the instance element with the first element
|
|
// node of the cloned node(s).
|
|
nsCOMPtr<nsIDOMNode> insertNode;
|
|
GetFirstNodeOfType(&cloneNodes, nsIDOMNode::ELEMENT_NODE,
|
|
getter_AddRefs(insertNode));
|
|
if (insertNode) {
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
locationDoc->RemoveChild(locationNode, getter_AddRefs(child));
|
|
locationDoc->AppendChild(insertNode, getter_AddRefs(resNode));
|
|
// Done...because we only consider the first node that does
|
|
// not cause a conflict.
|
|
break;
|
|
}
|
|
} else {
|
|
// Step 6d - the target location is immediately before or after the
|
|
// insert location node, based on the position attribute setting or
|
|
// its default.
|
|
PRBool insertAfter = PR_TRUE;
|
|
nsAutoString position;
|
|
element->GetAttribute(NS_LITERAL_STRING("position"), position);
|
|
if (!position.IsEmpty()) {
|
|
if (position.EqualsLiteral("before")) {
|
|
insertAfter = PR_FALSE;
|
|
} else if (!position.EqualsLiteral("after")) {
|
|
// This is not a valid document...
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
// Context Info: 'position'.
|
|
nsCOMPtr<nsXFormsContextInfo> contextInfo =
|
|
new nsXFormsContextInfo(element);
|
|
NS_ENSURE_TRUE(contextInfo, NS_ERROR_OUT_OF_MEMORY);
|
|
contextInfo->SetStringValue("position", position);
|
|
mContextInfo.AppendObject(contextInfo);
|
|
|
|
InsertNode(locationNode, newNode,
|
|
insertAfter ? eLocation_After: eLocation_Before,
|
|
getter_AddRefs(resNode));
|
|
}
|
|
}
|
|
}
|
|
rv = nsXFormsUtils::GetInstanceNodeForData(resNode, getter_AddRefs(instNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Step 8: Set indexes for repeats
|
|
rv = RefreshRepeats(&cloneNodes);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
} else {
|
|
// Delete
|
|
// If there is no delete location, each node in the Node Set Binding
|
|
// node-set is deleted, unless the node is the root document element of an
|
|
// instance.
|
|
//
|
|
// If there is a delete location, the node at the delete location in the
|
|
// Node Set Binding node-set is deleted, unless the node is the root
|
|
// document element of an instance.
|
|
PRBool didDelete = PR_FALSE;
|
|
|
|
PRUint32 deleteIndex, deleteCount;
|
|
|
|
if (!locationNode) {
|
|
// Delete all the nodes in the node-set.
|
|
deleteIndex = 0;
|
|
deleteCount = nodesetSize;
|
|
} else {
|
|
// Delete the node at the delete location.
|
|
deleteIndex = atInt - 1;
|
|
deleteCount = atInt;
|
|
}
|
|
// Context Info: 'deleted-nodes'
|
|
nsCOMPtr<nsXFormsContextInfo> contextInfo =
|
|
new nsXFormsContextInfo(element);
|
|
NS_ENSURE_TRUE(contextInfo, NS_ERROR_OUT_OF_MEMORY);
|
|
contextInfo->SetNodesetValue("deleted-nodes", nodeset);
|
|
mContextInfo.AppendObject(contextInfo);
|
|
|
|
nodeset->SnapshotItem(deleteIndex, getter_AddRefs(locationNode));
|
|
NS_ENSURE_STATE(locationNode);
|
|
|
|
locationNode->GetOwnerDocument(getter_AddRefs(locationDoc));
|
|
NS_ENSURE_STATE(locationDoc);
|
|
|
|
rv = nsXFormsUtils::GetInstanceNodeForData(locationNode, getter_AddRefs(instNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
locationDoc->GetDocumentElement(getter_AddRefs(locationDocElement));
|
|
while ((deleteIndex < deleteCount) && locationNode) {
|
|
// Delete the node(s) unless the delete location is the root document
|
|
// element of an instance.
|
|
PRUint16 locationNodeType;
|
|
locationNode->GetNodeType(&locationNodeType);
|
|
if (locationNodeType == nsIDOMNode::ATTRIBUTE_NODE) {
|
|
nsCOMPtr<nsIDOMElement> ownerElement;
|
|
nsCOMPtr<nsIDOMAttr> attrNode(do_QueryInterface(locationNode));
|
|
attrNode->GetOwnerElement(getter_AddRefs(ownerElement));
|
|
NS_ENSURE_STATE(ownerElement);
|
|
|
|
nsCOMPtr<nsIDOMAttr> resAttr;
|
|
ownerElement->RemoveAttributeNode(attrNode, getter_AddRefs(resAttr));
|
|
resNode = locationNode;
|
|
|
|
// Deleted at least one node so delete will not terminate.
|
|
didDelete = PR_TRUE;
|
|
} else {
|
|
if (!SameCOMIdentity(locationNode, locationDocElement)) {
|
|
locationNode->GetParentNode(getter_AddRefs(parentNode));
|
|
NS_ENSURE_STATE(parentNode);
|
|
|
|
rv = parentNode->RemoveChild(locationNode, getter_AddRefs(resNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Deleted at least one node so delete will not terminate.
|
|
didDelete = PR_TRUE;
|
|
}
|
|
}
|
|
// Get the next node in the node-set.
|
|
++deleteIndex;
|
|
nodeset->SnapshotItem(deleteIndex, getter_AddRefs(locationNode));
|
|
}
|
|
|
|
// The delete action is terminated with no effect if no node is deleted.
|
|
if (!didDelete)
|
|
return NS_OK;
|
|
}
|
|
NS_ENSURE_STATE(resNode);
|
|
|
|
// Dispatch xforms-insert/delete event to the instance node we have modified
|
|
// data for
|
|
rv = nsXFormsUtils::DispatchEvent(instNode,
|
|
mIsInsert ? eEvent_Insert : eEvent_Delete,
|
|
nsnull, nsnull, &mContextInfo);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Dispatch refreshing events to the model
|
|
if (aParentAction) {
|
|
aParentAction->SetRebuild(model, PR_TRUE);
|
|
aParentAction->SetRecalculate(model, PR_TRUE);
|
|
aParentAction->SetRevalidate(model, PR_TRUE);
|
|
aParentAction->SetRefresh(model, PR_TRUE);
|
|
} else {
|
|
rv = model->RequestRebuild();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = model->RequestRecalculate();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = model->RequestRevalidate();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = model->RequestRefresh();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// Repeat this action if it can iterate and if it uses the `while`
|
|
// attribute (the expression of which must have evaluated to true to
|
|
// arrive here).
|
|
if (!CanIterate() || !usesWhile) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// See if we've exceeded our time limit, and if so, prompt the user to
|
|
// determine if she wants to cancel the loop.
|
|
LL_SUB(runTime, PR_Now(), start);
|
|
if (microseconds <= 0 || runTime < microseconds) {
|
|
continue;
|
|
}
|
|
|
|
// The remaining part of the loop prompts the user about cancelling the
|
|
// loop, and is only executed if we've gone over the time limit.
|
|
PRBool stopWaiting = nsXFormsUtils::AskStopWaiting(element);
|
|
|
|
if (stopWaiting) {
|
|
// Stop the loop
|
|
return NS_OK;
|
|
} else {
|
|
start = PR_Now();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsXFormsInsertDeleteElement::GetFirstNodeOfType(nsCOMArray<nsIDOMNode> *aNodes,
|
|
PRUint16 aNodeType,
|
|
nsIDOMNode **aResult)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> currentNode;
|
|
|
|
for (PRInt32 i = 0; i < aNodes->Count(); ++i) {
|
|
currentNode = aNodes->ObjectAt(i);
|
|
PRUint16 nodeType;
|
|
currentNode->GetNodeType(&nodeType);
|
|
if (nodeType == aNodeType) {
|
|
NS_IF_ADDREF(*aResult = currentNode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXFormsInsertDeleteElement::InsertNode(nsIDOMNode *aTargetNode,
|
|
nsIDOMNode *aNewNode,
|
|
Location aLocation,
|
|
nsIDOMNode **aResNode)
|
|
{
|
|
NS_ENSURE_ARG(aTargetNode);
|
|
NS_ENSURE_ARG(aNewNode);
|
|
NS_ENSURE_ARG_POINTER(aResNode);
|
|
|
|
// Make sure the result node is null in case we encounter a condition
|
|
// where the node cannot be inserted and is skipped.
|
|
*aResNode = nsnull;
|
|
|
|
// Step 7 - The new node is inserted at the target location depending on its
|
|
// node type. If the cloned node is a duplicate of another attribute in its
|
|
// parent element, then the duplicate attribute is first removed. If a cloned
|
|
// node cannot be placed at the target location due to a node type conflict,
|
|
// then the insertion for that particular clone node is ignored.
|
|
nsCOMPtr<nsIDOMNode> resNode;
|
|
|
|
PRUint16 targetNodeType, newNodeType;
|
|
aTargetNode->GetNodeType(&targetNodeType);
|
|
aNewNode->GetNodeType(&newNodeType);
|
|
|
|
if (newNodeType == nsIDOMNode::ATTRIBUTE_NODE) {
|
|
// Can add an attribute to an element node or the owning element
|
|
// of an attribute node.
|
|
nsCOMPtr<nsIDOMElement> ownerElement;
|
|
|
|
if (targetNodeType == nsIDOMNode::ELEMENT_NODE) {
|
|
ownerElement = do_QueryInterface(aTargetNode);
|
|
} else if (targetNodeType == nsIDOMNode::ATTRIBUTE_NODE) {
|
|
nsCOMPtr<nsIDOMAttr> targetAttrNode(do_QueryInterface(aTargetNode));
|
|
targetAttrNode->GetOwnerElement(getter_AddRefs(ownerElement));
|
|
}
|
|
NS_ENSURE_STATE(ownerElement);
|
|
|
|
// Check for a duplicate attribute.
|
|
nsAutoString attrNamespace, attrName, attrValue;
|
|
aNewNode->GetNamespaceURI(attrNamespace);
|
|
aNewNode->GetLocalName(attrName);
|
|
aNewNode->GetNodeValue(attrValue);
|
|
|
|
PRBool hasAttribute = PR_FALSE;
|
|
ownerElement->HasAttributeNS(attrNamespace, attrName, &hasAttribute);
|
|
if (hasAttribute) {
|
|
ownerElement->RemoveAttributeNS(attrNamespace, attrName);
|
|
}
|
|
ownerElement->SetAttributeNS(attrNamespace, attrName, attrValue);
|
|
resNode = aTargetNode;
|
|
resNode.swap(*aResNode);
|
|
|
|
} else {
|
|
// New node will be inserted at location aLocation.
|
|
nsCOMPtr<nsIDOMNode> targetNode = aTargetNode;
|
|
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
targetNode->GetParentNode(getter_AddRefs(parentNode));
|
|
NS_ENSURE_STATE(parentNode);
|
|
|
|
if (aLocation == eLocation_FirstChild) {
|
|
aTargetNode->AppendChild(aNewNode, getter_AddRefs(resNode));
|
|
resNode.swap(*aResNode);
|
|
} else {
|
|
if (aLocation == eLocation_After) {
|
|
// If we're at the end of the nodeset, this returns nsnull, which is
|
|
// fine, because InsertBefore then inserts at the end of the nodeset.
|
|
aTargetNode->GetNextSibling(getter_AddRefs(targetNode));
|
|
}
|
|
parentNode->InsertBefore(aNewNode, targetNode,
|
|
getter_AddRefs(resNode));
|
|
resNode.swap(*aResNode);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsXFormsInsertDeleteElement::RefreshRepeats(nsCOMArray<nsIDOMNode> *aNodes)
|
|
{
|
|
// XXXbeaufour: only check repeats belonging to the same model...
|
|
// possibly use mFormControls? Should be quicker than searching through
|
|
// entire document!! mModel->GetControls("repeat"); Would also possibly
|
|
// save a QI?
|
|
|
|
nsCOMPtr<nsIDOMDocument> document;
|
|
|
|
nsresult rv = mElement->GetOwnerDocument(getter_AddRefs(document));
|
|
NS_ENSURE_STATE(document);
|
|
|
|
nsCOMPtr<nsIDOMNodeList> repeatNodes;
|
|
document->GetElementsByTagNameNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS),
|
|
NS_LITERAL_STRING("repeat"),
|
|
getter_AddRefs(repeatNodes));
|
|
NS_ENSURE_STATE(repeatNodes);
|
|
|
|
// work over each node and if the node contains the inserted element
|
|
PRUint32 nodeCount;
|
|
rv = repeatNodes->GetLength(&nodeCount);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (PRUint32 node = 0; node < nodeCount; ++node) {
|
|
nsCOMPtr<nsIDOMNode> repeatNode;
|
|
|
|
rv = repeatNodes->Item(node, getter_AddRefs(repeatNode));
|
|
nsCOMPtr<nsIXFormsRepeatElement> repeatEl(do_QueryInterface(repeatNode));
|
|
NS_ENSURE_STATE(repeatEl);
|
|
|
|
for (PRInt32 i = 0; i < aNodes->Count(); ++i) {
|
|
nsCOMPtr<nsIDOMNode> newNode = aNodes->ObjectAt(i);
|
|
rv = repeatEl->HandleNodeInsert(newNode);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_HIDDEN_(nsresult)
|
|
NS_NewXFormsInsertElement(nsIXTFElement **aResult)
|
|
{
|
|
*aResult = new nsXFormsInsertDeleteElement(PR_TRUE);
|
|
if (!*aResult)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_HIDDEN_(nsresult)
|
|
NS_NewXFormsDeleteElement(nsIXTFElement **aResult)
|
|
{
|
|
*aResult = new nsXFormsInsertDeleteElement(PR_FALSE);
|
|
if (!*aResult)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*aResult);
|
|
return NS_OK;
|
|
}
|