Tools for editing tree structures using a relational model.
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.
]
}
Assuming var tree = require('tree-surgeon');
-
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.
Assuming
var tree = require('tree-surgeon');
var relational = tree.decompose(my_object);
-
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.
-
-
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.
-
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
-
-
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
-
-
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.
-
-
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
- 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?