Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone node #89

Merged
merged 11 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"fs.js",
"loader.js"
],
"smartStep": false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this comment at the row above:
// smartStep can be enabled again once microsoft/vscode#68616 is fixed. TLDR; smartStep causes parts of code to not be highlighted when debugging unit tests.

"cwd": "${workspaceRoot}",
"internalConsoleOptions": "openOnSessionStart"
},
Expand Down
7 changes: 5 additions & 2 deletions src/domFacade/ConcreteNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const enum NODE_TYPES {
ELEMENT_NODE = 1,
ATTRIBUTE_NODE = 2,
TEXT_NODE = 3,
CDATA_SECION_NODE = 4,
CDATA_SECTION_NODE = 4,
PROCESSING_INSTRUCTION_NODE = 7,
COMMENT_NODE = 8,
DOCUMENT_NODE = 9,
Expand All @@ -18,7 +18,8 @@ export type ConcreteChildNode =
| ConcreteElementNode
| ConcreteTextNode
| ConcreteProcessingInstructionNode
| ConcreteCommentNode;
| ConcreteCommentNode
| ConcreteCDATASectionNode;

export type ConcreteElementNode = Element & { nodeType: NODE_TYPES.ELEMENT_NODE };

Expand All @@ -33,6 +34,8 @@ export type ConcreteProcessingInstructionNode = ProcessingInstruction & {

export type ConcreteCommentNode = Comment & { nodeType: NODE_TYPES.COMMENT_NODE };

export type ConcreteCDATASectionNode = CDATASection & { nodeType: NODE_TYPES.CDATA_SECTION_NODE };

export type ConcreteAttributeNode = Attr & { nodeType: NODE_TYPES.ATTRIBUTE_NODE };

export type ConcreteDocumentNode = Document & { nodeType: NODE_TYPES.DOCUMENT_NODE };
Expand Down
70 changes: 61 additions & 9 deletions src/expressions/xquery-update/TransformExpression.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import Expression, { RESULT_ORDERINGS } from '../Expression';

import IDocumentWriter from '../../documentWriter/IDocumentWriter';
import {
ConcreteDocumentNode,
ConcreteElementNode,
ConcreteNode,
NODE_TYPES
} from '../../domFacade/ConcreteNode';
import IWrappingDomFacade from '../../domFacade/IWrappingDomFacade';
import INodesFactory from '../../nodesFactory/INodesFactory';
import createNodeValue from '../dataTypes/createNodeValue';
import isSubTypeOf from '../dataTypes/isSubtypeOf';
import sequenceFactory from '../dataTypes/sequenceFactory';
import QName from '../dataTypes/valueTypes/QName';
import ExecutionParameters from '../ExecutionParameters';
import Expression, { RESULT_ORDERINGS } from '../Expression';
import Specificity from '../Specificity';
import { ready } from '../util/iterators';
import { mergeUpdates } from './pulRoutines';
import { applyUpdates } from './pulRoutines';
import { applyUpdates, mergeUpdates } from './pulRoutines';
import UpdatingExpression from './UpdatingExpression';
import { errXUDY0014, errXUDY0037, errXUTY0013 } from './XQueryUpdateFacilityErrors';

function deepCloneNode(node) {
function deepCloneNode(
node: ConcreteNode,
domFacade: IWrappingDomFacade,
nodesFactory: INodesFactory,
documentWriter: IDocumentWriter
) {
// Each copied node receives a new node identity. The parent, children, and attributes properties of the copied nodes are set so as to preserve their inter-node relationships. The parent property of the copy of $node is set to empty. Other properties of the copied nodes are determined as follows:

// For a copied document node, the document-uri property is set to empty.
Expand All @@ -21,8 +34,45 @@ function deepCloneNode(node) {
// Note:Implementations that store only the typed value of a node are required at this point to convert the typed value to a string form.
// If copy-namespaces mode in the static context specifies preserve, all in-scope-namespaces of the original element are retained in the new copy. If copy-namespaces mode specifies no-preserve, the new copy retains only those in-scope namespaces of the original element that are used in the names of the element and its attributes.
// All other properties of the copied nodes are preserved.
// TODO: Consider doing this with an INodesFactory.
return createNodeValue(node.value.cloneNode(true));

switch (node.nodeType) {
case NODE_TYPES.ELEMENT_NODE:
const cloneElem = nodesFactory.createElementNS(node.namespaceURI, node.nodeName);
domFacade
.getAllAttributes(node)
.forEach(attr =>
documentWriter.setAttributeNS(
cloneElem,
attr.namespaceURI,
attr.name,
attr.value
)
);
for (const child of domFacade.getChildNodes(node)) {
const descendant = deepCloneNode(child, domFacade, nodesFactory, documentWriter);
documentWriter.insertBefore(cloneElem as ConcreteElementNode, descendant, null);
}
return cloneElem;
case NODE_TYPES.ATTRIBUTE_NODE:
const cloneAttr = nodesFactory.createAttributeNS(node.namespaceURI, node.nodeName);
cloneAttr.value = node.value;
return cloneAttr;
case NODE_TYPES.CDATA_SECTION_NODE:
return nodesFactory.createCDATASection(node.data);
case NODE_TYPES.COMMENT_NODE:
return nodesFactory.createComment(node.data);
case NODE_TYPES.DOCUMENT_NODE:
const cloneDoc = nodesFactory.createDocument();
for (const child of domFacade.getChildNodes(node)) {
const descendant = deepCloneNode(child, domFacade, nodesFactory, documentWriter);
documentWriter.insertBefore(cloneDoc as ConcreteDocumentNode, descendant, null);
}
return cloneDoc;
case NODE_TYPES.PROCESSING_INSTRUCTION_NODE:
return nodesFactory.createProcessingInstruction(node.target, node.data);
case NODE_TYPES.TEXT_NODE:
return nodesFactory.createTextNode(node.data);
}
}

function isCreatedNode(node, createdNodes, domFacade) {
Expand Down Expand Up @@ -64,7 +114,7 @@ class TransformExpression extends UpdatingExpression {
this._returnExpr = returnExpr;
}

public evaluateWithUpdateList(dynamicContext, executionParameters) {
public evaluateWithUpdateList(dynamicContext, executionParameters: ExecutionParameters) {
const { domFacade, nodesFactory, documentWriter } = executionParameters;

const sourceValueIterators = [];
Expand Down Expand Up @@ -106,7 +156,9 @@ class TransformExpression extends UpdatingExpression {
const node = sv.value.xdmValue[0];

// A new copy is made of $node and all nodes that have $node as an ancestor, collectively referred to as copied nodes.
const copiedNodes = deepCloneNode(node);
const copiedNodes = createNodeValue(
deepCloneNode(node.value, domFacade, nodesFactory, documentWriter)
);
createdNodes.push(copiedNodes.value);
toMergePuls.push(sv.value.pendingUpdateList);

Expand Down
18 changes: 18 additions & 0 deletions src/nodesFactory/DomBackedNodesFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ export default class DomBackedNodesFactory implements INodesFactory {
return this._documentNode.createAttributeNS(namespaceURI, name);
}

public createCDATASection(contents) {
if (!this._documentNode) {
throw new Error(
'Please pass a node factory if an XQuery script uses node constructors'
);
}
return this._documentNode.createCDATASection(contents);
}

public createComment(contents) {
if (!this._documentNode) {
throw new Error(
Expand All @@ -39,6 +48,15 @@ export default class DomBackedNodesFactory implements INodesFactory {
return this._documentNode.createComment(contents);
}

public createDocument() {
if (!this._documentNode) {
throw new Error(
'Please pass a node factory if an XQuery script uses node constructors'
);
}
return this._documentNode.implementation.createDocument(null, null, null);
}

public createElementNS(namespaceURI, name) {
if (!this._documentNode) {
throw new Error(
Expand Down
4 changes: 4 additions & 0 deletions src/nodesFactory/INodesFactory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
export default interface INodesFactory {
createAttributeNS(namespaceURI: string, name: string): Attr;

createCDATASection(contents: string): CDATASection;

createComment(contents: string): Comment;

createDocument(): Document;

createElementNS(namespaceURI: string, name: string): Element;

createProcessingInstruction(target: string, data: string): ProcessingInstruction;
Expand Down
8 changes: 8 additions & 0 deletions src/nodesFactory/wrapExternalNodesFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ class WrappingNodesFactory implements INodesFactory {
return this._externalNodesFactory['createAttributeNS'](namespaceURI, name);
}

public createCDATASection(contents) {
return this._externalNodesFactory['createCDATASection'](contents);
}

public createComment(contents) {
return this._externalNodesFactory['createComment'](contents);
}

public createDocument() {
return this._externalNodesFactory['createDocument']();
}

public createElementNS(namespaceURI, name) {
return this._externalNodesFactory['createElementNS'](namespaceURI, name);
}
Expand Down
Loading