wed
Version:
Wed is a schema-aware editor for XML documents.
252 lines • 10.3 kB
JavaScript
/**
* Model for a GUI root.
* @author Louis-Dominique Dubeau
* @license MPL 2.0
* @copyright Mangalam Research Center for Buddhist Languages
*/
define(["require", "exports", "./dloc", "./domtypeguards", "./domutil", "./util"], function (require, exports, dloc_1, domtypeguards_1, domutil_1, util_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Raised if an attribute could not be found when converting a path to a node.
*/
class AttributeNotFound extends Error {
constructor(message) {
super(message);
util_1.fixPrototype(this, AttributeNotFound);
}
}
exports.AttributeNotFound = AttributeNotFound;
/**
* Count the number of relevant nodes in the ``_phantom_wrap``.
*
* @param top The top _phantom_wrap to consider.
*/
function countInPhantomWrap(top) {
if (!domtypeguards_1.isElement(top) || !top.classList.contains("_phantom_wrap")) {
throw new Error("the node should be a _phantom_wrap element");
}
let count = 0;
let child = top.firstChild;
while (child !== null) {
if (domtypeguards_1.isElement(child)) {
if (child.classList.contains("_phantom_wrap")) {
count += countInPhantomWrap(child);
}
else if (child.classList.contains("_real")) {
count += 1;
}
else if (child.classList.contains("_phantom")) {
// Phantoms don't count.
}
else {
throw new Error("unexpected element in _phantom_wrap");
}
}
else if (child.nodeType === Node.TEXT_NODE) {
// Text nodes also do not count.
}
else {
throw new Error("unexpected node in _phantom_wrap");
}
child = child.nextSibling;
}
return count;
}
function findInPhantomWrap(top, index) {
if (!domtypeguards_1.isElement(top) || !top.classList.contains("_phantom_wrap")) {
throw new Error("the node should be a _phantom_wrap element");
}
const originalIndex = index;
let found = null;
let child = top.firstChild;
while (found === null && child !== null) {
if (domtypeguards_1.isElement(child)) {
if (child.classList.contains("_phantom_wrap")) {
const result = findInPhantomWrap(child, index);
if (result.found !== null) {
found = result.found;
}
index -= result.count;
}
else if (child.classList.contains("_real")) {
index -= 1;
if (index < 0) {
found = child;
}
}
else if (child.classList.contains("_phantom")) {
// Phantoms don't count.
}
else {
throw new Error("unexpected element in _phantom_wrap");
}
}
else if (child.nodeType === Node.TEXT_NODE) {
// Text nodes do not count.
}
else {
throw new Error("unexpected node in _phantom_wrap");
}
child = child.nextSibling;
}
return {
found: found,
count: originalIndex - index,
};
}
/**
* This is a [[DLocRoot]] class customized for use to mark the root of the GUI
* tree.
*/
class GUIRoot extends dloc_1.DLocRoot {
/**
* Converts a node to a path suitable to be used by the
* [["wed/dloc".DLocRoot.pathToNode]] method so long as the root used is the
* one for the data tree corresponding to the GUI tree to which this object
* belongs.
*
* @param node The node for which to construct a path.
*
* @returns The path.
*/
nodeToPath(node) {
const root = this.node;
if (domutil_1.closestByClass(node, "_placeholder", root) !== null) {
throw new Error("cannot provide path to node because it is a placeholder node");
}
if (root === node) {
return "";
}
if (!root.contains(node)) {
throw new Error("node is not a descendant of root");
}
const ret = [];
while (node !== root) {
let parent;
if (domtypeguards_1.isElement(node) &&
!node.matches("._real, ._phantom_wrap, ._attribute_value")) {
throw new Error("only nodes of class ._real, ._phantom_wrap, and " +
"._attribute_value are supported");
}
const attrVal = domutil_1.closestByClass(node, "_attribute_value", root);
if (attrVal !== null) {
const child = domutil_1.siblingByClass(attrVal, "_attribute_name");
if (child === null) {
throw new Error("no attribute name found");
}
ret.unshift(`@${child.textContent}`);
parent = domutil_1.closestByClass(attrVal, "_real", root);
if (parent === null) {
throw new Error("attribute is detached from real element");
}
}
else {
parent = node.parentNode;
if (parent === null) {
throw new Error("detached node");
}
let offset = 0;
const location = domutil_1.indexOf(parent.childNodes, node);
for (let i = 0; i < location; ++i) {
const child = parent.childNodes[i];
if (domtypeguards_1.isText(child) ||
(domtypeguards_1.isElement(child) && child.classList.contains("_real"))) {
offset++;
}
else if (domtypeguards_1.isElement(child) &&
child.classList.contains("_phantom_wrap")) {
offset += countInPhantomWrap(child);
}
}
// Parent could be a document if it is not an element.
if (!domtypeguards_1.isElement(parent) || !parent.classList.contains("_phantom_wrap")) {
ret.unshift(String(offset));
}
}
node = parent;
}
return ret.join("/");
}
/**
* This function recovers a DOM node on the basis of a path previously created
* by [["wed/dloc".DLocRoot.nodeToPath]] provided that the root from which the
* path was obtained is on the data tree which corresponds to the GUI tree
* that this root was created for.
*
* @param path The path to interpret.
*
* @returns The node corresponding to the path, or ``null`` if no such node
* exists.
*
* @throws {Error} If given a malformed ``path``.
*/
pathToNode(path) {
const root = this.node;
if (path === "") {
return root;
}
const parts = path.split(/\//);
let parent = root;
let attribute;
// Set aside the last part if it is an attribute.
if (parts[parts.length - 1][0] === "@") {
attribute = parts.pop();
}
let found = null;
for (const part of parts) {
const match = /^(\d+)$/.exec(part);
if (match !== null) {
found = null;
let index = parseInt(match[1]);
for (let i = 0; found === null && (i < parent.childNodes.length); i++) {
const node = parent.childNodes[i];
if ((domtypeguards_1.isText(node) ||
(domtypeguards_1.isElement(node) && node.classList.contains("_real"))) &&
--index < 0) {
found = node;
}
else if (domtypeguards_1.isElement(node) &&
node.classList.contains("_phantom_wrap")) {
const result = findInPhantomWrap(node, index);
if (result.found !== null) {
found = result.found;
}
index -= result.count;
}
}
if (found === null) {
return null;
}
parent = found;
}
else {
throw new Error("malformed path expression");
}
}
if (attribute !== undefined) {
const name = attribute.slice(1);
if (!domtypeguards_1.isElement(parent)) {
throw new Error("looking for attribute on something which is not an element");
}
const attrs = parent.getElementsByClassName("_attribute_name");
found = null;
for (let aix = 0; aix < attrs.length; ++aix) {
const attr = attrs[aix];
if (attr.textContent === name) {
found = attr;
break;
}
}
if (found === null) {
throw new AttributeNotFound(`could not find attribute with name: ${name}`);
}
parent = domutil_1.siblingByClass(found, "_attribute_value");
}
return parent;
}
}
exports.GUIRoot = GUIRoot;
});
// LocalWords: MPL
//# sourceMappingURL=guiroot.js.map