Skip to content

Commit

Permalink
adds addition of nodes at any index (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmoDav authored and samuel-cloete committed Nov 14, 2018
1 parent ff2f65d commit e540e3e
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 72 deletions.
27 changes: 17 additions & 10 deletions examples/manual generation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,23 @@ class TestDocument {

getBody() {
const body = new Elements.Body();
const { root, last } = Engine.Generator.fromSelector('chapter>part[num:1,heading:Some Part]>section>subsection>content>p');
const noteRef = this.createNote();
const sampleBody = new Elements.TextElement('Always have notes.');
last.appendChild(sampleBody);
last.appendChild(noteRef);

body.appendChild(root);

console.log(root, last);

let generated = Engine.Generator.fromSelector('chapter>part[num:2,heading:Some Part]>section>subsection>content>p');
let noteRef = this.createNote();
let sampleBody = new Elements.TextElement('Always have notes.');
generated.last.appendChild(sampleBody);
generated.last.appendChild(noteRef);

/** Append the first child */
body.appendChild(generated.root);

generated = Engine.Generator.fromSelector('chapter>part[num:1,heading:First Part]>section>subsection>content>p');
noteRef = this.createNote();
sampleBody = new Elements.TextElement('Never forget a part.');
generated.last.appendChild(sampleBody);
generated.last.appendChild(noteRef);

/** Append the new node as the first child */
body.insertChildAtPosition(generated.root, 0);

return body;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nkyimu",
"version": "1.2.2",
"version": "1.3.1",
"description": "A library written in TypeScript that allows creating self validating AkomaNtoso documents",
"author": "Libryo Ltd",
"main": "build/src/index.js",
Expand Down
171 changes: 110 additions & 61 deletions src/Abstracts/AbstractNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,23 +249,59 @@ export abstract class AbstractNode implements HasChildrenMap {
* @returns AbstractNode
*/
appendChild(node: AbstractNode): AbstractNode {
return this.insertChild(node);
}

/**
* Insert a child node at a given index.
*
* @param insertionIndex number|null
* @param node AbstractNode
*
* @returns AbstractNode
*/
insertChildAtPosition(node: AbstractNode, insertionIndex: number): AbstractNode {
return this.insertChild(node, insertionIndex);
}

/**
* Insert a child node at a given index and update the node tree.
*
* @param node AbstractNode
* @param insertionIndex number|null
*
* @returns AbstractNode
*/
private insertChild(node: AbstractNode, insertionIndex: number|null = null): AbstractNode {
if (this.node instanceof Text || this.node instanceof Comment) {
throw new Error('This node does not accepts any children.');
}

const lastNodeIndex = this.node.childNodes.length - 1;

if (insertionIndex !== null && (lastNodeIndex < insertionIndex)) {
throw new Error('The selected position is not valid.');
}

if (this._allowedChildren.length < 1) {
this.setupValidationParams();
}

this.validateChild(node);

this.childrenOrder.push(node.getNodeName());
this.validateChild(node, insertionIndex);

node._parent = this;
if (insertionIndex === null) {
this.childrenOrder.push(node.getNodeName());
node._parent = this;
this._children.push(node);
this.node.appendChild(node.getNode());

this._children.push(node);
return this;
}

this.node.appendChild(node.getNode());
this.childrenOrder = [...this.getUpdatedChildOrder(node, insertionIndex)];
node._parent = this;
this._children.splice(insertionIndex, 0, node);
this.node.insertBefore(node.getNode(), this.node.childNodes[insertionIndex]);

return this;
}
Expand Down Expand Up @@ -342,8 +378,9 @@ export abstract class AbstractNode implements HasChildrenMap {
* Validate the child being added.
*
* @param node AbstractNode
* @param insertionIndex number|null
*/
validateChild(node: AbstractNode): void {
validateChild(node: AbstractNode, insertionIndex: number|null = null): void {
/** Check if the node is allowed as a child. We also validate for any that matches AnyOtherType complexType */
if (this._allowedChildren.indexOf('any') === -1 && this._allowedChildren.indexOf(node.getNodeName()) === -1) {
throw new Error(`Node ${node.getNodeName()} is not allowed as a child.`);
Expand All @@ -356,7 +393,7 @@ export abstract class AbstractNode implements HasChildrenMap {
throw new Error(`Node ${node.getNodeName()} is not allowed as a child.`);
}

this.validateNode(node, rule);
this.validateNode(node, rule, insertionIndex);

/** let's finally check if the node has valid children and attributes */
node.validate();
Expand Down Expand Up @@ -420,74 +457,86 @@ export abstract class AbstractNode implements HasChildrenMap {
*
* @param node AbstractNode
* @param rule Rule
* @param insertionIndex number|null
*
* @throws Error
*/
private validateNode(node: AbstractNode, rule: Rule) {
private validateNode(node: AbstractNode, rule: Rule, insertionIndex: number|null = null) {
this.validateNodeCounts(node, rule);
this.validateChildrenSequence(node, insertionIndex);
}

if (this.SEQUENCE && this.SEQUENCE.length > 0) {
let expected;

/** Get the last child node */
/** Remove the modifiers to the node names */
const cleanSequence = this.SEQUENCE.map(e => e.split(':')[0]);

/** The last child node if any */
const lastNode = this.childrenOrder.length > 0 ? this.childrenOrder[this.childrenOrder.length - 1] : null;

/** The index of the last child node or -1 if none exists */
const lastNodeSequenceIndex = lastNode ? cleanSequence.indexOf(lastNode) : -1;
/**
* Validate the child sequence.
*
* @param node AbstractNode
* @param insertionIndex number|null
*/
private validateChildrenSequence(node: AbstractNode, insertionIndex: number|null = null) {
if (!this.SEQUENCE || this.SEQUENCE.length < 1) return;

const cleanSequence = this.SEQUENCE.map(e => e.split(':')[0]);
const updatedOrder = this.getUpdatedChildOrder(node, insertionIndex);

const absent: string[] = [];

updatedOrder.forEach((item, index) => {
const orderBeforeNode = updatedOrder.slice(0, index);
const orderAfterNode = updatedOrder.slice(index + 1);
const sequenceBefore = cleanSequence.slice(0, cleanSequence.indexOf(item));

/** Get the sequence before the current order node. */
sequenceBefore.forEach((sequenceNode, sequenceIndex) => {
/**
* check if the sequence node is required,
* if its present after the current node in the order
* and if its present before the order node.
*/
const isRequired = this.requiredIndices.indexOf(sequenceIndex) !== -1;
const isPresentAfter = orderAfterNode.indexOf(sequenceNode) !== -1;
const isPresentBefore = orderBeforeNode.indexOf(sequenceNode) !== -1;

if (isPresentAfter) {
throw new Error(`The child node ${sequenceNode} should appear before the child node ${item}`);
}

/** The index of the current node */
const nodeIndex = cleanSequence.indexOf(node.getNodeName());
if (!isPresentBefore) {
absent.push(sequenceNode);
}

/**
* if the node we are attempting to add appears before the last child node in the sequence,
* then we cannot add it and can only add anything that comes after the last child.
*/
if (lastNodeSequenceIndex > nodeIndex) {
expected = cleanSequence.slice(lastNodeSequenceIndex + 1);
expected = expected.length > 1 ? 'one of ' + expected.join(', ') : expected.join(', ');
if (!isPresentBefore && isRequired) {
const expected = absent.length > 1 ? 'one of ' + absent.join(', ') : absent.join(', ');

throw new Error(`The child node ${node.getNodeName()} is expected before the current last child(${lastNode}). Expected is ${expected}`);
}
throw new Error(`The child node ${node.getNodeName()} is unexpected. Expected is ${expected}`);
}
});
});
}

cleanSequence
.some((seqNode, index) => {
/** if the current sequence node is required */
const isRequired = this.requiredIndices.indexOf(index) !== -1;
private generateMovementMap() {

/** if the current sequence node is already a child */
const isPresent = this.childrenOrder.indexOf(seqNode) !== -1;
}

/** if the current sequence node is the node we are attempting to add */
const isNode = node.getNodeName() === seqNode;
/**
* Insert the current node to the child order and return the updated order.
*
* @param node AbstractNode
* @param insertionIndex number|null
*
* @return string[]
*/
private getUpdatedChildOrder(node: AbstractNode, insertionIndex: number|null = null): string[] {
const updated = [...this.childrenOrder];

/**
* if the node appears before the last child but a required node is missing,
* then we can only add the required node or anything before the node we
* are attempting to add.
*/
if (isRequired && !isPresent && !isNode) {
expected = cleanSequence.slice(lastNodeSequenceIndex + 1, index + 1);
expected = expected.length > 1 ? 'one of ' + expected.join(', ') : expected.join(', ');
if (insertionIndex !== null) {
updated.splice(insertionIndex, 0, node.getNodeName());

throw new Error(`The child node ${node.getNodeName()} is unexpected. Expected is ${expected}`);
}
return updated;
}

/**
* if nothing is required before the node and the node we are attempting to add
* is the current node in the sequence, then we can add it.
*/
if (isNode) {
return true;
}
updated.push(node.getNodeName());

/** Carry on to the next sequence item */
return false;
});
}
return updated;
}


Expand Down

0 comments on commit e540e3e

Please sign in to comment.