import { Tools } from 'babylonjs'; import { ViewerConfiguration } from './configuration'; import { kebabToCamel } from '../helper'; /** * This is the mapper's interface. Implement this function to create your own mapper and register it at the mapper manager */ export interface IMapper { map(rawSource: any): ViewerConfiguration; } /** * This is a simple HTML mapper. * This mapper parses a single HTML element and returns the configuration from its attributes. * it parses numbers and boolean values to the corresponding variable types. * The following HTML element: *
will result in the following configuration: * * { * test: 1, //a number! * randomFlag: boolean, //camelCase and boolean * a: { * string: { * object: "test" //dot-separated object levels * } * } * } */ class HTMLMapper implements IMapper { /** * Map a specific element and get configuration from it * @param element the HTML element to analyze. */ map(element: HTMLElement): ViewerConfiguration { let config = {}; for (let attrIdx = 0; attrIdx < element.attributes.length; ++attrIdx) { let attr = element.attributes.item(attrIdx); if (!attr) { continue; } // map "object.property" to the right configuration place. let split = attr.nodeName.split('.'); split.reduce((currentConfig, key, idx) => { //convert html-style to json-style let camelKey = kebabToCamel(key); if (idx === split.length - 1) { let val: any = attr!.nodeValue; // firefox warns nodeValue is deprecated, but I found no sign of it anywhere. if (val === "true") { val = true; } else if (val === "false") { val = false; } else { var isnum = !isNaN(parseFloat(val)) && isFinite(val);///^\d+$/.test(val); if (isnum) { let number = parseFloat(val); if (!isNaN(number)) { val = number; } } } currentConfig[camelKey] = val; } else { currentConfig[camelKey] = currentConfig[camelKey] || {}; } return currentConfig[camelKey]; }, config); } return config; } } /** * A simple string-to-JSON mapper. * This is the main mapper, used to analyze downloaded JSON-Configuration or JSON payload */ class JSONMapper implements IMapper { map(rawSource: string) { return JSON.parse(rawSource); } } /** * The DOM Mapper will traverse an entire DOM Tree and will load the configuration from the * DOM elements and attributes. */ class DOMMapper implements IMapper { /** * The mapping function that will convert HTML data to a viewer configuration object * @param baseElement the baseElement from which to start traversing * @returns a ViewerCOnfiguration object from the provided HTML Element */ map(baseElement: HTMLElement): ViewerConfiguration { let htmlMapper = new HTMLMapper(); let config = htmlMapper.map(baseElement); let traverseChildren = function (element: HTMLElement, partConfig) { let children = element.children; if (children.length) { for (let i = 0; i < children.length; ++i) { let item = children.item(i); // use the HTML Mapper to read configuration from a single element let configMapped = htmlMapper.map(item); let key = kebabToCamel(item.nodeName.toLowerCase()); if (item.attributes.getNamedItem('array') && item.attributes.getNamedItem('array')!.nodeValue === 'true') { partConfig[key] = []; } else { if (element.attributes.getNamedItem('array') && element.attributes.getNamedItem('array')!.nodeValue === 'true') { partConfig.push(configMapped) } else if (partConfig[key]) { //exists already! probably an array element.setAttribute('array', 'true'); let oldItem = partConfig[key]; partConfig = [oldItem, configMapped] } else { partConfig[key] = configMapped; } } traverseChildren(item, partConfig[key] || configMapped); } } return partConfig; } traverseChildren(baseElement, config); return config; } } /** * The MapperManager manages the different implemented mappers. * It allows the user to register new mappers as well and use them to parse their own configuration data */ export class MapperManager { private _mappers: { [key: string]: IMapper }; /** * The default mapper is the JSON mapper. */ public static DefaultMapper = 'json'; constructor() { this._mappers = { "html": new HTMLMapper(), "json": new JSONMapper(), "dom": new DOMMapper() } } /** * Get a specific configuration mapper. * * @param type the name of the mapper to load */ public getMapper(type: string) { if (!this._mappers[type]) { Tools.Error("No mapper defined for " + type); } return this._mappers[type] || this._mappers[MapperManager.DefaultMapper]; } /** * Use this functio to register your own configuration mapper. * After a mapper is registered, it can be used to parse the specific type fo configuration to the standard ViewerConfiguration. * @param type the name of the mapper. This will be used to define the configuration type and/or to get the mapper * @param mapper The implemented mapper */ public registerMapper(type: string, mapper: IMapper) { this._mappers[type] = mapper; } /** * Dispose the mapper manager and all of its mappers. */ public dispose() { this._mappers = {}; } } /** * mapperManager is a singleton of the type MapperManager. * The mapperManager can be disposed directly with calling mapperManager.dispose() * or indirectly with using BabylonViewer.disposeAll() */ export let mapperManager = new MapperManager();