Source: diff/patch/Patcher.js

import {Dsl} from '../../config/Dsl.js';
import {Node} from '../../tree/Node.js';

/**
 * A wrapper class for the patching capabilities of CpeeDiff.
 */
export class Patcher {
  /**
   * @type {Node}
   * @private
   */
  #tree;

  /**
   * Apply an edit script to a tree.
   * @param {Node} tree The root of the tree. It will not be modified.
   * @param {EditScript} editScript
   * @return {Node} The root of the editOpd tree.
   */
  patch(tree, editScript) {
    // Copy the old tree
    this.#tree = Node.fromNode(tree);

    for (const editOp of editScript.editOperations) {
      switch (editOp.type) {
        case Dsl.CHANGE_MODEL.INSERTION.label: {
          this.#handleInsertion(editOp);
          break;
        }
        case Dsl.CHANGE_MODEL.MOVE.label: {
          this.#handleMove(editOp);
          break;
        }
        case Dsl.CHANGE_MODEL.UPDATE.label: {
          this.#handleUpdate(editOp);
          break;
        }
        case Dsl.CHANGE_MODEL.DELETION.label: {
          this.#handleDeletion(editOp);
          break;
        }
      }
    }
    return this.#tree;
  }

  /**
   * @param {EditOperation} move
   */
  #handleMove(move) {
    const movedNode = this.#tree.findNode(move.oldPath);
    movedNode.removeFromParent();

    // Extract new child index
    const indexArr =
        move
            .newPath
            .split('/')
            .map((str) => parseInt(str));
    const index = indexArr.pop();

    // Find parent node
    const targetParent = this.#tree.findNode(indexArr.join('/'));

    targetParent.insertChild(index, movedNode);
  }

  /**
   * @param {EditOperation} insertion
   */
  #handleInsertion(insertion) {
    // Extract new child index
    const indexArr =
        insertion
            .newPath
            .split('/')
            .map((str)=> parseInt(str));
    const index = indexArr.pop();

    // Find parent node
    const parent = this.#tree.findNode(indexArr.join('/'));

    // Insert
    const newNode = Node.fromNode(insertion.newContent, true);
    parent.insertChild(index, newNode);
  }

  /**
   * @param {EditOperation} update
   */
  #handleUpdate(update) {
    const node = this.#tree.findNode(update.oldPath);

    node.attributes.clear();
    for (const [key, val] of update.newContent.attributes) {
      node.attributes.set(key, val);
    }
    node.text = update.newContent.text;
  }

  /**
   * @param {EditOperation} deletion
   */
  #handleDeletion(deletion) {
    this.#tree.findNode(deletion.oldPath).removeFromParent();
  }
}