UNPKG

wed

Version:

Wed is a schema-aware editor for XML documents.

434 lines (433 loc) 17.6 kB
/** * Facility for updating a DOM tree and issue synchronous events on changes. * @author Louis-Dominique Dubeau * @license MPL 2.0 * @copyright Mangalam Research Center for Buddhist Languages */ import { Observable, Subject } from "rxjs"; import { DLoc, DLocRoot } from "./dloc"; export interface TreeUpdaterEvent { name: string; } export interface ChangedEvent extends TreeUpdaterEvent { name: "Changed"; } export interface BeforeInsertNodeAtEvent extends TreeUpdaterEvent { name: "BeforeInsertNodeAt"; parent: Node; index: number; node: Node; } export interface InsertNodeAtEvent extends TreeUpdaterEvent { name: "InsertNodeAt"; parent: Node; index: number; node: Node; } export interface SetTextNodeValueEvent extends TreeUpdaterEvent { name: "SetTextNodeValue"; node: Text; value: string; oldValue: string; } export interface BeforeDeleteNodeEvent extends TreeUpdaterEvent { name: "BeforeDeleteNode"; node: Node; } export interface DeleteNodeEvent extends TreeUpdaterEvent { name: "DeleteNode"; node: Node; formerParent: Node; } export interface SetAttributeNSEvent extends TreeUpdaterEvent { name: "SetAttributeNS"; node: Element; ns: string; attribute: string; oldValue: string | null; newValue: string | null; } export declare type TreeUpdaterEvents = ChangedEvent | BeforeInsertNodeAtEvent | InsertNodeAtEvent | SetTextNodeValueEvent | BeforeDeleteNodeEvent | DeleteNodeEvent | SetAttributeNSEvent; export declare type InsertableAtom = string | Element | Text; export declare type Insertable = InsertableAtom | InsertableAtom[] | NodeList; export declare type SplitResult = [Node | null, Node | null]; /** * Records the results of inserting text into the tree. */ export interface TextInsertionResult { /** The node that contains the added text. */ node: Text | undefined; /** Whether [[node]] is a new node. If ``false``, it was modified. */ isNew: boolean; /** The caret position after the insertion. */ caret: DLoc; } export declare type InsertionBoundaries = [DLoc, DLoc]; /** * A TreeUpdater is meant to serve as the sole point of modification for a DOM * tree. As methods are invoked on the TreeUpdater to modify the tree, events * are issued synchronously, which allows a listener to know what is happening * on the tree. * * Methods are divided into primitive and complex methods. Primitive methods * perform one and only one modification and issue an event of the same name as * their own name. Complex methods use primitive methods to perform a series of * modifications on the tree. Or they delegate the actual modification work to * the primitive methods. They may emit one or more events of a name different * from their own name. Events are emitted **after** their corresponding * operation is performed on the tree. * * For primitive methods, the list of events which they are documented to be * firing is exhaustive. For complex methods, the list is not exhaustive. * * Many events have a name identical to a corresponding method. Such events are * accompanied by event objects which have the same properties as the parameters * of the corresponding method, with the same meaning. Therefore, their * properties are not further documented. * * There is a generic [[ChangedEvent]] that is emitted with every other * event. This event does not carry information about what changed exactly. * * The [[TreeUpdater.deleteNode]] operation is the one major exception to the * basic rules given above: * * - [[BeforeDeleteNodeEvent]] is emitted **before** the deletion is * performed. This allows performing operations based on the node's location * before it is removed. For instance, calling the DOM method ``matches`` on a * node that has been removed from its DOM tree is generally going to fail to * perform the intended check. * * - [[DeleteNodeEvent]] has the additional ``formerParent`` property. * * ``DocumentFragment`` have special handling. Although the methods below that * insert new content into the tree accept ``DocumentFragment`` nodes, the tree * updater inserts the *contents* of the fragment rather than the fragment * itself. Or to put it differently, inserting a fragment is equivalent to * iterating through the fragment's children and inserting each one in order. A * fragment is essentially equivalent to an array. */ export declare class TreeUpdater { protected readonly tree: Element | Document; protected readonly dlocRoot: DLocRoot; protected readonly _events: Subject<TreeUpdaterEvents>; readonly events: Observable<TreeUpdaterEvents>; /** * @param tree The node which contains the tree to update. */ constructor(tree: Element | Document); protected _emit(event: TreeUpdaterEvents): void; /** * A complex method. This is a convenience method that will call primitive * methods to insert the specified item at the specified location. Note that * this method returns nothing even if the primitives it uses return some * information. * * @param loc The location where to insert. * * @param node The node where to insert. If ``loc`` is not used. * * @param offset The offset where to insert. If ``loc`` is not used. * * @param what The data to insert. * * @throws {Error} If ``loc`` is not in an element or text node or if * ``what`` is not an element or text node. */ insertAt(loc: DLoc, what: Insertable): void; insertAt(node: Node, offset: number, what: Insertable): void; /** * A complex method. Splits a DOM tree into two halves. * * @param top The node at which the splitting operation should end. This node * will be split but the function won't split anything above this node. * * @param loc The location where to start the split. * * @param node The node at which to start the split, if ``loc`` is not used. * * @param index The index at which to start in the node, if ``loc`` is not * used. * * @returns An array containing in order the first and second half of the * split. * * @throws {Error} If the split location is not inside the top node or if the * call would merely split a text node in two. */ splitAt(top: Node, loc: DLoc): SplitResult; splitAt(top: Node, node: Node, index: number): SplitResult; /** * Splits a DOM tree into two halves. * * @param top The node at which the splitting operation should end. This node * will be split but the function won't split anything above this node. * * @param node The node at which to start. * * @param index The index at which to start in the node. * * @returns An array containing in order the first and second half of the * split. */ protected _splitAt(top: Node, node: Node, index: number): SplitResult; /** * A complex method. Inserts the specified item before another one. Note that * the order of operands is the same as for the ``insertBefore`` DOM method. * * @param parent The node that contains the two other parameters. * * @param toInsert The node to insert. * * @param beforeThis The node in front of which to insert. A value of * ``null`` results in appending to the parent node. * * @throws {Error} If ``beforeThis`` is not a child of ``parent``. */ insertBefore(parent: Element, toInsert: Element | Text, beforeThis: Node | null): void; /** * A complex method. Inserts text into a node. This function will use already * existing text nodes whenever possible rather than create a new text node. * * @param loc The location at which to insert the text. * * @param node The node at which to insert the text, if ``loc`` is not used. * * @param index The location in the node at which to insert the text, if * ``loc`` is not used. * * @param text The text to insert. * * @param caretAtEnd Whether the returned caret should be at the end of the * inserted text or the start. If not specified, the default is ``true``. * * @returns The result of inserting text. * * @throws {Error} If ``node`` is not an element or text Node type. */ insertText(loc: DLoc, text: string, caretAtEnd?: boolean): TextInsertionResult; insertText(node: Node, index: number, text: string, caretAtEnd?: boolean): TextInsertionResult; /** * A complex method. Deletes text from a text node. If the text node becomes * empty, it is deleted. * * @param loc Where to delete. * * @param node The text node from which to delete text. * * @param index The index at which to delete text. * * @param length The length of text to delete. */ deleteText(loc: DLoc, length: number): void; deleteText(node: Text, index: number, length: number): void; /** * A complex method. Inserts an element into text, effectively splitting the * text node in two. This function takes care to modify the DOM tree only * once. * * @param loc The location at which to cut. * * @param parent The text node that will be cut in two by the new element. * * @param index The offset into the text node where the new element is to be * inserted. * * @param node The node to insert. * * @returns The first element of the array is a ``DLoc`` at the boundary * between what comes before the material inserted and the material * inserted. The second element of the array is a ``DLoc`` at the boundary * between the material inserted and what comes after. If I insert "foo" at * position 2 in "abcd", then the final result would be "abfoocd" and the * first location would be the boundary between "ab" and "foo" and the second * location the boundary between "foo" and "cd". * * @throws {Error} If the node to insert is undefined or null. */ insertIntoText(loc: DLoc, node: Node): InsertionBoundaries; insertIntoText(parent: Text, index: number, node: Node): InsertionBoundaries; /** * A primitive method. Inserts a node at the specified position. * * @param loc The location at which to insert. * @param node The node to insert. * @param parent The node which will become the parent of the * inserted node. * @param index The position at which to insert the node * into the parent. * * @emits InsertNodeAtEvent * @emits ChangedEvent */ insertNodeAt(loc: DLoc, node: Node): void; insertNodeAt(parent: Node, index: number, node: Node): void; /** * A complex method. Sets a text node to a specified value. * * @param node The node to modify. * * @param value The new value of the node. * * @throws {Error} If called on a non-text Node type. */ setTextNode(node: Text, value: string): void; /** * A primitive method. Sets a text node to a specified value. This method must * not be called directly by code that performs changes of the DOM tree at a * high level, because it does not prevent a text node from becoming * empty. Call [[TreeUpdater.setTextNode]] instead. This method is meant to be * used by other complex methods of TreeUpdater and by some low-level * facilities of wed. * * @param node The node to modify. Must be a text node. * * @param value The new value of the node. * * @emits SetTextNodeValueEvent * @emits ChangedEvent * @throws {Error} If called on a non-text Node type. */ setTextNodeValue(node: Text, value: string): void; /** * A complex method. Removes a node from the DOM tree. If two text nodes * become adjacent, they are merged. * * @param node The node to remove. This method will fail with an exception if * this parameter is ``undefined`` or ``null``. Use [[removeNodeNF]] if you * want a method that will silently do nothing if ``undefined`` or ``null`` * are expected values. * * @returns A location between the two parts that were merged, or between the * two nodes that were not merged (because they were not both text). */ removeNode(node: Node | undefined | null): DLoc; /** * A complex method. Removes a node from the DOM tree. If two text nodes * become adjacent, they are merged. * * @param node The node to remove. This method will do nothing if the node to * remove is ``undefined`` or ``null``. * * @returns A location between the two parts that were merged, or between the * two nodes that were not merged (because they were not both text). This will * be ``undefined`` if there was no node to remove. */ removeNodeNF(node: Node | undefined | null): DLoc | undefined; /** * A complex method. Removes a list of nodes from the DOM tree. If two text * nodes become adjacent, they are merged. * * @param nodes These nodes must be immediately contiguous siblings in * document order. * * @returns The location between the two parts that were merged, or between * the two nodes that were not merged (because they were not both * text). Undefined if the list of nodes is empty. * * @throws {Error} If nodes are not contiguous siblings. */ removeNodes(nodes: Node[]): DLoc | undefined; /** * A complex method. Removes the contents between the start and end carets * from the DOM tree. If two text nodes become adjacent, they are merged. * * @param start The start position. * * @param end The end position. * * @returns A pair of items. The first item is a ``DLoc`` object indicating * the position where the cut happened. The second item is a list of nodes, * the cut contents. * * @throws {Error} If Nodes in the range are not in the same element. */ cut(start: DLoc, end: DLoc): [DLoc, Node[]]; /** * A complex method. If the node is a text node and followed by a text node, * this method will combine them. * * @param node The node to check. This method will fail with an exception if * this parameter is ``undefined`` or ``null``. Use [[mergeTextNodesNF]] if * you want a method that will silently do nothing if ``undefined`` or * ``null`` are expected values. * * @returns A position between the two parts that were merged, or between the * two nodes that were not merged (because they were not both text). */ mergeTextNodes(node: Node): DLoc; /** * A complex method. If the node is a text node and followed by a text node, * this method will combine them. * * @param node The node to check. This method will do nothing if the node to * remove is ``undefined`` or ``null``. * * @returns A position between the two parts that were merged, or between the * two nodes that were not merged (because they were not both text). This will * be ``undefined`` if there was no node to remove. */ mergeTextNodesNF(node: Node | null | undefined): DLoc | undefined; /** * A primitive method. Removes a node from the DOM tree. This method must not * be called directly by code that performs changes of the DOM tree at a high * level, because it does not prevent two text nodes from being contiguous * after deletion of the node. Call [[removeNode]] instead. This method is * meant to be used by other complex methods of TreeUpdater and by some * low-level facilities of wed. * * @param node The node to remove * * @emits DeleteNodeEvent * @emits BeforeDeleteNodeEvent * @emits ChangedEvent */ deleteNode(node: Node): void; /** * A complex method. Sets an attribute to a value. Setting to the value * ``null`` or ``undefined`` deletes the attribute. This method sets * attributes outside of any namespace. * * @param node The node to modify. * * @param attribute The name of the attribute to modify. * * @param value The value to give to the attribute. * * @emits SetAttributeNSEvent * @emits ChangedEvent */ setAttribute(node: Element, attribute: string, value: string | null | undefined): void; /** * A primitive method. Sets an attribute to a value. Setting to the value * ``null`` or ``undefined`` deletes the attribute. * * @param node The node to modify. * * @param ns The URI of the namespace of the attribute. * * @param attribute The name of the attribute to modify. * * @param value The value to give to the attribute. * * @emits SetAttributeNSEvent * @emits ChangedEvent */ setAttributeNS(node: Element, ns: string, attribute: string, value: string | null | undefined): void; /** * Converts a node to a path. * * @param node The node for which to return a path. * * @returns The path of the node relative to the root of the tree we are * updating. */ nodeToPath(node: Node): string; /** * Converts a path to a node. * * @param path The path to convert. * * @returns The node corresponding to the path passed. */ pathToNode(path: string): Node | null; }