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.
*
* ```javascript
* var encoder = new Codec();
* var result = encoder.encode(graph.getDataModel());
* var xml = mxUtils.getXml(result);
* const encoder = new Codec();
* const result = encoder.encode(graph.getDataModel());
* const xml = mxUtils.getXml(result);
* ```
*
* #### Example
*
* 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
* an XML file, eg. {@link mxUtils.get}, or using {@link mxUtils.parseXml} for parsing an
* XML string.
* document may be obtained using {@link parseXml} for parsing an XML string.
*
* ```javascript
* var doc = mxUtils.parseXml(xmlString);
* var codec = new Codec(doc);
* const doc = xmlUtils.parseXml(xmlString);
* const codec = new Codec(doc);
* codec.decode(doc.documentElement, graph.getDataModel());
* ```
*
@ -70,18 +68,17 @@ const createXmlDocument = () => {
* be added anywhere in the cell hierarchy after parsing.
*
* ```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>';
* var doc = mxUtils.parseXml(xml);
* var codec = new Codec(doc);
* var elt = doc.documentElement.firstChild;
* var cells = [];
* 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>';
* const doc = mxUtils.parseXml(xml);
* const codec = new Codec(doc);
* let elt = doc.documentElement.firstChild;
* const cells = [];
*
* while (elt != null)
* {
* cells.push(codec.decode(elt));
* elt = elt.nextSibling;
* }
*
* graph.addCells(cells);
* ```
*
@ -91,13 +88,13 @@ const createXmlDocument = () => {
* output is displayed in a dialog box.
*
* ```javascript
* var enc = new Codec();
* var cells = graph.getSelectionCells();
* mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
* const enc = new Codec();
* const cells = graph.getSelectionCells();
* const xml = xmlUtils.getPrettyXml(enc.encode(cells));
* ```
*
* 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
*
@ -105,11 +102,11 @@ const createXmlDocument = () => {
* encoded objects:
*
* ```javascript
* var oldEncode = encode;
* const oldEncode = encode;
* encode(obj)
* {
* 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);
* };
@ -146,7 +143,7 @@ class Codec {
/**
* 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.
@ -154,7 +151,7 @@ class Codec {
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 obj Object to be associated with the ID.
@ -199,7 +196,7 @@ class Codec {
* Example:
*
* ```javascript
* var codec = new Codec();
* const codec = new Codec();
* codec.lookup(id)
* {
* return model.getCell(id);
@ -222,11 +219,6 @@ class Codec {
return this.elements[id];
}
/**
* Returns the element with the given ID from {@link document}.
*
* @param id String that contains the ID.
*/
updateElements(): void {
if (this.elements == null) {
this.elements = {};
@ -299,7 +291,7 @@ class Codec {
* Example:
*
* ```javascript
* var codec = new Codec();
* const codec = new Codec();
* codec.reference(obj)
* {
* return obj.getCustomId();
@ -313,8 +305,7 @@ class Codec {
}
/**
* Encodes the specified object and returns the resulting
* XML node.
* Encodes the specified object and returns the resulting XML node.
*
* @param obj Object to be encoded.
*/
@ -345,7 +336,7 @@ class Codec {
* the new instance if no object was given.
*
* @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 {
this.updateElements();
@ -418,10 +409,9 @@ class Codec {
* @param restoreStructures Optional boolean indicating whether
* the graph structure should be restored by calling insert
* and insertEdge on the parent and terminals, respectively.
* Default is true.
* Default is `true`.
*/
decodeCell(node: Element, restoreStructures?: boolean): Cell {
restoreStructures = restoreStructures != null ? restoreStructures : true;
decodeCell(node: Element, restoreStructures = true): Cell {
let cell = null;
if (node != null && node.nodeType === NODETYPE.ELEMENT) {
@ -490,7 +480,7 @@ class Codec {
* are not null.
*
* @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.
*/
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.
*
* ### Adding an <Codec>:
* ### Adding a Codec
*
* 1. Define a default codec with a new instance of the object to be handled.
*
* ```javascript
* var codec = new ObjectCodec(new Transactions());
* const codec = new ObjectCodec(new Transactions());
* ```
*
* 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 { ... }
* ```
*
* 3. Register the codec in the <CodecRegistry>.
* 3. Register the codec in the CodecRegistry.
*
* ```javascript
* CodecRegistry.register(codec);
* ```
*
* {@link ObjectCodec.decode} may be used to either create a new
* 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.
*
* @class CodecRegistry
* {@link ObjectCodec.decode} may be used to either create a new 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.
*/
class CodecRegistry {
static codecs: { [key: string]: ObjectCodec | undefined } = {};
/**
* Maps from classnames to codecnames.
* @static
* Maps from classnames to codec names.
*/
static aliases: { [key: string]: string | undefined } = {};
/**
* Registers a new codec and associates the name of the template
* constructor in the codec with the codec object.
* Registers a new codec and associates the name of the template constructor in the codec with the codec object.
*
* @static
*
* @param codec - {@link ObjectCodec} to be registered.
* @param codec ObjectCodec to be registered.
*/
static register(codec: ObjectCodec): ObjectCodec {
if (codec != null) {
@ -81,20 +72,16 @@ class CodecRegistry {
}
/**
* Adds an alias for mapping a classname to a codecname.
* @static
* Adds an alias for mapping a classname to a codec name.
*/
static addAlias(classname: string, codecname: string): void {
CodecRegistry.aliases[classname] = codecname;
}
/**
* Returns a codec that handles objects that are constructed
* using the given constructor.
* Returns a codec that handles objects that are constructed using the given constructor.
*
* @static
*
* @param ctor - JavaScript constructor function.
* @param constructor_ JavaScript constructor function.
*/
static getCodec(constructor_: any): ObjectCodec | null {
let codec = null;
@ -109,8 +96,7 @@ class CodecRegistry {
codec = CodecRegistry.codecs[name] ?? null;
// Registers a new default codec for the given constructor
// if no codec has been previously defined.
// Registers a new default codec for the given constructor if no codec has been previously defined.
if (codec == null) {
try {
codec = new ObjectCodec(new constructor_());

View File

@ -24,7 +24,7 @@ import { NODETYPE } from '../util/Constants';
import { isInteger, isNumeric } from '../util/mathUtils';
import { getTextContent } from '../util/domUtils';
import { load } from '../util/MaxXmlRequest';
import Codec from './Codec';
import type Codec from './Codec';
/**
* Generic codec for JavaScript objects that implements a mapping between
@ -36,7 +36,7 @@ import Codec from './Codec';
* Consider the following example.
*
* ```javascript
* var obj = new Object();
* const obj = new Object();
* obj.foo = "Foo";
* obj.bar = "Bar";
* ```
@ -44,8 +44,8 @@ import Codec from './Codec';
* This object is encoded into an XML node using the following.
*
* ```javascript
* var enc = new Codec();
* var node = enc.encode(obj);
* const enc = new Codec();
* const node = enc.encode(obj);
* ```
*
* 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
* with the same names, and the name of the constructor was used for the
* nodename.
* node name.
*
* ### Booleans
*
@ -75,15 +75,15 @@ import Codec from './Codec';
*
* 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
* 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
* fieldname.
* field name.
*
* Consider the following example where foo is an object and bar is an atomic
* property of foo.
*
* ```javascript
* var obj = {foo: {bar: "Bar"}};
* const obj = {foo: {bar: "Bar"}};
* ```
*
* 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
* 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.
*
* ### Arrays
*
* 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
* 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
@ -113,7 +113,7 @@ import Codec from './Codec';
* called bar with an atomic value, and foo with an object value.
*
* ```javascript
* var obj = ["Bar", {bar: "Bar"}];
* const obj = ["Bar", {bar: "Bar"}];
* obj["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
* {@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
* 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}.
*
* 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
* content that contains the JavaScript expression. For example, the following
* 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
* <Object>
* <add as="foo">mxConstants.ALIGN_LEFT</add>
* <add as="foo">Constants.ALIGN.LEFT</add>
* </Object>
* ```
*
@ -195,8 +195,6 @@ import Codec from './Codec';
* functions on the resulting object.
*
* Expressions are only evaluated if {@link allowEval} is true.
*
* @class ObjectCodec
*/
class ObjectCodec {
constructor(
@ -220,8 +218,9 @@ class ObjectCodec {
/**
* 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;
@ -231,8 +230,7 @@ class ObjectCodec {
template: any;
/**
* Array containing the variable names that should be
* ignored by the codec.
* Array containing the variable names that should be ignored by the codec.
*/
exclude: string[];
@ -244,21 +242,22 @@ class ObjectCodec {
idrefs: string[];
/**
* Maps from from fieldnames to XML attribute names.
* Maps from field names to XML attribute names.
*/
mapping: { [key: string]: string };
/**
* Maps from from XML attribute names to fieldnames.
* Maps from XML attribute names to fieldnames.
*/
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
* this the codec registry automatically adds an alias for the classname
* if that is different than what this returns. The default implementation
* returns the classname of the template class.
* if that is different from what this returns.
*
* The default implementation returns the classname of the template class.
*/
getName(): string {
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
* the input if there is no reverse mapping for the
* 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
* the input if there is no mapping for the
* given name.
@ -308,8 +307,8 @@ class ObjectCodec {
/**
* 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
* if the fieldname equals {@link ObjectIdentity.FIELD_NAME}.
* implementation returns true if the given field name is in {@link exclude} or
* if the field name equals {@link ObjectIdentity.FIELD_NAME}.
*
* @param obj Object instance that contains 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
* 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 attr Fieldname of the field.
* @param attr Field name of the field.
* @param value Value of the field.
* @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.
@ -545,7 +544,7 @@ class ObjectCodec {
* Returns true if the given object attribute is a boolean value.
*
* @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 value Value of the attribute to be converted.
*/
@ -580,20 +579,19 @@ class ObjectCodec {
*
* @param dec {@link Codec} that controls the decoding process.
* @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 {
// Handles known numeric attributes for generic objects
const result =
return (
(obj.constructor === Geometry &&
(attr.name === 'x' ||
attr.name === 'y' ||
attr.name === 'width' ||
attr.name === 'height')) ||
(obj.constructor === Point && (attr.name === 'x' || attr.name === 'y')) ||
isNumeric(attr.value);
return result;
isNumeric(attr.value)
);
}
/**
@ -711,7 +709,7 @@ class ObjectCodec {
*
* @param dec {@link Codec} that controls the decoding process.
* @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 {
const attrs = node.attributes;
@ -847,8 +845,8 @@ class ObjectCodec {
/**
* 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
* key. If the fieldname is not empty, then setFieldValue is called or
* object is a map, then the value is added with the given field name as a
* 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
* collection. For strongly typed languages it may be required to
* 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
* 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 obj Object that represents the default decoding.
*/