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); // 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 = /^\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; } } class JSONMapper implements IMapper { map(rawSource: any) { return JSON.parse(rawSource); } } // TODO - Dom configuration mapper. class DOMMapper implements IMapper { 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); 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! problem... 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; } } export class MapperManager { private _mappers: { [key: string]: IMapper }; public static DefaultMapper = 'json'; constructor() { this._mappers = { "html": new HTMLMapper(), "json": new JSONMapper(), "dom": new DOMMapper() } } public getMapper(type: string) { if (!this._mappers[type]) { Tools.Error("No mapper defined for " + type); } return this._mappers[type] || this._mappers[MapperManager.DefaultMapper]; } public registerMapper(type: string, mapper: IMapper) { this._mappers[type] = mapper; } public dispose() { this._mappers = {}; } } export let mapperManager = new MapperManager();