| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- import { _TypeStore } from 'babylonjs/Misc/typeStore';
- /**
- * Class used to load GUI via XML.
- */
- export class XmlLoader {
- private _nodes: any = {};
- private _nodeTypes: any = {
- element: 1,
- attribute: 2,
- text: 3
- };
- private _isLoaded: boolean = false;
- private _objectAttributes: any = {
- "textHorizontalAlignment": 1,
- "textVerticalAlignment": 2,
- "horizontalAlignment": 3,
- "verticalAlignment": 4,
- "stretch": 5,
- };
- private _parentClass: any;
- /**
- * Create a new xml loader
- * @param parentClass Sets the class context. Used when the loader is instanced inside a class and not in a global context
- */
- constructor(parentClass = null) {
- if (parentClass) {
- this._parentClass = parentClass;
- }
- }
- private _getChainElement(attributeValue: any): any {
- let element: any = window;
- if (this._parentClass) {
- element = this._parentClass;
- }
- let value = attributeValue;
- value = value.split(".");
- for (let i = 0; i < value.length; i++) {
- element = element[value[i]];
- }
- return element;
- }
- private _getClassAttribute(attributeName: string): any {
- const attribute = attributeName.split(".");
- const className = _TypeStore.GetClass("BABYLON.GUI." + attribute[0]);
- return className[attribute[1]];
- }
- private _createGuiElement(node: any, parent: any, linkParent: boolean = true): void {
- try {
- let className = _TypeStore.GetClass("BABYLON.GUI." + node.nodeName);
- let guiNode = new className();
- if (parent && linkParent) {
- parent.addControl(guiNode);
- }
- for (let i = 0; i < node.attributes.length; i++) {
- if (node.attributes[i].name.toLowerCase().includes("datasource")) {
- continue;
- }
- if (node.attributes[i].name.toLowerCase().includes("observable")) {
- let element = this._getChainElement(node.attributes[i].value);
- guiNode[node.attributes[i].name].add(element);
- continue;
- } else if (node.attributes[i].name == "linkWithMesh") {
- if (this._parentClass) {
- guiNode.linkWithMesh(this._parentClass[node.attributes[i].value]);
- } else {
- guiNode.linkWithMesh(window[node.attributes[i].value]);
- }
- } else if (node.attributes[i].value.startsWith("{{") && node.attributes[i].value.endsWith("}}")) {
- let element = this._getChainElement(node.attributes[i].value.substring(2, node.attributes[i].value.length - 2));
- guiNode[node.attributes[i].name] = element;
- } else if (!this._objectAttributes[node.attributes[i].name]) {
- if (node.attributes[i].value == "true" || node.attributes[i].value == "false") {
- guiNode[node.attributes[i].name] = (node.attributes[i].value == 'true');
- } else {
- guiNode[node.attributes[i].name] = !isNaN(Number(node.attributes[i].value)) ? Number(node.attributes[i].value) : node.attributes[i].value;
- }
- } else {
- guiNode[node.attributes[i].name] = this._getClassAttribute(node.attributes[i].value);
- }
- }
- if (!node.attributes.getNamedItem("id")) {
- this._nodes[node.nodeName + Object.keys(this._nodes).length + "_gen"] = guiNode;
- return guiNode;
- }
- let id = node.attributes.getNamedItem("id").value;
- if (id.startsWith("{{") && id.endsWith("}}")) {
- id = this._getChainElement(id.substring(2, id.length - 2));
- }
- if (!this._nodes[id]) {
- this._nodes[id] = guiNode;
- } else {
- throw "XmlLoader Exception : Duplicate ID, every element should have an unique ID attribute";
- }
- return guiNode;
- } catch (e) {
- throw "XmlLoader Exception : Error parsing Control " + node.nodeName + "," + e + ".";
- }
- }
- private _parseGrid(node: any, guiNode: any, parent: any): void {
- let width;
- let height;
- let columns;
- let rows = node.children;
- let cells;
- let isPixel = false;
- let cellNode;
- let rowNumber = -1;
- let columnNumber = -1;
- let totalColumnsNumber = 0;
- for (let i = 0; i < rows.length; i++) {
- if (rows[i].nodeType != this._nodeTypes.element) {
- continue;
- }
- if (rows[i].nodeName != "Row") {
- throw "XmlLoader Exception : Expecting Row node, received " + rows[i].nodeName;
- }
- rowNumber += 1;
- columns = rows[i].children;
- if (!rows[i].attributes.getNamedItem("height")) {
- throw "XmlLoader Exception : Height must be defined for grid rows";
- }
- height = Number(rows[i].attributes.getNamedItem("height").nodeValue);
- isPixel = rows[i].attributes.getNamedItem("isPixel") ? JSON.parse(rows[i].attributes.getNamedItem("isPixel").nodeValue) : false;
- guiNode.addRowDefinition(height, isPixel);
- for (let j = 0; j < columns.length; j++) {
- if (columns[j].nodeType != this._nodeTypes.element) {
- continue;
- }
- if (columns[j].nodeName != "Column") {
- throw "XmlLoader Exception : Expecting Column node, received " + columns[j].nodeName;
- }
- columnNumber += 1;
- if (rowNumber > 0 && columnNumber > totalColumnsNumber) {
- throw "XmlLoader Exception : In the Grid element, the number of columns is defined in the first row, do not add more columns in the subsequent rows.";
- }
- if (rowNumber == 0) {
- if (!columns[j].attributes.getNamedItem("width")) {
- throw "XmlLoader Exception : Width must be defined for all the grid columns in the first row";
- }
- width = Number(columns[j].attributes.getNamedItem("width").nodeValue);
- isPixel = columns[j].attributes.getNamedItem("isPixel") ? JSON.parse(columns[j].attributes.getNamedItem("isPixel").nodeValue) : false;
- guiNode.addColumnDefinition(width, isPixel);
- }
- cells = columns[j].children;
- for (let k = 0; k < cells.length; k++) {
- if (cells[k].nodeType != this._nodeTypes.element) {
- continue;
- }
- cellNode = this._createGuiElement(cells[k], guiNode, false);
- guiNode.addControl(cellNode, rowNumber, columnNumber);
- if (cells[k].firstChild) {
- this._parseXml(cells[k].firstChild, cellNode);
- }
- }
- }
- if (rowNumber == 0) {
- totalColumnsNumber = columnNumber;
- }
- columnNumber = -1;
- }
- if (node.nextSibling) {
- this._parseXml(node.nextSibling, parent);
- }
- }
- private _parseElement(node: any, guiNode: any, parent: any): void {
- if (node.firstChild) {
- this._parseXml(node.firstChild, guiNode);
- }
- if (node.nextSibling) {
- this._parseXml(node.nextSibling, parent);
- }
- }
- private _prepareSourceElement(node: any, guiNode: any, variable: any, source: any, iterator: any): void {
- if (this._parentClass) {
- this._parentClass[variable] = source[iterator];
- } else {
- window[variable] = source[iterator];
- }
- if (node.firstChild) {
- this._parseXml(node.firstChild, guiNode, true);
- }
- }
- private _parseElementsFromSource(node: any, guiNode: any, parent: any): void {
- let dataSource = node.attributes.getNamedItem("dataSource").value;
- if (!dataSource.includes(" in ")) {
- throw "XmlLoader Exception : Malformed XML, Data Source must include an in";
- } else {
- let isArray = true;
- let splittedSource = dataSource.split(" in ");
- if (splittedSource.length < 2) {
- throw "XmlLoader Exception : Malformed XML, Data Source must an iterator and a source";
- }
- let source = splittedSource[1];
- if (source.startsWith("{") && source.endsWith("}")) {
- isArray = false;
- }
- if (!isArray || (source.startsWith("[") && source.endsWith("]"))) {
- source = source.substring(1, source.length - 1);
- }
- if (this._parentClass) {
- source = this._parentClass[source];
- } else {
- source = window[source];
- }
- if (isArray) {
- for (let i = 0; i < source.length; i++) {
- this._prepareSourceElement(node, guiNode, splittedSource[0], source, i);
- }
- } else {
- for (let i in source) {
- this._prepareSourceElement(node, guiNode, splittedSource[0], source, i);
- }
- }
- if (node.nextSibling) {
- this._parseXml(node.nextSibling, parent);
- }
- }
- }
- private _parseXml(node: any, parent: any, generated: boolean = false): void {
- if (node.nodeType != this._nodeTypes.element) {
- if (node.nextSibling) {
- this._parseXml(node.nextSibling, parent, generated);
- }
- return;
- }
- if (generated) {
- node.setAttribute("id", parent.id + (parent._children.length + 1));
- }
- let guiNode = this._createGuiElement(node, parent);
- if (node.nodeName == "Grid") {
- this._parseGrid(node, guiNode, parent);
- } else if (!node.attributes.getNamedItem("dataSource")) {
- this._parseElement(node, guiNode, parent);
- } else {
- this._parseElementsFromSource(node, guiNode, parent);
- }
- }
- /**
- * Gets if the loading has finished.
- * @returns whether the loading has finished or not
- */
- public isLoaded(): boolean {
- return this._isLoaded;
- }
- /**
- * Gets a loaded node / control by id.
- * @param id the Controls id set in the xml
- * @returns element of type Control
- */
- public getNodeById(id: string): any {
- return this._nodes[id];
- }
- /**
- * Gets all loaded nodes / controls
- * @returns Array of controls
- */
- public getNodes(): any {
- return this._nodes;
- }
- /**
- * Initiates the xml layout loading
- * @param xmlFile defines the xml layout to load
- * @param rootNode defines the node / control to use as a parent for the loaded layout controls.
- * @param callback defines the callback called on layout load.
- */
- public loadLayout(xmlFile: any, rootNode: any, callback: any): void {
- let xhttp = new XMLHttpRequest();
- xhttp.onreadystatechange = function(this: XmlLoader) {
- if (xhttp.readyState == 4 && xhttp.status == 200) {
- if (!xhttp.responseXML) {
- throw "XmlLoader Exception : XML file is malformed or corrupted.";
- }
- let xmlDoc = xhttp.responseXML.documentElement;
- this._parseXml(xmlDoc.firstChild, rootNode);
- this._isLoaded = true;
- if (callback) {
- callback();
- }
- }
- }.bind(this);
- xhttp.open("GET", xmlFile, true);
- xhttp.send();
- }
- }
|