2022-01-21 01:57:16 +01:00
import { Conversion , DesugaringContext , Fuse , OnEveryConcat , SetDefault } from "./Conversion" ;
import { LayerConfigJson } from "../Json/LayerConfigJson" ;
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" ;
import { Utils } from "../../../Utils" ;
2022-01-29 02:45:59 +01:00
import Translations from "../../../UI/i18n/Translations" ;
import { Translation } from "../../../UI/i18n/Translation" ;
2022-01-21 01:57:16 +01:00
class ExpandTagRendering extends Conversion < string | TagRenderingConfigJson | { builtin : string | string [ ] , override : any } , TagRenderingConfigJson [ ] > {
2022-02-04 01:05:35 +01:00
private readonly _state : DesugaringContext ;
constructor ( state : DesugaringContext ) {
2022-02-14 02:26:03 +01:00
super ( "Converts a tagRenderingSpec into the full tagRendering" , [ ] , "ExpandTagRendering" ) ;
2022-02-04 01:05:35 +01:00
this . _state = state ;
2022-01-21 01:57:16 +01:00
}
2022-02-04 01:05:35 +01:00
convert ( json : string | TagRenderingConfigJson | { builtin : string | string [ ] ; override : any } , context : string ) : { result : TagRenderingConfigJson [ ] ; errors : string [ ] ; warnings : string [ ] } {
2022-01-21 01:57:16 +01:00
const errors = [ ]
const warnings = [ ]
return {
2022-02-04 01:05:35 +01:00
result : this.convertUntilStable ( json , warnings , errors , context ) ,
2022-01-21 01:57:16 +01:00
errors , warnings
} ;
}
2022-02-04 01:05:35 +01:00
private lookup ( name : string ) : TagRenderingConfigJson [ ] {
const state = this . _state ;
2022-01-21 01:57:16 +01:00
if ( state . tagRenderings . has ( name ) ) {
return [ state . tagRenderings . get ( name ) ]
}
if ( name . indexOf ( "." ) >= 0 ) {
const spl = name . split ( "." ) ;
const layer = state . sharedLayers . get ( spl [ 0 ] )
if ( spl . length === 2 && layer !== undefined ) {
const id = spl [ 1 ] ;
const layerTrs = < TagRenderingConfigJson [ ] > layer . tagRenderings . filter ( tr = > tr [ "id" ] !== undefined )
let matchingTrs : TagRenderingConfigJson [ ]
if ( id === "*" ) {
matchingTrs = layerTrs
} else if ( id . startsWith ( "*" ) ) {
const id_ = id . substring ( 1 )
matchingTrs = layerTrs . filter ( tr = > tr . group === id_ )
} else {
matchingTrs = layerTrs . filter ( tr = > tr . id === id )
}
for ( let i = 0 ; i < matchingTrs . length ; i ++ ) {
// The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown
const found = Utils . Clone ( matchingTrs [ i ] ) ;
if ( found . condition === undefined ) {
found . condition = layer . source . osmTags
} else {
found . condition = { and : [ found . condition , layer . source . osmTags ] }
}
matchingTrs [ i ] = found
}
if ( matchingTrs . length !== 0 ) {
return matchingTrs
}
}
}
return undefined ;
}
2022-02-04 01:05:35 +01:00
private convertOnce ( tr : string | any , warnings : string [ ] , errors : string [ ] , ctx : string ) : TagRenderingConfigJson [ ] {
const state = this . _state
2022-01-21 01:57:16 +01:00
if ( tr === "questions" ) {
return [ {
id : "questions"
} ]
}
if ( typeof tr === "string" ) {
2022-02-04 01:05:35 +01:00
const lookup = this . lookup ( tr ) ;
2022-01-21 01:57:16 +01:00
if ( lookup !== undefined ) {
return lookup
}
warnings . push ( ctx + "A literal rendering was detected: " + tr )
return [ {
render : tr ,
id : tr.replace ( /![a-zA-Z0-9]/g , "" )
} ]
}
if ( tr [ "builtin" ] !== undefined ) {
let names = tr [ "builtin" ]
if ( typeof names === "string" ) {
names = [ names ]
}
for ( const key of Object . keys ( tr ) ) {
if ( key === "builtin" || key === "override" || key === "id" || key . startsWith ( "#" ) ) {
continue
}
errors . push ( "At " + ctx + ": an object calling a builtin can only have keys `builtin` or `override`, but a key with name `" + key + "` was found. This won't be picked up! The full object is: " + JSON . stringify ( tr ) )
}
const trs : TagRenderingConfigJson [ ] = [ ]
for ( const name of names ) {
2022-02-04 01:05:35 +01:00
const lookup = this . lookup ( name )
2022-01-21 01:57:16 +01:00
if ( lookup === undefined ) {
errors . push ( ctx + ": The tagRendering with identifier " + name + " was not found.\n\tDid you mean one of " + Array . from ( state . tagRenderings . keys ( ) ) . join ( ", " ) + "?" )
continue
}
for ( let foundTr of lookup ) {
foundTr = Utils . Clone < any > ( foundTr )
Utils . Merge ( tr [ "override" ] ? ? { } , foundTr )
trs . push ( foundTr )
}
}
return trs ;
}
return [ tr ]
}
2022-02-04 01:05:35 +01:00
private convertUntilStable ( spec : string | any , warnings : string [ ] , errors : string [ ] , ctx : string ) : TagRenderingConfigJson [ ] {
const trs = this . convertOnce ( spec , warnings , errors , ctx ) ;
2022-01-21 01:57:16 +01:00
const result = [ ]
for ( const tr of trs ) {
if ( tr [ "builtin" ] !== undefined ) {
2022-02-04 01:05:35 +01:00
const stable = this . convertUntilStable ( tr , warnings , errors , ctx + "(RECURSIVE RESOLVE)" )
2022-01-21 01:57:16 +01:00
result . push ( . . . stable )
} else {
result . push ( tr )
}
}
return result ;
}
}
class ExpandGroupRewrite extends Conversion < {
rewrite : {
sourceString : string ,
into : string [ ]
} [ ] ,
renderings : ( string | { builtin : string , override : any } | TagRenderingConfigJson ) [ ]
} | TagRenderingConfigJson , TagRenderingConfigJson [ ] > {
2022-02-04 01:05:35 +01:00
private _expandSubTagRenderings ;
2022-01-21 01:57:16 +01:00
2022-02-04 01:05:35 +01:00
constructor ( state : DesugaringContext ) {
2022-01-21 01:57:16 +01:00
super (
2022-02-14 02:26:03 +01:00
"Converts a rewrite config for tagRenderings into the expanded form" , [ ] ,
"ExpandGroupRewrite"
2022-01-21 01:57:16 +01:00
) ;
2022-02-04 01:05:35 +01:00
this . _expandSubTagRenderings = new ExpandTagRendering ( state )
2022-01-21 01:57:16 +01:00
}
2022-02-04 01:05:35 +01:00
convert ( json :
2022-01-21 01:57:16 +01:00
{
rewrite :
{ sourceString : string ; into : string [ ] } [ ] ; renderings : ( string | { builtin : string ; override : any } | TagRenderingConfigJson ) [ ]
2022-02-14 02:39:33 +01:00
} | TagRenderingConfigJson , context : string ) : { result : TagRenderingConfigJson [ ] ; errors : string [ ] ; warnings? : string [ ] } {
2022-01-21 01:57:16 +01:00
if ( json [ "rewrite" ] === undefined ) {
return { result : [ < TagRenderingConfigJson > json ] , errors : [ ] , warnings : [ ] }
}
let config = < {
rewrite :
2022-01-29 02:45:59 +01:00
{ sourceString : string [ ] ; into : ( string | any ) [ ] [ ] } ;
2022-01-21 01:57:16 +01:00
renderings : ( string | { builtin : string ; override : any } | TagRenderingConfigJson ) [ ]
} > json ;
2022-02-14 02:39:33 +01:00
2022-01-21 01:57:16 +01:00
2022-01-29 02:45:59 +01:00
{
const errors = [ ]
2022-02-14 02:39:33 +01:00
if ( ! Array . isArray ( config . rewrite . sourceString ) ) {
let extra = "" ;
if ( typeof config . rewrite . sourceString === "string" ) {
extra = ` <br/>Try <span class='literal-code'>"sourceString": [ " ${ config . rewrite . sourceString } " ] </span> instead (note the [ and ]) `
}
const msg = context + "<br/>Invalid format: a rewrite block is defined, but the 'sourceString' should be an array of strings, but it is a " + typeof config . rewrite . sourceString + extra
errors . push ( msg )
}
2022-01-29 02:45:59 +01:00
const expectedLength = config . rewrite . sourceString . length
for ( let i = 0 ; i < config . rewrite . into . length ; i ++ ) {
const targets = config . rewrite . into [ i ] ;
2022-02-14 02:39:33 +01:00
if ( ! Array . isArray ( targets ) ) {
errors . push ( ` ${ context } .rewrite.into[ ${ i } ] should be an array of values, but it is a ` + typeof targets )
} else if ( targets . length !== expectedLength ) {
errors . push ( ` ${ context } .rewrite.into[ ${ i } ]:<br/>The rewrite specified ${ config . rewrite . sourceString } as sourcestring, which consists of ${ expectedLength } values. The target ${ JSON . stringify ( targets ) } has ${ targets . length } items ` )
2022-01-29 02:45:59 +01:00
if ( typeof targets [ 0 ] !== "string" ) {
errors . push ( context + ".rewrite.into[" + i + "]: expected a string as first rewrite value values, but got " + targets [ 0 ] )
}
2022-02-14 02:39:33 +01:00
}
2022-01-29 02:45:59 +01:00
}
2022-01-21 01:57:16 +01:00
2022-01-29 02:45:59 +01:00
if ( errors . length > 0 ) {
return {
errors ,
warnings : [ ] ,
result : undefined
}
}
}
2022-02-04 01:05:35 +01:00
const subRenderingsRes = < { result : TagRenderingConfigJson [ ] [ ] , errors , warnings } > this . _expandSubTagRenderings . convertAll ( config . renderings , context ) ;
2022-01-29 02:45:59 +01:00
const subRenderings : TagRenderingConfigJson [ ] = [ ] . concat ( . . . subRenderingsRes . result ) ;
2022-01-21 01:57:16 +01:00
const errors = subRenderingsRes . errors ;
const warnings = subRenderingsRes . warnings ;
const rewrittenPerGroup = new Map < string , TagRenderingConfigJson [ ] > ( )
// The actual rewriting
2022-01-29 02:45:59 +01:00
const sourceStrings = config . rewrite . sourceString ;
for ( const targets of config . rewrite . into ) {
const groupName = targets [ 0 ] ;
if ( typeof groupName !== "string" ) {
throw "The first string of 'targets' should always be a string"
}
const trs : TagRenderingConfigJson [ ] = [ ]
for ( const tr of subRenderings ) {
let rewritten = tr ;
for ( let i = 0 ; i < sourceStrings . length ; i ++ ) {
const source = sourceStrings [ i ]
const target = targets [ i ] // This is a string OR a translation
rewritten = this . prepConfig ( source , target , rewritten )
2022-01-21 01:57:16 +01:00
}
2022-01-29 02:45:59 +01:00
rewritten . group = rewritten . group ? ? groupName
trs . push ( rewritten )
}
2022-01-21 01:57:16 +01:00
2022-01-29 02:45:59 +01:00
if ( rewrittenPerGroup . has ( groupName ) ) {
rewrittenPerGroup . get ( groupName ) . push ( . . . trs )
} else {
rewrittenPerGroup . set ( groupName , trs )
2022-01-21 01:57:16 +01:00
}
}
// Add questions box for this category
rewrittenPerGroup . forEach ( ( group , groupName ) = > {
group . push ( < TagRenderingConfigJson > {
id : "questions" ,
group : groupName
} )
} )
rewrittenPerGroup . forEach ( ( group , _ ) = > {
group . forEach ( tr = > {
if ( tr . id === undefined || tr . id === "" ) {
2022-01-29 02:45:59 +01:00
errors . push ( "A tagrendering has an empty ID after expanding the tag; the tagrendering is: " + JSON . stringify ( tr ) )
2022-01-21 01:57:16 +01:00
}
} )
} )
return {
result : [ ] . concat ( . . . Array . from ( rewrittenPerGroup . values ( ) ) ) ,
errors , warnings
} ;
}
2022-01-29 02:45:59 +01:00
/ * U s e d f o r l e f t | r i g h t g r o u p c r e a t i o n a n d r e p l a c e m e n t .
* Every 'keyToRewrite' will be replaced with 'target' recursively . This substitution will happen in place in the object 'tr' * /
private prepConfig ( keyToRewrite : string , target : string | any , tr : TagRenderingConfigJson ) : TagRenderingConfigJson {
const isTranslation = typeof target !== "string"
2022-01-21 01:57:16 +01:00
function replaceRecursive ( transl : string | any ) {
if ( typeof transl === "string" ) {
2022-01-29 02:45:59 +01:00
// This is a simple string - we do a simple replace
2022-01-21 01:57:16 +01:00
return transl . replace ( keyToRewrite , target )
}
if ( transl . map !== undefined ) {
2022-01-29 02:45:59 +01:00
// This is a list of items
2022-01-21 01:57:16 +01:00
return transl . map ( o = > replaceRecursive ( o ) )
}
2022-01-29 02:45:59 +01:00
if ( Translations . isProbablyATranslation ( transl ) && isTranslation ) {
return Translations . T ( transl ) . Fuse ( new Translation ( target ) , keyToRewrite ) . translations
}
2022-01-21 01:57:16 +01:00
transl = { . . . transl }
for ( const key in transl ) {
transl [ key ] = replaceRecursive ( transl [ key ] )
}
return transl
}
2022-01-29 02:45:59 +01:00
return replaceRecursive ( tr )
2022-01-21 01:57:16 +01:00
}
}
export class PrepareLayer extends Fuse < LayerConfigJson > {
2022-02-04 01:05:35 +01:00
constructor ( state : DesugaringContext ) {
2022-01-21 01:57:16 +01:00
super (
"Fully prepares and expands a layer for the LayerConfig." ,
2022-02-04 01:05:35 +01:00
new OnEveryConcat ( "tagRenderings" , new ExpandGroupRewrite ( state ) ) ,
new OnEveryConcat ( "tagRenderings" , new ExpandTagRendering ( state ) ) ,
2022-01-21 01:57:16 +01:00
new SetDefault ( "titleIcons" , [ "defaults" ] ) ,
2022-02-04 01:05:35 +01:00
new OnEveryConcat ( "titleIcons" , new ExpandTagRendering ( state ) )
2022-01-21 01:57:16 +01:00
) ;
}
}