docxml
Version:
TypeScript (component) library for building and parsing a DOCX file
114 lines (113 loc) • 4.56 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Registry = void 0;
const fontoxpath_1 = __importDefault(require("fontoxpath"));
class Registry {
/**
* A class that you instantiate to contain "metadata" associated with certain XML nodes. The metadata could be anything,
* but in context of being an "xml renderer" you'll probably want to use it for templates or React components.
*
* See also {@link GenericRenderer} and {@link ReactRenderer} which extend the `Registry` class and add a `.render()`
* method to it.
*
* Render functions (metadata) are associated with XML nodes via an XPath test. For any given node, the renderer will
* use the metadata associated the most specific test that matches the node.
*/
constructor(...sets) {
/**
* All test/value sets known to this registery. Is kept in descending order of test specificity because {@link
* Registry.optimize} is always called when modifying this set through public methods.
*/
Object.defineProperty(this, "sets", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
this.merge(...sets);
}
/**
* Reorders the sets based on XPath test specificity so that an `Array#find` finds the closest matching test
* first.
*
* For example `<b />` could match tests `self::b` as well as `self::b[not(child::*)]`, but the latter is more
* specific so it wins.
*
* This method is private because it is already called at all relevant times. Calling it again will normally not
* yield any different results.
*/
optimize() {
this.sets = this.sets
// Sort alphabetically by test to get a consistent sorting even if selectors are equally specific
.sort((setLeft, setRight) => setLeft.test.localeCompare(setRight.test))
// Sort by descreasing specificity as determined by fontoxpath
.sort((setLeft, setRight) => fontoxpath_1.default.compareSpecificity(setRight.test, setLeft.test));
}
get length() {
return this.sets.length;
}
/**
* Merges other registry instances into this one, and optimizes ({@link Registry.optimize}) when done.
*/
merge(...sets) {
this.sets = sets.reduce((sets, registry) => sets
// Remove any duplicates from the pre-existing set
.filter((set) => !registry.sets.some((s) => s.test === set.test))
.concat(registry.sets), this.sets);
this.optimize();
return this;
}
/**
* Add a test/value set to the registry, and optimizes ({@link Registry.optimize}).
*/
add(test, value) {
if (value === undefined) {
throw new TypeError('Required to pass a value when adding to registry.');
}
if (this.sets.some((set) => set.test === test)) {
throw new TypeError('Refusing to add a selector in duplicate, use #overwrite() instead.');
}
this.sets.push({
test,
value,
});
this.optimize();
return this;
}
overwrite(test, value) {
if (value === undefined) {
throw new TypeError('Required to pass a value when overwriting to registry, use #remove() instead.');
}
const index = this.sets.findIndex((set) => set.test === test);
if (index < 0) {
throw new TypeError('Refusing to overwrite a selector because it was never set before.');
}
this.sets.splice(index, 1, {
test,
value,
});
return this;
}
/**
* Remove a test/value set from the registry. This is the opposite of the {@link Registry.add} method.
*/
remove(test) {
const index = this.sets.findIndex((set) => set.test === test);
if (index >= 0) {
this.sets.splice(index, 1).length === 1;
}
return this;
}
/**
* Retrieve the metadata that was associated with this node before. If there are several rules that match, `.find`
* gives you only the value of the best match.
*/
find(node) {
const set = this.sets.find((set) => fontoxpath_1.default.evaluateXPathToBoolean(set.test, node));
return set?.value;
}
}
exports.Registry = Registry;