import * as parseXml from '../parser/types' ; import { PropertyResolver } from './property-resolver' ; import { PropertyHandlerProvider , WidgetResolveResult } from '../providers/property-handler-provider' ; import { RootWidgetModel , ImportModel , WidgetModel , PropertyModel , ParamModel , VariableModel } from '../models/models' ; import { Config } from '../models/config' ; import { removeDuplicatedBuilders } from './util' ; export class WidgetResolver { constructor ( private readonly config , private readonly propertyHandlerProvider , private readonly propertyResolver ) { } resolve ( xmlDoc ) { const rootElement = xmlDoc . children [ 0 ] as parseXml . Element ; const rootElementAttrs = JSON . parse ( JSON . stringify ( rootElement . attributes ) ) ; const rootChild = this . getChildWidget ( rootElement ) ; let rootChildWidget = this . resolveWidget ( rootChild , null ) ; if ( rootChildWidget . isPropertyElement ) { const propertyHandler = this . propertyHandlerProvider . get ( rootChildWidget . propertyElement ) ; if ( propertyHandler && propertyHandler . canResolvePropertyElement ( ) ) { const childrenElements = rootElement . children . filter ( a => a . type === 'element' ) . map ( a => a as parseXml . Element ) ; const child = childrenElements . filter ( a => a . name === rootChildWidget . propertyElement ) [ 0 ] ; const widget = this . resolvePropertyElement ( rootChildWidget , rootElement , childrenElements , child ) ; if ( widget ) { rootChildWidget = { widget : widget , isPropertyElement : false , propertyElement : null , propertyElementProperties : [ ] } ; } } } removeDuplicatedBuilders ( rootChildWidget . widget , null , 'wrappedWidgets' , { } ) ; this . callOnResolved ( rootChildWidget . widget ) ; const mixins = this . resolveMixins ( rootElement ) ; const params = this . resolveParams ( rootElement ) ; const providers = this . resolveProviders ( rootElement ) ; const vars = this . resolveVars ( rootElement ) ; const imports = this . resolveImports ( rootElementAttrs ) ; const routeAware = 'routeAware' in rootElementAttrs ; if ( routeAware ) { const hasRouteAware = mixins . filter ( a => a === 'RouteAware' ) . length > 0 ; if ( ! hasRouteAware ) { mixins . push ( 'RouteAware' ) ; } } const rootWidget = { type : rootElement . name , stateful : 'stateful' in rootElementAttrs , controller : rootElementAttrs . controller , controllerPath : rootElementAttrs . controllerPath , rootChild : rootChildWidget . widget , params : params , mixins : mixins , providers : providers , vars : vars , imports : imports , routeAware : routeAware } ; return rootWidget ; } private callOnResolved ( widget ) { if ( ! widget ) { return ; } if ( widget . onResolved ) { widget . onResolved . forEach ( a => a && a ( widget ) ) ; widget . onResolved = null as any ; } if ( widget . wrappedWidgets ) { widget . wrappedWidgets . forEach ( w => { this . callOnResolved ( w ) ; } ) ; } widget . properties . forEach ( p => { let property = p ; if ( p . dataType === 'propertyElement' ) { property = p . value as any ; } if ( property . dataType === 'widget' ) { this . callOnResolved ( property . value as any ) ; } else if ( property . dataType === 'widgetList' ) { ( property . value as WidgetModel [ ] ) . forEach ( w => { this . callOnResolved ( w ) ; } ) ; } } ) ; } private getChildWidget ( rootElement ) { return rootElement . children . filter ( a => a . type === 'element' && [ 'var' , 'param' , 'provider' , 'with' ] . indexOf ( ( a as parseXml . Element ) . name ) === - 1 ) [ 0 ] as parseXml . Element ; } private resolveMixins ( rootElement ) { const nodes = rootElement . children . filter ( a => a . type === 'element' && ( a as parseXml . Element ) . name === 'with' ) as parseXml . Element [ ] ; const params = nodes . map ( n => { return n . attributes [ 'mixin' ] ; } ) . filter ( a => ! ! a ) ; return params ; } private resolveImports ( attrs ) { const imports = [ ] ; Object . keys ( attrs ) . filter ( k => k . startsWith ( 'xmlns' ) ) . forEach ( k => { const name = k . replace ( 'xmlns:' , '' ) . replace ( 'xmlns' , '' ) ; const path = attrs [ k ] ; imports . push ( { path : path } ) ; } ) ; return imports ; } private resolveParams ( rootElement ) { const nodes = rootElement . children . filter ( a => a . type === 'element' && ( a as parseXml . Element ) . name === 'param' ) as parseXml . Element [ ] ; const params = nodes . map ( n => { return { type : n . attributes [ 'type' ] || 'dynamic' , name : n . attributes [ 'name' ] , value : n . attributes [ 'value' ] , superParamName : n . attributes [ 'superParamName' ] , required : 'required' in n . attributes } ; } ) . filter ( a => ! ! a . name || ! ! a . superParamName ) ; return params ; } private resolveProviders ( rootElement ) { const nodes = rootElement . children . filter ( a => a . type === 'element' && ( a as parseXml . Element ) . name === 'provider' ) as parseXml . Element [ ] ; const params = nodes . map ( n => { return { type : n . attributes [ 'type' ] , name : n . attributes [ 'name' ] } ; } ) . filter ( a => ! ! a . name ) ; return params ; } private resolveVars ( rootElement ) { const nodes = rootElement . children . filter ( a => a . type === 'element' && ( a as parseXml . Element ) . name === 'var' ) as parseXml . Element [ ] ; const params = nodes . map ( n => { const value = n . attributes [ 'value' ] ; const type = n . attributes [ 'type' ] ; const name = n . attributes [ 'name' ] ; return { type : type ? type : ( ( value || '' ) . split ( '(' ) [ 0 ] ) , name : n . attributes [ 'name' ] , value : value } ; } ) . filter ( a => ! ! a . name && ! ! a . type && ! ! a . value ) ; return params ; } private resolveWidget ( element , parent ) { let widget = { type : element . name , controllers : [ ] , vars : [ ] , formControls : [ ] , properties : [ ] , wrappedWidgets : [ ] , onResolved : [ ] } ; const propertyElementResult = this . checkIfPropertyElement ( element ) ; widget . isPropertyElement = propertyElementResult . isPropertyElement ; const propertyElementProperties = [ ] ; widget . comments = element . children . filter ( a => a . type === 'comment' ) . map ( a => a as parseXml . Comment ) . map ( a => a . content . trim ( ) ) . filter ( a => ! ! a ) ; this . resolveChildren ( element , widget , parent ) ; this . resolveContentChildData ( element , widget ) ; const wrapperWidget = this . propertyResolver . resolveProperties ( element , widget ) ; widget . controllers = [ ... ( widget . controllers || [ ] ) , ... this . resolveControllers ( widget . properties ) ] ; if ( propertyElementResult . isPropertyElement ) { propertyElementProperties . push ( ... widget . properties . filter ( a => a . dataType !== 'widgetList' && a . dataType !== 'widget' ) ) ; const widgetProp = widget . properties . filter ( a => a . dataType === 'widgetList' || a . dataType === 'widget' ) [ 0 ] ; const contentWidget = ( widgetProp ? widgetProp . value as WidgetModel : null ) as any ; widget = contentWidget ; } if ( wrapperWidget ) { widget = wrapperWidget ; } const result = { widget : widget , isPropertyElement : propertyElementResult . isPropertyElement , propertyElement : propertyElementResult . name , propertyElementProperties : propertyElementProperties } ; return result ; } private resolveChildren ( element , widget , parent ) { const childrenWidgets = [ ] ; const childrenElements = element . children . filter ( a => a . type === 'element' ) . map ( a => a as parseXml . Element ) ; for ( const child of childrenElements ) { const childResult = this . resolveWidget ( child , element ) ; if ( ! childResult ) { continue ; } if ( childResult . isPropertyElement ) { const widget = this . resolvePropertyElement ( childResult , element , childrenElements , child ) ; if ( widget ) { childrenWidgets . push ( widget ) ; } } else { childrenWidgets . push ( childResult . widget ) ; } } const hasArrayAttribute = 'array' in element . attributes ; if ( childrenWidgets . length > 1 || hasArrayAttribute || this . hasChildrenProperty ( element . name , parent ? parent . name : '' ) ) { widget . properties . push ( { dataType : 'widgetList' , name : 'children' , value : childrenWidgets } ) ; } else if ( childrenWidgets . length === 1 ) { widget . properties . push ( { dataType : 'widget' , name : 'child' , value : childrenWidgets [ 0 ] } ) ; } } private resolvePropertyElement ( resolvedWidget , parentElement , parentChildrenElements , childElement ) { const propertyHandler = this . propertyHandlerProvider . get ( resolvedWidget . propertyElement ) ; if ( propertyHandler && propertyHandler . canResolvePropertyElement ( ) ) { const propertyElementWidget = propertyHandler . resolvePropertyElement ( childElement , resolvedWidget , parentElement , parentChildrenElements , ( el , parent ) => this . resolveWidget ( el , parent ) ) ; if ( propertyElementWidget ) { resolvedWidget . isPropertyElement = false ; return propertyElementWidget ; } } else { this . addElementAsAttribute ( parentElement , resolvedWidget ) ; } return null ; } private resolveContentChildData ( element , widget ) { const contentChild = element . children . filter ( a => a . type === 'text' && ( a as parseXml . Text ) . text . trimLeft ( ) . trimRight ( ) ) . map ( a => a as parseXml . Text ) [ 0 ] ; if ( contentChild ) { const value = contentChild . text ? contentChild . text . trimLeft ( ) . trimRight ( ) : '' ; if ( value && value !== '' ) { const result = this . propertyResolver . resolveProperty ( element , '' , value , widget , widget ) ; widget . properties . push ( result . property ) ; } } } private addElementAsAttribute ( element , childResult ) { const attrValue = { dataType : childResult . widget instanceof Array ? 'widgetList' : 'widget' , value : childResult . widget , extraData : { properties : childResult . propertyElementProperties } } ; if ( element . attributes [ childResult . propertyElement ] ) { if ( ! ( element . attributes [ childResult . propertyElement ] as any instanceof Array ) ) { element . attributes [ childResult . propertyElement ] = [ element . attributes [ childResult . propertyElement ] ] as any ; } ( element . attributes [ childResult . propertyElement ] as any ) . push ( attrValue ) ; } else { element . attributes [ childResult . propertyElement ] = attrValue as any ; } } private hasChildrenProperty ( name , parentName ) { const elementsHaveChildren = [ 'Column' , 'Row' , 'ListView' , 'GridView' , 'Stack' , 'Wrap' , ... this . getWidgetsWithChildrenProperty ( ) ] ; let result = ! ! elementsHaveChildren . find ( a => a === name ) ; if ( ! result ) { const arrayProperties = { 'DropdownButton' : [ 'items' ] , 'CustomScrollView' : [ 'slivers' ] } ; result = arrayProperties [ parentName ] && arrayProperties [ parentName ] . filter ( ( a ) => a === name ) . length === 1 || ! ! this . config . arrayProperties && this . config . arrayProperties [ parentName ] && this . config . arrayProperties [ parentName ] . filter ( a => a === name ) . length === 1 ; } return result ; } private getWidgetsWithChildrenProperty ( ) { const items = this . config . arrayProperties || { } ; return Object . keys ( items ) . filter ( k => { return items [ k ] . filter ( a => a === 'children' ) . length > 0 ; } ) ; } private resolveControllers ( properties ) { const controllers = properties . filter ( a => ! ! a . controller ) . map ( a => a . controller as any ) ; return controllers ; } private checkIfPropertyElement ( element ) { const dotIndex = element . name . indexOf ( '.' ) ; let childPropName = '' ; let isPropertyElement = false ; if ( dotIndex > - 1 ) { childPropName = element . name . substring ( dotIndex + 1 ) ; isPropertyElement = true ; } else if ( /[a-z]/ . test ( element . name [ 0 ] ) ) { childPropName = element . name ; isPropertyElement = true ; } return { name : childPropName , isPropertyElement } ; } }