Home Manual Reference Source Test

src/transform/Mapping.js

import { basename } from 'path';
import assert from 'assert';
import { VariantArrayType, DataType } from 'node-opcua/lib/datamodel/variant';
import Transformer from '../lib/transform/Transformer';

/**
 * Atvise specific types that need special extensions.
 * @type {Map<string, Object>}
 */
const standardTypes = {
  'VariableTypes.ATVISE.HtmlHelp': {
    extension: '.help.html',
    dataType: DataType.ByteString,
  },
  'VariableTypes.ATVISE.TranslationTable': {
    extension: '.locs.xml',
    dataType: DataType.XmlElement,
  },
};

/**
 * Extensions to use for {@link node-opcua~DataType}s.
 * @type {Map<string, string>}
 */
const extensionForDataType = {
  [DataType.Boolean.key]: '.bool',
  [DataType.SByte.key]: '.sbyte',
  [DataType.Byte.key]: '.byte',
  [DataType.Int16.key]: '.int16',
  [DataType.UInt16.key]: '.uint16',
  [DataType.Int32.key]: '.int32',
  [DataType.UInt32.key]: '.uint32',
  [DataType.Int64.key]: '.int64',
  [DataType.UInt64.key]: '.uint64',
  [DataType.Float.key]: '.float',
  [DataType.Double.key]: '.double',
  [DataType.String.key]: '.string',
  [DataType.DateTime.key]: '.datetime',
  [DataType.Guid.key]: '.guid',
  // [DataType.ByteString.key]: '.bytestring',
  [DataType.XmlElement.key]: '.xml',
  [DataType.NodeId.key]: '.nodeid',
  [DataType.ExpandedNodeId.key]: '.enodeid',
  [DataType.StatusCode.key]: '.status',
  [DataType.QualifiedName.key]: '.name',
  [DataType.LocalizedText.key]: '.text',
  [DataType.ExtensionObject.key]: '.obj',
  [DataType.DataValue.key]: '.value',
  [DataType.Variant.key]: '.variant',
  [DataType.DiagnosticInfo.key]: '.info',
};

/**
 * Extensions to use for {@link node-opcua~VariantArrayType}s.
 * @type {Map<string, string>}
 */
const extensionForArrayType = {
  [VariantArrayType.Array.key]: '.array',
  [VariantArrayType.Matrix.key]: '.matrix',
};

/**
 * A Transformer that maps {@link ReadStream.ReadResult}s to {@link AtviseFile}s.
 */
export default class MappingTransformer extends Transformer {
  /**
   * Creates a new mapping transformer.
   * @param {Object} [options] The arguments passed to the {@link Transformer} constructor.
   */
  constructor(options = {}) {
    super(options);

    /**
     * Contents of the reference files read but not used yet.
     * @type {Object}
     */
    this._readReferenceFiles = {};
  }

  /**
   * Writes an {@link AtviseFile} for each {@link ReadStream.ReadResult} read. If a read file has a
   * non-standard type (definition) an additional `rc` file is pushed holding this type.
   * @param {Node} node The read result to create the file for.
   * @param {string} encoding The encoding used.
   * @param {function(err: ?Error, data: ?AtviseFile)} callback Called with the error that occurred
   * while transforming the read result or the resulting file.
   */
  transformFromDB(node, encoding, callback) {
    if (!node.fullyMapped && !node.parentResolvesMetadata) {
      // Skip mapping for e.g. split files
      const typeDefinition = node.typeDefinition;
      let isStandardTypeNode = false;

      // Add extensions for standard types
      for (const [def, { extension }] of Object.entries(standardTypes)) {
        if (node.isVariable && typeDefinition === def) {
          node.renameTo(`${node.name}${extension}`);
          isStandardTypeNode = true;

          // FIXME: Set dataType and mark as resolved
          // FIXME: Set typeDefinition and mark as resolved
        } else if (node.fileName.endsWith(extension)) {
          callback(new Error(`Name conflict: ${node.nodeId} should not end with '${extension}'`));
          return;
        }
      }

      // Add extensions for data types
      for (const [type, ext] of Object.entries(extensionForDataType)) {
        if (node.isVariable && node.value && node.value.dataType.key === type) {
          if (!isStandardTypeNode) {
            node.renameTo(`${node.name}${ext}`);
            break;
          }

          // FIXME: Set dataType and mark as resolved
        }
      }

      // Add extensions for array types
      for (const [type, ext] of Object.entries(extensionForArrayType)) {
        if (node.isVariable && node.value.arrayType.key === type) {
          if (!isStandardTypeNode) {
            node.renameTo(`${node.name}${ext}`);
          }

          // FIXME: Set arrayType and mark as resolved
        } else if (node.fileName.endsWith(ext)) {
          callback(new Error(`Name conflict: ${node.nodeId} should not end with '${ext}'`));
          return;
        }
      }
    }

    // Compact mapping: Root source folders are AGENT, SYSTEM, ObjectTypes and VariableTypes
    // FIXME: Make optional
    const ignore = new Set([
      58, // Objects -> Types -> BaseObjectType
      62, // Objects -> Types -> BaseVariableType
      85, // Objects
      86, // Objects -> Types
    ]);

    for (let c = node; c && c.parent && !c._compactMappingApplied; c = c.parent) {
      if (ignore.has(c.parent.id.value)) {
        c.parent = c.parent.parent;
        c = node;
      }
    }

    Object.assign(node, {
      _compactMappingApplied: true,
    });

    callback(null, node);
  }

  /**
   * Writes an {@link AtviseFile} for each {@link Node} read.
   * @param {Node} node The raw file.
   * @param {string} encoding The encoding used.
   * @param {function(err: ?Error, data: ?AtviseFile)} callback Called with the error that occurred
   * while transforming the read result or the resulting file.
   */
  transformFromFilesystem(node, encoding, callback) {
    let isStandardTypeNode = false;

    // Resolve standard type from extension
    for (const [, { extension }] of Object.entries(standardTypes)) {
      if (node.name.endsWith(extension)) {
        isStandardTypeNode = true;

        // FIXME: Set dataType and mark as resolved
        // FIXME: Set typeDefinition and mark as resolved

        node.renameTo(basename(node.name, extension));
      }
    }

    // Resolve arrayType from extension
    for (const [type, extension] of Object.entries(extensionForArrayType)) {
      if (node.name.endsWith(extension) && !isStandardTypeNode) {
        assert.equal(node.arrayType.key, type);

        // FIXME: Set arrayType and mark as resolved

        node.renameTo(basename(node.name, extension));
        break;
      }
    }

    // Resolve dataType from extension
    for (const [type, extension] of Object.entries(extensionForDataType)) {
      if (node.name.endsWith(extension) && !isStandardTypeNode && node.dataType.key === type) {
        // FIXME: Set dataType and mark as resolved

        node.renameTo(basename(node.name, extension));
        break;
      }
    }

    return callback(null, node);
  }

  /**
   * `true` as the mapping transformer should infer references from config files.
   */
  get transformsReferenceConfigFiles() {
    return true;
  }
}