import { scan } from "../parser/parser" ; import * as parseXml from '../parser/types' ; import * as Syntax from '../parser/syntax' ; import { WidgetModel , ExtraDataModel } from "../models/models" ; import { makeVariableName , makePipeUniqueName } from "../utils" ; export class PipeValueResolver { resolve ( element , name , value , widget , addReturn = true ) { let wrapperWidget = null ; let extraData = null ; value = this . prepareValue ( value ) ; const pipesResult = this . resolvePipes ( value ) ; const groupedPipesValues = { } ; if ( pipesResult . groupedPipes ) { pipesResult . groupedPipes . forEach ( groupedPipe => { ( { value , wrapperWidget } = this . applyPipes ( value , groupedPipe , wrapperWidget , widget , name , addReturn ) ) ; const pipesNames = makePipeUniqueName ( groupedPipe ) ; groupedPipesValues [ pipesNames ] = value ; } ) ; } ( { value , wrapperWidget } = this . applyPipes ( value , pipesResult , wrapperWidget , widget , name , addReturn ) ) ; if ( pipesResult . groupedPipes ) { pipesResult . groupedPipes . forEach ( groupedPipe => { const pipesNames = makePipeUniqueName ( groupedPipe ) ; const placeholder = '_placeholder_for_' + pipesNames + '_pipes' ; value = value . replace ( placeholder , groupedPipesValues [ pipesNames ] ) ; if ( wrapperWidget ) { wrapperWidget . properties . filter ( p => typeof p . value === 'string' ) . forEach ( prop => { prop . value = ( prop . value as String ) . replace ( placeholder , groupedPipesValues [ pipesNames ] ) ; } ) ; } } ) ; } return { wrapperWidget , extraData , value } ; } private isBoundValue ( value ) { return ! ! value && typeof value === 'string' && value . startsWith ( '{{' ) && value . endsWith ( '}}' ) ; } private prepareValue ( value ) { if ( this . isBoundValue ( value ) ) { return value . substring ( 2 , value . length - 2 ) ; } return value ; } createStreamBuilder ( value , initialValue , resultVarName , widget , propertyName , addReturn , addNullChecking = true , addLocalVar = true ) { return this . createCustomBuilder ( 'StreamBuilder' , 'stream' , value , initialValue , resultVarName , widget , propertyName , addReturn , addNullChecking , addLocalVar ) ; } createStreamBuilderWithInitialValue ( value , resultVarName , widget , propertyName , addReturn , addNullChecking = true , addLocalVar = true ) { return this . createCustomBuilder ( 'StreamBuilder' , 'stream' , value , `${ value }.value` , resultVarName , widget , propertyName , addReturn , addNullChecking , addLocalVar ) ; } createFutureBuilder ( value , initialValue , resultVarName , widget , propertyName , addReturn , addNullChecking = true , addLocalVar = true ) { return this . createCustomBuilder ( 'FutureBuilder' , 'future' , value , initialValue , resultVarName , widget , propertyName , addReturn , addNullChecking , addLocalVar ) ; } createCustomBuilder ( builderWidgetName , builderPropertyName , value , initialValue , resultVarName , widget , propertyName , addReturn , addNullChecking = true , addLocalVar = true ) { const parameterNamePrefix = makeVariableName ( propertyName , value ) ; const snapshotVarName = parameterNamePrefix + 'Snapshot' ; resultVarName = resultVarName || `${ parameterNamePrefix }Value` ; let streamBuilderWidget = { controllers : [ ] , vars : [ ] , formControls : [ ] , properties : [ { dataType : 'object' , value : `${ value }` , name : builderPropertyName } , { dataType : 'object' , value : `${ initialValue ? initialValue : 'null' }` , name : 'initialData' } , { dataType : 'function' , name : 'builder' , value : '' , extraData : { parameters : [ { name : 'context' , type : 'BuildContext' } , { name : `${ snapshotVarName }` , type : '' } ] , logic : [ ... ( addLocalVar ? [ `final ${ resultVarName } = ${ snapshotVarName }.data;` ] : [ ] ) , ... ( addNullChecking ? [ `if (${ resultVarName } == null) {` , '  return Container(width: 0, height: 0);' , '}' ] : [ ] ) ] , addReturn : addReturn } } ] , type : builderWidgetName , wrappedWidgets : [ widget ] , id : Math . random ( ) * 1000 , onResolved : [ ] , isCustom : true } ; value = `${ resultVarName }` ; return { wrapperWidget : streamBuilderWidget , value } ; } private applyPipes ( value , pipesResult , wrapperWidget , widget , name , addReturn ) { value = pipesResult . value ; let remainingPipes = pipesResult . pipes ; const streamPipIndex = pipesResult . pipes . findIndex ( a => a . name === 'stream' ) ; const futurePipIndex = pipesResult . pipes . findIndex ( a => a . name === 'future' ) ; const streamWithInitialValuePipIndex = pipesResult . pipes . findIndex ( a => a . name === 'behavior' ) ; const hasStream = streamPipIndex > - 1 ; const hasFuture = futurePipIndex > - 1 ; const hasStreamWithInitialValue = streamWithInitialValuePipIndex > - 1 ; if ( hasStream ) { const streamPipe = pipesResult . pipes [ streamPipIndex ] ; const pipeValue = this . resolvePipeValue ( pipesResult . value , pipesResult . pipes . slice ( 0 , streamPipIndex ) . reverse ( ) ) ; ( { wrapperWidget , value } = this . createStreamBuilder ( pipeValue , streamPipe . args [ 0 ] , streamPipe . args [ 1 ] , wrapperWidget || widget , name , addReturn , true , true ) ) ; remainingPipes = pipesResult . pipes . slice ( streamPipIndex + 1 ) ; } else if ( hasStreamWithInitialValue ) { const streamWithInitialValuePipe = pipesResult . pipes [ streamWithInitialValuePipIndex ] ; const pipeValue = this . resolvePipeValue ( pipesResult . value , pipesResult . pipes . slice ( 0 , streamWithInitialValuePipIndex ) . reverse ( ) ) ; ( { wrapperWidget , value } = this . createStreamBuilderWithInitialValue ( pipeValue , streamWithInitialValuePipe . args [ 0 ] , wrapperWidget || widget , name , addReturn , true , true ) ) ; remainingPipes = pipesResult . pipes . slice ( streamWithInitialValuePipIndex + 1 ) ; } else if ( hasFuture ) { const futurePipe = pipesResult . pipes [ futurePipIndex ] ; const pipeValue = this . resolvePipeValue ( pipesResult . value , pipesResult . pipes . slice ( 0 , futurePipIndex ) . reverse ( ) ) ; ( { wrapperWidget , value } = this . createFutureBuilder ( pipeValue , futurePipe . args [ 0 ] , futurePipe . args [ 1 ] , wrapperWidget || widget , name , addReturn , true , true ) ) ; remainingPipes = pipesResult . pipes . slice ( futurePipIndex + 1 ) ; } value = this . resolvePipeValue ( value , remainingPipes . reverse ( ) ) ; return { value , wrapperWidget } ; } private resolvePipeValue ( value , pipes , index = 0 ) { if ( index >= pipes . length ) { return value . trim ( ) ; } const pipe = pipes [ index ] ; value = ( pipes . length > index + 1 ? this . resolvePipeValue ( value , pipes , index + 1 ) : value ) . trim ( ) ; return `_pipeProvider.transform(context, "${ pipe . name }", ${ value }, [${ pipe . args . join ( ', ' ) }])` ; } private resolvePipes ( value ) { const groupedPipes = [ ] ; const { groups , output } = this . resolveGroupedPipes ( value ) ; const result = this . extractPipes ( output ) ; return { ... result , groupedPipes : groups } ; } private resolveGroupedPipes ( input ) { const stack = [ ] ; const groupedPipes = [ ] ; let quotedWith = '' ; let hasInterpolation = false ; const resolvePipes = ( start , index , groupStart , groupEnd ) : number => { const group = input . substring ( start . index + 1 , index ) . trim ( ) ; if ( group ) { const result = this . extractPipes ( group ) ; if ( result . pipes . length ) { groupedPipes . push ( result ) ; const pipesNames = makePipeUniqueName ( result ) ; const placeholder = groupStart + '_placeholder_for_' + pipesNames + '_pipes' + groupEnd ; const originalLength = input . length ; input = this . replaceAt ( input , placeholder , start . index , index ) ; index -= originalLength - input . length ; } } return index ; } ; for ( let index = 0 ; index < input . length ; index ++ ) { const c = input [ index ] ; if ( c === '$' && input . length > index && input [ index + 1 ] === '{' ) { stack . push ( { exp : c , index } ) ; hasInterpolation = true ; } else if ( c === '}' ) { hasInterpolation = false ; const start = stack . pop ( ) ; if ( start ) { index = resolvePipes ( start , index , '${' , '}' ) ; } } else if ( quotedWith && ! hasInterpolation ) { if ( quotedWith === c ) { quotedWith = '' ; } } else if ( c === '"' || c === "'" ) { quotedWith = c ; } else if ( c === '(' ) { stack . push ( { exp : c , index } ) ; } else if ( c === ')' ) { const start = stack . pop ( ) ; if ( start ) { index = resolvePipes ( start , index , '(' , ')' ) ; } } } return { groups : groupedPipes , output : input } ; } private replaceAt ( input , replace , startsAt , endsAt = - 1 ) { const first = input . substring ( 0 , startsAt ) ; const second = input . substring ( ( endsAt === - 1 ? startsAt : endsAt ) + 1 ) ; return first + replace + second ; } private extractPipes ( value ) { const pipesReduced = value . split ( /([^"'\|]+)|("[^"]*")|('[^']*')/g ) . filter ( a => a && a . trim ( ) ) . reduce ( ( arr , item ) => { if ( item === '|' ) { arr . push ( [ true ] ) ; } else { arr . length > 1 ? arr [ arr . length - 1 ] . push ( item ) : arr . push ( [ false , item ] ) ; } return arr ; } , [ ] ) ; const hasPipes = pipesReduced . filter ( a => a [ 0 ] ) . length > 0 ; if ( hasPipes ) { value = pipesReduced . filter ( a => ! a [ 0 ] ) . map ( a => a . slice ( 1 ) . join ( '' ) ) . join ( '' ) ; } const pipes = pipesReduced . filter ( a => a [ 0 ] ) . map ( a => { const pipeText = a . slice ( 1 ) . join ( '' ) ; const args = this . resolvePipeArgs ( pipeText ) . map ( a => a . trim ( ) ) ; return { name : args [ 0 ] , args : args . slice ( 1 ) } ; } ) ; return { value , pipes : hasPipes ? pipes : [ ] } ; } private resolvePipeArgs ( args ) { const matches = scan ( { xml : args , pos : 0 } , Syntax . Global . PipeArgs ) ; return matches ; } }