import { GetClass } from 'babylonjs/Misc/typeStore' ; import { Nullable } from 'babylonjs/types' ; /**
* 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 _rootNode : any ; private _parentClass : any ; param parentClass constructor ( parentClass = null ) { if ( parentClass ) { this . _parentClass = parentClass ; } } private _getChainElement ( attributeValue ) { let element = 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 ) { const attribute = attributeName . split ( "." ) ; const className = GetClass ( "BABYLON.GUI." + attribute [ 0 ] ) ; return className [ attribute [ 1 ] ] ; } private _createGuiElement ( node , parent , linkParent = true ) { try { let className = 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 ( exception ) { throw "XmlLoader Exception : Error parsing Control " + node . nodeName + "," + exception + "." ; } } private _parseGrid ( node , guiNode , parent ) { 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 , guiNode , parent ) { if ( node . firstChild ) { this . _parseXml ( node . firstChild , guiNode ) ; } if ( node . nextSibling ) { this . _parseXml ( node . nextSibling , parent ) ; } } private _prepareSourceElement ( node , guiNode , variable , source , iterator ) { 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 , guiNode , parent ) { 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 have 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 , parent , generated = false ) { 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 ( ! this . _rootNode ) { this . _rootNode = guiNode ; } 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 ) ; } } returns public isLoaded ( ) { return this . _isLoaded ; } param id returns public getNodeById ( id ) { return this . _nodes [ id ] ; } returns public getNodes ( ) { return this . _nodes ; } /**
     * Disposes the loaded layout
    */ public dispose ( ) { if ( this . _rootNode ) { this . _rootNode . dispose ( ) ; this . _rootNode = null ; this . _nodes = { } ; } } param xmlFile param rootNode param onSuccess param onError public loadLayout ( xmlFile , rootNode , onSuccess = null , onError = null ) { let xhttp = new XMLHttpRequest ( ) ; xhttp . onload = ( ) => { 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 ( onSuccess ) { onSuccess ( ) ; } } } ; xhttp . onerror = function ( ) { if ( onError ) { onError ( "an error occurred during loading the layout" ) ; } } ; xhttp . open ( "GET" , xmlFile , true ) ; xhttp . send ( ) ; } param xmlFile param rootNode returns public async loadLayoutAsync ( xmlFile , rootNode ) { return new Promise ( ( resolve , reject ) => { this . loadLayout ( xmlFile , rootNode , resolve , reject ) ; } ) ; } }