2024-01-25 13:41:33 +01:00
import { RenderingSpecification , SpecialVisualization } from "./SpecialVisualization"
export default class SpecialVisualisationUtils {
/ * *
*
* For a given string , returns a specification what parts are fixed and what parts are special renderings .
* Note that _normal_ substitutions are ignored .
*
* import SpecialVisualisations from "./SpecialVisualizations"
*
* // Return empty list on empty input
2024-08-13 17:56:33 +02:00
* SpecialVisualisationUtils . constructSpecification ( "" , SpecialVisualisations . specialVisualisationsDict ) // => []
2024-01-25 13:41:33 +01:00
*
* // Simple case
2024-08-13 17:56:33 +02:00
* const oh = SpecialVisualisationUtils . constructSpecification ( "The opening hours with value {opening_hours} can be seen in the following table: <br/> {opening_hours_table()}" , SpecialVisualisations . specialVisualisationsDict )
2024-01-25 13:41:33 +01:00
* oh [ 0 ] // => "The opening hours with value {opening_hours} can be seen in the following table: <br/> "
* oh [ 1 ] . func . funcName // => "opening_hours_table"
*
* // Advanced cases with commas, braces and newlines should be handled without problem
2024-08-13 17:56:33 +02:00
* const templates = SpecialVisualisationUtils . constructSpecification ( "{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.org/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}" , SpecialVisualisations . specialVisualisationsDict )
2024-01-25 13:41:33 +01:00
* const templ = < Exclude < RenderingSpecification , string > > templates [ 0 ]
* templ . func . funcName // => "send_email"
* templ . args [ 0 ] = "{email}"
2024-08-16 01:54:14 +02:00
*
* // Regression test - multiple special functions should all be found
* const spec = "{create_review()}{list_reviews()}"
* const parsed = SpecialVisualisationUtils . constructSpecification ( spec , SpecialVisualisations . specialVisualisationsDict )
* parsed [ 0 ] . func . funcName // => "create_review"
* parsed [ 1 ] . func . funcName // => "list_reviews"
2024-01-25 13:41:33 +01:00
* /
public static constructSpecification (
template : string ,
2024-08-13 17:56:33 +02:00
specialVisualisations : Map < string , SpecialVisualization > ,
2024-08-14 13:53:56 +02:00
extraMappings : SpecialVisualization [ ] = [ ]
2024-01-25 13:41:33 +01:00
) : RenderingSpecification [ ] {
if ( template === "" ) {
return [ ]
}
if ( template [ "type" ] !== undefined ) {
console . trace (
"Got a non-expanded template while constructing the specification, it still has a 'special-key':" ,
2024-08-14 13:53:56 +02:00
template
2024-01-25 13:41:33 +01:00
)
throw "Got a non-expanded template while constructing the specification"
}
2024-08-13 17:56:33 +02:00
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
2024-08-23 13:13:41 +02:00
const matched = template . match (
new RegExp ( ` (.*?){ \ ([a-zA-Z_]+ \ ) \\ ((.*?) \\ )(:.*)?}(.*) ` , "s" )
)
2024-08-13 17:56:33 +02:00
if ( matched === null ) {
// IF we end up here, no changes have to be made - except to remove any resting {}
return [ template ]
}
const fName = matched [ 2 ]
let knownSpecial = specialVisualisations . get ( fName )
2024-08-14 13:53:56 +02:00
if ( ! knownSpecial && extraMappings ? . length > 0 ) {
knownSpecial = extraMappings . find ( ( em ) = > em . funcName === fName )
2024-08-13 17:56:33 +02:00
}
2024-08-14 13:53:56 +02:00
if ( ! knownSpecial ) {
throw "Didn't find a special visualisation: " + fName + " in " + template
2024-08-13 17:56:33 +02:00
}
// Always a boring string
const partBefore : string = matched [ 1 ]
const argument : string =
matched [ 3 ] /* .trim() // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/
const style : string = matched [ 4 ] ? . substring ( 1 ) ? ? ""
2024-08-14 13:53:56 +02:00
const partAfter : RenderingSpecification [ ] =
SpecialVisualisationUtils . constructSpecification (
matched [ 5 ] ,
specialVisualisations ,
extraMappings
)
2024-01-25 13:41:33 +01:00
2024-08-13 17:56:33 +02:00
const args : string [ ] = knownSpecial . args . map ( ( arg ) = > arg . defaultValue ? ? "" )
if ( argument . length > 0 ) {
const realArgs = argument
. split ( "," )
. map ( ( str ) = > SpecialVisualisationUtils . undoEncoding ( str ) )
for ( let i = 0 ; i < realArgs . length ; i ++ ) {
if ( args . length <= i ) {
args . push ( realArgs [ i ] )
} else {
args [ i ] = realArgs [ i ]
2024-01-25 13:41:33 +01:00
}
}
}
2024-08-13 17:56:33 +02:00
const element : RenderingSpecification = {
args ,
style ,
func : knownSpecial ,
}
partAfter . unshift ( element )
2024-08-14 13:53:56 +02:00
if ( partBefore . length > 0 ) {
2024-08-13 17:56:33 +02:00
partAfter . unshift ( partBefore )
}
return partAfter
2024-01-25 13:41:33 +01:00
}
private static undoEncoding ( str : string ) {
return str
. trim ( )
. replace ( /&LPARENS/g , "(" )
. replace ( /&RPARENS/g , ")" )
. replace ( /&LBRACE/g , "{" )
. replace ( /&RBRACE/g , "}" )
. replace ( /&COMMA/g , "," )
}
}