tree-surgeon v0.2.0
node-tree-surgeon
Tools for editing tree structures using a relational model.
General purpose:
Trees are represented internally with two sets: (relational structure)
- Relations:
[{"Parent":id, "Child":id, "Kind":any, ...}, ...]
- Nodes:
[{... your data ... }, ...]
Functions given to split a POJO into this structure, and merge the structure into a POJO.
Names of object tree parts, as used below:
{ // this object is the parent, it has one property "I" = "am parent"
"I" : "am parent", // properties (whose values are not objects) remain
// on the containing object.
"Kind" : // properties (whose values are objects) become relationships.
// The key becomes the kind of the relation.
{
// this object is the child
"Property" : "Value"
}
}
Properties with array values are treated one of two ways:
{
"JustAProperty" : ["hello", 1,2,3], // first element is NOT an object.
// Entire array is a single value,
// one of the parent's properties
"OneToMany" : [
{"child":1}, // First element is an object. All elements are
// considered children of the parent. Kind is 'OneToMany'
{"child":2} // There is no way to express many-to-one, and
// putting this in the relational structure is not supported.
]
}
Operations on POJO structure:
Assuming var tree = require('tree-surgeon');
Input
- decompose -- turn a normal js object tree into the relational structure
`tree.decompose(obj, excludedKinds, relationDecorator, useEmptyRelations);` - `obj` The object to be decomposed for relational operations. This should be a simple object, as returned by `JSON.parse` - `excludedKinds` An array of property names. These properties and their sub-trees will not be decomposed - `relationDecorator` A function to read node as they are decomposed and inject data into the relations table. This data can be used in subsequent operations. - `useEmptyRelations` Bool, default false. If `true`, empty arrays will be treated as object nodes with no children. Returns a relational structure.
Operations on the relational structure:
Assuming
var tree = require('tree-surgeon');
var relational = tree.decompose(my_object);
Output
- compose -- convert a relational structure back into a plain object. Any manipulations of the relational structure will take effect.
`relational.compose()` or `tree.compose(relational)` This is the fastest output function.
- render -- pass each node through a function, and each kind name through a function and compose tree from the results. Manipulations of the relational structure will take effect, and both property names and object contents can be manipulated during output.
`relational.render(renderNodeFunc, renderKindFunc)` - `renderNodeFunc` function that takes (node, path, id) and returns the rendered object contents - `renderKindFunc` function that takes (kind, path) and returns the output property name
- harvest -- return an object of composed sub-trees by kind, keyed by a parent node value
`relational.harvest(kind, idSelector)` - `kind` the target property names to extract - `idSelector` a function that takes the object nodes and returns a unique new property name
- gather -- return an array of sub-trees
- gatherByKind -- subtrees selected by property name
`relational.gatherByKind(kind)` - `kind` the target property names to extract
- gatherByNode -- subtrees selected by a function
`relational.gatherByNode(selector)` - `selector` function that takes a node and returns `bool`. The subtrees of node that result in `true` will be returned.
- gatherByKind -- subtrees selected by property name
Navigation
- parentIdOf -- get parent ID from child ID, or
null
if not found`relational.parentIdOf(childId)` - `childId` the ID of a node returns the parent ID, or `null` if this was the root node
- getChildrenOf -- get an array of node IDs for the given parent ID
`relational.getChildrenOf(parentId)` - `parentId` the ID of a node returns an array of all child nodes' IDs. Returns empty array if a leaf node was passed.
- getChildrenByKindOf - get an array of child node IDs for a given parent where the child is a specified type. Kind can be a string, or a
where
predicate on the relationship (an object with exact value matches)`relational.getChildrenByKindOf(parentId, kind)` - `parentId` the ID of a node - `kind` selector for the children to be included - string: pick children with a matching property name - object: pick children whose relation object matches properties on the selector object - function: given the relation object, pick where the function returns `true`
- getNode -- return the node data for a given ID
`relational.getNode(id)` - `id` the ID of a node
- getPathOf -- give the
Kind
path for a given node ID`relational.getPathOf(nodeId)` - `nodeId` the ID of a node
- forEachByKind -- given a
Kind
execute the supplied function for all nodes of that kind. Kind can be a string, or awhere
predicate`relational.forEachByKind(kind, actionFunc)` - `kind` selector for the children to be included - string: pick children with a matching property name - object: pick children whose relation object matches properties on the selector object - function: given the relation object, pick where the function returns `true` - `actionFunc` function of `(node, id)` to execute for each matching node. Any return is ignored. Any changes made to the node data is retained.
Manipulation
- Normalise -- removes any relationships or nodes that are not reachable from the root, but keeps node and relation indexes consistent.
`relational.normalise()`
- prune -- remove subtrees by relationship kind. Kind can be a string, or a
where
predicate on the relationship (an object with exact value matches)`relational.prune(kind)` - `kind` property name or relation match to remove.
- pruneAfter -- remove subtrees by relationship kind, but keep the immediate children. Kind can be a string, or a
where
predicate`relational.pruneAfter(kind)` - `kind` property name or relation match. The children of matches will be removed
- pruneAllBut -- remove subtrees that don't match a set of kinds. Supports only array of string kinds.
`relational.pruneAllBut(kind)` - `kind` property name or relation match. All non-matching relations will be removed
- pruneAfter -- remove subtrees by relationship kind, but keep the immediate children. Kind can be a string, or a
chop -- remove subtrees by data predicate
`relational.chop(filterFunc)` - `filterFunc` function of `(node, id)`. If this returns a truthy value, node will be removed, else node will be kept
chopAfter -- remove subtrees by data predicate, but keep the matched children
`relational.chopAfter(filterFunc)` - `filterFunc` function of `(node, id)`. If this returns a truthy value, all children of the matched node will be removed
chopByKind -- remove subtrees of a specified 'kind' by data predicate. Kind can be a string, or a
where
predicate`relational.chopByKind(kind, filterFunc)` - `kind` property name or relation match. Matched nodes will be included in the filter - `filterFunc` function of `(node, id)`. If this returns a truthy value, node will be removed, else node will be kept
- chopChildless -- remove nodes which have no children (ie. leaves) by data predicate
`relational.chopChildless(filterFunc)` - `filterFunc` function of `(node, id)`. All leaf nodes will be passed to this function. If this returns a truthy value, node will be removed.
- chopNodesByIds -- remove nodes and their subtrees by their IDs
`relational.chopNodesByIds(ids)` - `ids` an array of node ids. All these nodes and their subtrees will be removed.
- MergeUp -- remove a relationship and one node by merging data from child to parent. Subtree remains
- mergeUpByKind -- select merge targets by relationship kind. Kind can be a string or a
where
predicate`relational.mergeUpByKind(kind)` - `kind` target property or relation match. Matching nodes will be merged into their parents
- mergeUpByNode -- select merge targets by applying a predicate to nodes
`relational.mergeUpByNode(predFunc)` - `predFunc` function to select nodes. Matching nodes will be merged into their parents
- mergeUpByKind -- select merge targets by relationship kind. Kind can be a string or a
MergeDown -- remove a relationship and one node by merging data from parent to child. Subtree remains
mergeDownByKind -- select merge targets by relationship kind. Kind can be a string or a
where
predicate`relational.mergeDownByKind(kind)` - `kind` target property or relation match. Matching nodes' data will be copied into their parents, then the matching node removed.
mergeDownByNode -- select merge targets by applying a predicate to nodes
`relational.mergeUpByNode(predFunc)` - `predFunc` function to select nodes. Matching nodes' data will be copied into their parents, then the matching node removed.
Fuse -- remove a node by merging into it's parent and child (by supplied function). This is a generalisation of merge up/down.
- fuseByNode -- remove a node picked by a predicate on that node
`relational.fuseByNode(nodeFunc, pickForParentFunc, pickForChildFunc)` - `nodeFunc` function to pick nodes to fuse - `pickForParentFunc` function that is given node data, and returns the data to copy into the parent node - `pickForChildFunc` function that is given node data, and returns the data to copy into the child node.
- fuseByKind -- remove a node picked by kind. Kind can be a string or a
where
predicate`relational.fuseByKind(kind, pickForParentFunc, pickForChildFunc)` - `kind` target property or relation match. Matching nodes will be removed, but sub-trees will remain - `pickForParentFunc` function that is given node data, and returns the data to copy into the parent node - `pickForChildFunc` function that is given node data, and returns the data to copy into the child node.
- fuseByNode -- remove a node picked by a predicate on that node
flipRelationship -- given a parent kind, a child kind, and an equality function for children; swap parents⇔children, grouping children by equality. The new child kind can be a string or a
where
predicate, but the new parent kind can only be a string.`relational.flipRelationship(newChildKind, newParentKind, newParentHashFunc)` - `newChildKind` property name that is currently the parent node, and should be flipped to being a parent - `newParentKind` property name that is currently the child node, and should be flipped to being a child - `newParentHashFunc` function that takes a node and returns a comparable value (preferable a number)
reverseByRelation -- make children into parents and parents into children. This is a more complex and powerful version of
flipRelationship
. Have a look in the test cases for examples.- reduce -- reduce objects to a single value from inside them, by kind or node predicate (
{a:[{x:1},{x:2}]} -> {a:[1,2]}
) - editByKind -- given a
kind
name and an editor function, change all immediate children of that kind. Kind can be a string or awhere
predicate - removeEmptyNodes -- recursively remove nodes which contain only
null
orundefined
. This can remove entire subtrees that contain only empty children
Unimplemented
- graft -- insert new subtrees
- disconnect -- the opposite of Fuse, place a new node between a parent and child
- fork -- move some of the values of a node into a new or existing sibling
- move -- move some of the values of a node into an existing sibling, or do nothing
- editPath -- given a path of kinds and a func node→node, replace data at those paths
- fuseAway -- remove a node by connecting it's parents to it's children, losing the data in the selected nodes
- fuseAwayByNode
- fuseAwayByKind
Note:
- To run istanbul on Windows, use
istanbul cover C:\Users\[username]\AppData\Roaming\npm\node_modules\mocha\bin\_mocha -- -R spec
Todo:
- optimisations
- a good way to find subtrees based on paths, and perform operations based on results
- bring
.d.ts
file up-to-date with available features - syntax should allow chaining of functions
- extend with
kind
andpredicate
functions - some way of mutating kind when fusing/merging?
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago