docs: improve JSDoc of codec classes (#287)

In addition, apply minor refactoring
  - Codec.decodeCell: improve signature (default value)
  - ObjectCodec
    - use Codec type instead of value in import
    - simplify implementation of ObjectCodec.isNumericAttribute
development
Thomas Bouffard 2023-12-11 08:42:29 +01:00 committed by GitHub
parent 8d6f0e107c
commit 3041ca337e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 104 deletions

View File

@ -45,21 +45,19 @@ const createXmlDocument = () => {
* The following code is used to encode a graph model. * The following code is used to encode a graph model.
* *
* ```javascript * ```javascript
* var encoder = new Codec(); * const encoder = new Codec();
* var result = encoder.encode(graph.getDataModel()); * const result = encoder.encode(graph.getDataModel());
* var xml = mxUtils.getXml(result); * const xml = mxUtils.getXml(result);
* ``` * ```
* *
* #### Example * #### Example
* *
* Using the code below, an XML document is decoded into an existing model. The * Using the code below, an XML document is decoded into an existing model. The
* document may be obtained using one of the functions in mxUtils for loading * document may be obtained using {@link parseXml} for parsing an XML string.
* an XML file, eg. {@link mxUtils.get}, or using {@link mxUtils.parseXml} for parsing an
* XML string.
* *
* ```javascript * ```javascript
* var doc = mxUtils.parseXml(xmlString); * const doc = xmlUtils.parseXml(xmlString);
* var codec = new Codec(doc); * const codec = new Codec(doc);
* codec.decode(doc.documentElement, graph.getDataModel()); * codec.decode(doc.documentElement, graph.getDataModel());
* ``` * ```
* *
@ -70,18 +68,17 @@ const createXmlDocument = () => {
* be added anywhere in the cell hierarchy after parsing. * be added anywhere in the cell hierarchy after parsing.
* *
* ```javascript * ```javascript
* var xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>'; * const xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>';
* var doc = mxUtils.parseXml(xml); * const doc = mxUtils.parseXml(xml);
* var codec = new Codec(doc); * const codec = new Codec(doc);
* var elt = doc.documentElement.firstChild; * let elt = doc.documentElement.firstChild;
* var cells = []; * const cells = [];
* *
* while (elt != null) * while (elt != null)
* { * {
* cells.push(codec.decode(elt)); * cells.push(codec.decode(elt));
* elt = elt.nextSibling; * elt = elt.nextSibling;
* } * }
*
* graph.addCells(cells); * graph.addCells(cells);
* ``` * ```
* *
@ -91,13 +88,13 @@ const createXmlDocument = () => {
* output is displayed in a dialog box. * output is displayed in a dialog box.
* *
* ```javascript * ```javascript
* var enc = new Codec(); * const enc = new Codec();
* var cells = graph.getSelectionCells(); * const cells = graph.getSelectionCells();
* mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells))); * const xml = xmlUtils.getPrettyXml(enc.encode(cells));
* ``` * ```
* *
* Newlines in the XML can be converted to <br>, in which case a '<br>' argument * Newlines in the XML can be converted to <br>, in which case a '<br>' argument
* must be passed to {@link mxUtils.getXml} as the second argument. * must be passed to {@link getXml} as the second argument.
* *
* ### Debugging * ### Debugging
* *
@ -105,11 +102,11 @@ const createXmlDocument = () => {
* encoded objects: * encoded objects:
* *
* ```javascript * ```javascript
* var oldEncode = encode; * const oldEncode = encode;
* encode(obj) * encode(obj)
* { * {
* MaxLog.show(); * MaxLog.show();
* MaxLog.debug('Codec.encode: obj='+mxUtils.getFunctionName(obj.constructor)); * MaxLog.debug('Codec.encode: obj='+StringUtils.getFunctionName(obj.constructor));
* *
* return oldEncode.apply(this, arguments); * return oldEncode.apply(this, arguments);
* }; * };
@ -146,7 +143,7 @@ class Codec {
/** /**
* Lookup table for resolving IDs to elements. * Lookup table for resolving IDs to elements.
*/ */
elements: any = null; // { [key: string]: Element } | null elements: any = null; // TODO why not { [key: string]: Element } | null
/** /**
* Specifies if default values should be encoded. Default is false. * Specifies if default values should be encoded. Default is false.
@ -154,7 +151,7 @@ class Codec {
encodeDefaults = false; encodeDefaults = false;
/** /**
* Assoiates the given object with the given ID and returns the given object. * Associates the given object with the given ID and returns the given object.
* *
* @param id ID for the object to be associated with. * @param id ID for the object to be associated with.
* @param obj Object to be associated with the ID. * @param obj Object to be associated with the ID.
@ -199,7 +196,7 @@ class Codec {
* Example: * Example:
* *
* ```javascript * ```javascript
* var codec = new Codec(); * const codec = new Codec();
* codec.lookup(id) * codec.lookup(id)
* { * {
* return model.getCell(id); * return model.getCell(id);
@ -222,11 +219,6 @@ class Codec {
return this.elements[id]; return this.elements[id];
} }
/**
* Returns the element with the given ID from {@link document}.
*
* @param id String that contains the ID.
*/
updateElements(): void { updateElements(): void {
if (this.elements == null) { if (this.elements == null) {
this.elements = {}; this.elements = {};
@ -299,7 +291,7 @@ class Codec {
* Example: * Example:
* *
* ```javascript * ```javascript
* var codec = new Codec(); * const codec = new Codec();
* codec.reference(obj) * codec.reference(obj)
* { * {
* return obj.getCustomId(); * return obj.getCustomId();
@ -313,8 +305,7 @@ class Codec {
} }
/** /**
* Encodes the specified object and returns the resulting * Encodes the specified object and returns the resulting XML node.
* XML node.
* *
* @param obj Object to be encoded. * @param obj Object to be encoded.
*/ */
@ -345,7 +336,7 @@ class Codec {
* the new instance if no object was given. * the new instance if no object was given.
* *
* @param node XML node to be decoded. * @param node XML node to be decoded.
* @param into Optional object to be decodec into. * @param into Optional object to be decoded into.
*/ */
decode(node: Element | null, into?: any): any { decode(node: Element | null, into?: any): any {
this.updateElements(); this.updateElements();
@ -418,10 +409,9 @@ class Codec {
* @param restoreStructures Optional boolean indicating whether * @param restoreStructures Optional boolean indicating whether
* the graph structure should be restored by calling insert * the graph structure should be restored by calling insert
* and insertEdge on the parent and terminals, respectively. * and insertEdge on the parent and terminals, respectively.
* Default is true. * Default is `true`.
*/ */
decodeCell(node: Element, restoreStructures?: boolean): Cell { decodeCell(node: Element, restoreStructures = true): Cell {
restoreStructures = restoreStructures != null ? restoreStructures : true;
let cell = null; let cell = null;
if (node != null && node.nodeType === NODETYPE.ELEMENT) { if (node != null && node.nodeType === NODETYPE.ELEMENT) {
@ -490,7 +480,7 @@ class Codec {
* are not null. * are not null.
* *
* @param node XML node to set the attribute for. * @param node XML node to set the attribute for.
* @param attributes Attributename to be set. * @param attribute The name of the attribute to be set.
* @param value New value of the attribute. * @param value New value of the attribute.
*/ */
setAttribute(node: Element, attribute: string, value: any): void { setAttribute(node: Element, attribute: string, value: any): void {

View File

@ -21,12 +21,12 @@ import ObjectCodec from './ObjectCodec';
/** /**
* Singleton class that acts as a global registry for codecs. * Singleton class that acts as a global registry for codecs.
* *
* ### Adding an <Codec>: * ### Adding a Codec
* *
* 1. Define a default codec with a new instance of the object to be handled. * 1. Define a default codec with a new instance of the object to be handled.
* *
* ```javascript * ```javascript
* var codec = new ObjectCodec(new Transactions()); * const codec = new ObjectCodec(new Transactions());
* ``` * ```
* *
* 2. Define the functions required for encoding and decoding objects. * 2. Define the functions required for encoding and decoding objects.
@ -36,36 +36,27 @@ import ObjectCodec from './ObjectCodec';
* codec.decode = function(dec: Codec, node: Element, into: any): any { ... } * codec.decode = function(dec: Codec, node: Element, into: any): any { ... }
* ``` * ```
* *
* 3. Register the codec in the <CodecRegistry>. * 3. Register the codec in the CodecRegistry.
* *
* ```javascript * ```javascript
* CodecRegistry.register(codec); * CodecRegistry.register(codec);
* ``` * ```
* *
* {@link ObjectCodec.decode} may be used to either create a new * {@link ObjectCodec.decode} may be used to either create a new instance of an object or to configure an existing instance,
* instance of an object or to configure an existing instance, * in which case the into argument points to the existing object. In this case, we say the codec "configures" the object.
* in which case the into argument points to the existing
* object. In this case, we say the codec "configures" the
* object.
*
* @class CodecRegistry
*/ */
class CodecRegistry { class CodecRegistry {
static codecs: { [key: string]: ObjectCodec | undefined } = {}; static codecs: { [key: string]: ObjectCodec | undefined } = {};
/** /**
* Maps from classnames to codecnames. * Maps from classnames to codec names.
* @static
*/ */
static aliases: { [key: string]: string | undefined } = {}; static aliases: { [key: string]: string | undefined } = {};
/** /**
* Registers a new codec and associates the name of the template * Registers a new codec and associates the name of the template constructor in the codec with the codec object.
* constructor in the codec with the codec object.
* *
* @static * @param codec ObjectCodec to be registered.
*
* @param codec - {@link ObjectCodec} to be registered.
*/ */
static register(codec: ObjectCodec): ObjectCodec { static register(codec: ObjectCodec): ObjectCodec {
if (codec != null) { if (codec != null) {
@ -81,20 +72,16 @@ class CodecRegistry {
} }
/** /**
* Adds an alias for mapping a classname to a codecname. * Adds an alias for mapping a classname to a codec name.
* @static
*/ */
static addAlias(classname: string, codecname: string): void { static addAlias(classname: string, codecname: string): void {
CodecRegistry.aliases[classname] = codecname; CodecRegistry.aliases[classname] = codecname;
} }
/** /**
* Returns a codec that handles objects that are constructed * Returns a codec that handles objects that are constructed using the given constructor.
* using the given constructor.
* *
* @static * @param constructor_ JavaScript constructor function.
*
* @param ctor - JavaScript constructor function.
*/ */
static getCodec(constructor_: any): ObjectCodec | null { static getCodec(constructor_: any): ObjectCodec | null {
let codec = null; let codec = null;
@ -109,8 +96,7 @@ class CodecRegistry {
codec = CodecRegistry.codecs[name] ?? null; codec = CodecRegistry.codecs[name] ?? null;
// Registers a new default codec for the given constructor // Registers a new default codec for the given constructor if no codec has been previously defined.
// if no codec has been previously defined.
if (codec == null) { if (codec == null) {
try { try {
codec = new ObjectCodec(new constructor_()); codec = new ObjectCodec(new constructor_());

View File

@ -24,7 +24,7 @@ import { NODETYPE } from '../util/Constants';
import { isInteger, isNumeric } from '../util/mathUtils'; import { isInteger, isNumeric } from '../util/mathUtils';
import { getTextContent } from '../util/domUtils'; import { getTextContent } from '../util/domUtils';
import { load } from '../util/MaxXmlRequest'; import { load } from '../util/MaxXmlRequest';
import Codec from './Codec'; import type Codec from './Codec';
/** /**
* Generic codec for JavaScript objects that implements a mapping between * Generic codec for JavaScript objects that implements a mapping between
@ -36,7 +36,7 @@ import Codec from './Codec';
* Consider the following example. * Consider the following example.
* *
* ```javascript * ```javascript
* var obj = new Object(); * const obj = new Object();
* obj.foo = "Foo"; * obj.foo = "Foo";
* obj.bar = "Bar"; * obj.bar = "Bar";
* ``` * ```
@ -44,8 +44,8 @@ import Codec from './Codec';
* This object is encoded into an XML node using the following. * This object is encoded into an XML node using the following.
* *
* ```javascript * ```javascript
* var enc = new Codec(); * const enc = new Codec();
* var node = enc.encode(obj); * const node = enc.encode(obj);
* ``` * ```
* *
* The output of the encoding may be viewed using {@link MaxLog} as follows. * The output of the encoding may be viewed using {@link MaxLog} as follows.
@ -63,7 +63,7 @@ import Codec from './Codec';
* *
* In the above output, the foo and bar fields have been mapped to attributes * In the above output, the foo and bar fields have been mapped to attributes
* with the same names, and the name of the constructor was used for the * with the same names, and the name of the constructor was used for the
* nodename. * node name.
* *
* ### Booleans * ### Booleans
* *
@ -75,15 +75,15 @@ import Codec from './Codec';
* *
* The above scheme is applied to all atomic fields, that is, to all non-object * The above scheme is applied to all atomic fields, that is, to all non-object
* fields of an object. For object fields, a child node is created with a * fields of an object. For object fields, a child node is created with a
* special attribute that contains the fieldname. This special attribute is * special attribute that contains the field name. This special attribute is
* called "as" and hence, as is a reserved word that should not be used for a * called "as" and hence, as is a reserved word that should not be used for a
* fieldname. * field name.
* *
* Consider the following example where foo is an object and bar is an atomic * Consider the following example where foo is an object and bar is an atomic
* property of foo. * property of foo.
* *
* ```javascript * ```javascript
* var obj = {foo: {bar: "Bar"}}; * const obj = {foo: {bar: "Bar"}};
* ``` * ```
* *
* This will be mapped to the following XML structure by ObjectCodec. * This will be mapped to the following XML structure by ObjectCodec.
@ -95,13 +95,13 @@ import Codec from './Codec';
* ``` * ```
* *
* In the above output, the inner Object node contains the as-attribute that * In the above output, the inner Object node contains the as-attribute that
* specifies the fieldname in the enclosing object. That is, the field foo was * specifies the field name in the enclosing object. That is, the field foo was
* mapped to a child node with an as-attribute that has the value foo. * mapped to a child node with an as-attribute that has the value foo.
* *
* ### Arrays * ### Arrays
* *
* Arrays are special objects that are either associative, in which case each * Arrays are special objects that are either associative, in which case each
* key, value pair is treated like a field where the key is the fieldname, or * key, value pair is treated like a field where the key is the field name, or
* they are a sequence of atomic values and objects, which is mapped to a * they are a sequence of atomic values and objects, which is mapped to a
* sequence of child nodes. For object elements, the above scheme is applied * sequence of child nodes. For object elements, the above scheme is applied
* without the use of the special as-attribute for creating each child. For * without the use of the special as-attribute for creating each child. For
@ -113,7 +113,7 @@ import Codec from './Codec';
* called bar with an atomic value, and foo with an object value. * called bar with an atomic value, and foo with an object value.
* *
* ```javascript * ```javascript
* var obj = ["Bar", {bar: "Bar"}]; * const obj = ["Bar", {bar: "Bar"}];
* obj["bar"] = "Bar"; * obj["bar"] = "Bar";
* obj["foo"] = {bar: "Bar"}; * obj["foo"] = {bar: "Bar"};
* ``` * ```
@ -139,7 +139,7 @@ import Codec from './Codec';
* which are used to lookup the object in a table within {@link Codec}. The * which are used to lookup the object in a table within {@link Codec}. The
* {@link isReference} function is in charge of deciding if a specific field should * {@link isReference} function is in charge of deciding if a specific field should
* be encoded as a reference or not. Its default implementation returns true if * be encoded as a reference or not. Its default implementation returns true if
* the fieldname is in {@link idrefs}, an array of strings that is used to configure * the field name is in {@link idrefs}, an array of strings that is used to configure
* the {@link ObjectCodec}. * the {@link ObjectCodec}.
* *
* Using this approach, the mapping does not guarantee that the referenced * Using this approach, the mapping does not guarantee that the referenced
@ -174,11 +174,11 @@ import Codec from './Codec';
* For decoding JavaScript expressions, the add-node may be used with a text * For decoding JavaScript expressions, the add-node may be used with a text
* content that contains the JavaScript expression. For example, the following * content that contains the JavaScript expression. For example, the following
* creates a field called foo in the enclosing object and assigns it the value * creates a field called foo in the enclosing object and assigns it the value
* of {@link mxConstants.ALIGN_LEFT}. * of {@link Constants.ALIGN.LEFT}.
* *
* ```javascript * ```javascript
* <Object> * <Object>
* <add as="foo">mxConstants.ALIGN_LEFT</add> * <add as="foo">Constants.ALIGN.LEFT</add>
* </Object> * </Object>
* ``` * ```
* *
@ -195,8 +195,6 @@ import Codec from './Codec';
* functions on the resulting object. * functions on the resulting object.
* *
* Expressions are only evaluated if {@link allowEval} is true. * Expressions are only evaluated if {@link allowEval} is true.
*
* @class ObjectCodec
*/ */
class ObjectCodec { class ObjectCodec {
constructor( constructor(
@ -220,8 +218,9 @@ class ObjectCodec {
/** /**
* Static global switch that specifies if expressions in arrays are allowed. * Static global switch that specifies if expressions in arrays are allowed.
* Default is false. NOTE: Enabling this carries a possible security risk. *
* @static * **NOTE**: Enabling this carries a possible security risk.
* @default false
*/ */
static allowEval = false; static allowEval = false;
@ -231,8 +230,7 @@ class ObjectCodec {
template: any; template: any;
/** /**
* Array containing the variable names that should be * Array containing the variable names that should be ignored by the codec.
* ignored by the codec.
*/ */
exclude: string[]; exclude: string[];
@ -244,21 +242,22 @@ class ObjectCodec {
idrefs: string[]; idrefs: string[];
/** /**
* Maps from from fieldnames to XML attribute names. * Maps from field names to XML attribute names.
*/ */
mapping: { [key: string]: string }; mapping: { [key: string]: string };
/** /**
* Maps from from XML attribute names to fieldnames. * Maps from XML attribute names to fieldnames.
*/ */
reverse: { [key: string]: string }; reverse: { [key: string]: string };
/** /**
* Returns the name used for the nodenames and lookup of the codec when * Returns the name used for the node names and lookup of the codec when
* classes are encoded and nodes are decoded. For classes to work with * classes are encoded and nodes are decoded. For classes to work with
* this the codec registry automatically adds an alias for the classname * this the codec registry automatically adds an alias for the classname
* if that is different than what this returns. The default implementation * if that is different from what this returns.
* returns the classname of the template class. *
* The default implementation returns the classname of the template class.
*/ */
getName(): string { getName(): string {
return this.template.constructor.name; return this.template.constructor.name;
@ -272,7 +271,7 @@ class ObjectCodec {
} }
/** /**
* Returns the fieldname for the given attributename. * Returns the field name for the given attribute name.
* Looks up the value in the {@link reverse} mapping or returns * Looks up the value in the {@link reverse} mapping or returns
* the input if there is no reverse mapping for the * the input if there is no reverse mapping for the
* given name. * given name.
@ -290,7 +289,7 @@ class ObjectCodec {
} }
/** /**
* Returns the attributename for the given fieldname. * Returns the attribute name for the given field name.
* Looks up the value in the {@link mapping} or returns * Looks up the value in the {@link mapping} or returns
* the input if there is no mapping for the * the input if there is no mapping for the
* given name. * given name.
@ -308,8 +307,8 @@ class ObjectCodec {
/** /**
* Returns true if the given attribute is to be ignored by the codec. This * Returns true if the given attribute is to be ignored by the codec. This
* implementation returns true if the given fieldname is in {@link exclude} or * implementation returns true if the given field name is in {@link exclude} or
* if the fieldname equals {@link ObjectIdentity.FIELD_NAME}. * if the field name equals {@link ObjectIdentity.FIELD_NAME}.
* *
* @param obj Object instance that contains the field. * @param obj Object instance that contains the field.
* @param attr Fieldname of the field. * @param attr Fieldname of the field.
@ -322,12 +321,12 @@ class ObjectCodec {
} }
/** /**
* Returns true if the given fieldname is to be treated * Returns true if the given field name is to be treated
* as a textual reference (ID). This implementation returns * as a textual reference (ID). This implementation returns
* true if the given fieldname is in {@link idrefs}. * true if the given field name is in {@link idrefs}.
* *
* @param obj Object instance that contains the field. * @param obj Object instance that contains the field.
* @param attr Fieldname of the field. * @param attr Field name of the field.
* @param value Value of the field. * @param value Value of the field.
* @param write Boolean indicating if the field is being encoded or decoded. * @param write Boolean indicating if the field is being encoded or decoded.
* Write is true if the field is being encoded, else it is being decoded. * Write is true if the field is being encoded, else it is being decoded.
@ -545,7 +544,7 @@ class ObjectCodec {
* Returns true if the given object attribute is a boolean value. * Returns true if the given object attribute is a boolean value.
* *
* @param enc {@link Codec} that controls the encoding process. * @param enc {@link Codec} that controls the encoding process.
* @param obj Objec to convert the attribute for. * @param obj Object to convert the attribute for.
* @param name Name of the attribute to be converted. * @param name Name of the attribute to be converted.
* @param value Value of the attribute to be converted. * @param value Value of the attribute to be converted.
*/ */
@ -580,20 +579,19 @@ class ObjectCodec {
* *
* @param dec {@link Codec} that controls the decoding process. * @param dec {@link Codec} that controls the decoding process.
* @param attr XML attribute to be converted. * @param attr XML attribute to be converted.
* @param obj Objec to convert the attribute for. * @param obj Object to convert the attribute for.
*/ */
isNumericAttribute(dec: Codec, attr: any, obj: any): boolean { isNumericAttribute(dec: Codec, attr: any, obj: any): boolean {
// Handles known numeric attributes for generic objects // Handles known numeric attributes for generic objects
const result = return (
(obj.constructor === Geometry && (obj.constructor === Geometry &&
(attr.name === 'x' || (attr.name === 'x' ||
attr.name === 'y' || attr.name === 'y' ||
attr.name === 'width' || attr.name === 'width' ||
attr.name === 'height')) || attr.name === 'height')) ||
(obj.constructor === Point && (attr.name === 'x' || attr.name === 'y')) || (obj.constructor === Point && (attr.name === 'x' || attr.name === 'y')) ||
isNumeric(attr.value); isNumeric(attr.value)
);
return result;
} }
/** /**
@ -711,7 +709,7 @@ class ObjectCodec {
* *
* @param dec {@link Codec} that controls the decoding process. * @param dec {@link Codec} that controls the decoding process.
* @param node XML node to be decoded. * @param node XML node to be decoded.
* @param obj Objec to encode the node into. * @param obj Object to encode the node into.
*/ */
decodeAttributes(dec: Codec, node: Element, obj: any): void { decodeAttributes(dec: Codec, node: Element, obj: any): void {
const attrs = node.attributes; const attrs = node.attributes;
@ -847,8 +845,8 @@ class ObjectCodec {
/** /**
* Sets the decoded child node as a value of the given object. If the * Sets the decoded child node as a value of the given object. If the
* object is a map, then the value is added with the given fieldname as a * object is a map, then the value is added with the given field name as a
* key. If the fieldname is not empty, then setFieldValue is called or * key. If the field name is not empty, then setFieldValue is called or
* else, if the object is a collection, the value is added to the * else, if the object is a collection, the value is added to the
* collection. For strongly typed languages it may be required to * collection. For strongly typed languages it may be required to
* override this with the correct code to add an entry to an object. * override this with the correct code to add an entry to an object.
@ -915,7 +913,7 @@ class ObjectCodec {
* without any changes. The return value of this method * without any changes. The return value of this method
* is returned to the decoder from {@link decode}. * is returned to the decoder from {@link decode}.
* *
* @param enc {@link Codec} that controls the encoding process. * @param dec {@link Codec} that controls the encoding process.
* @param node XML node to be decoded. * @param node XML node to be decoded.
* @param obj Object that represents the default decoding. * @param obj Object that represents the default decoding.
*/ */