forked from MapComplete/MapComplete
		
	Add more checks on parsing from JSON, fix of those issues on the builtin themes
This commit is contained in:
		
							parent
							
								
									8da0893c05
								
							
						
					
					
						commit
						97ec893479
					
				
					 9 changed files with 117 additions and 29 deletions
				
			
		|  | @ -108,6 +108,7 @@ export class FromJSON { | |||
|             throw `Tagrendering ${propertyName} is undefined...` | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         if (typeof json === "string") { | ||||
| 
 | ||||
|             switch (json) { | ||||
|  | @ -137,6 +138,8 @@ export class FromJSON { | |||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         // It's the question that drives us, neo
 | ||||
|         const question =  FromJSON.Translation(json.question); | ||||
| 
 | ||||
|         let template = FromJSON.Translation(json.render); | ||||
| 
 | ||||
|  | @ -165,28 +168,35 @@ export class FromJSON { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const mappings = json.mappings?.map(mapping => ( | ||||
|             { | ||||
|                 k: FromJSON.Tag(mapping.if), | ||||
|                 txt: FromJSON.Translation(mapping.then), | ||||
|                 hideInAnswer: mapping.hideInAnswer | ||||
|             }) | ||||
|         const mappings = json.mappings?.map((mapping, i) => { | ||||
|                 const k = FromJSON.Tag(mapping.if, `IN mapping #${i} of tagrendering ${propertyName}`) | ||||
| 
 | ||||
|                 if (question !== undefined && !mapping.hideInAnswer && !k.isUsableAsAnswer()) { | ||||
|                     throw `Invalid mapping in ${propertyName}: the tags use an OR-expression or regex expression but are also assignable as answer.` | ||||
|                 } | ||||
| 
 | ||||
|                 return { | ||||
|                     k: k, | ||||
|                     txt: FromJSON.Translation(mapping.then), | ||||
|                     hideInAnswer: mapping.hideInAnswer | ||||
|                 }; | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         if(template === undefined && (mappings === undefined || mappings.length === 0)){ | ||||
|             console.error("Empty tagrendering detected: no mappings nor template given", json) | ||||
|         if (template === undefined && (mappings === undefined || mappings.length === 0)) { | ||||
|             console.error(`Empty tagrendering detected in ${propertyName}: no mappings nor template given`, json) | ||||
|             throw `Empty tagrendering ${propertyName} detected: no mappings nor template given` | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         let rendering = new TagRenderingOptions({ | ||||
|             question: FromJSON.Translation(json.question), | ||||
|             question: question, | ||||
|             freeform: freeform, | ||||
|             mappings: mappings | ||||
|         }); | ||||
| 
 | ||||
|         if (json.condition) { | ||||
|             return rendering.OnlyShowIf(FromJSON.Tag(json.condition)); | ||||
|             return rendering.OnlyShowIf(FromJSON.Tag(json.condition, `In tagrendering ${propertyName}.condition`)); | ||||
|         } | ||||
| 
 | ||||
|         return rendering; | ||||
|  | @ -197,7 +207,7 @@ export class FromJSON { | |||
|         return new Tag(tag[0], tag[1]); | ||||
|     } | ||||
| 
 | ||||
|     public static Tag(json: AndOrTagConfigJson | string): TagsFilter { | ||||
|     public static Tag(json: AndOrTagConfigJson | string, context: string): TagsFilter { | ||||
|         if(json === undefined){ | ||||
|             throw "Error while parsing a tag: nothing defined. Make sure all the tags are defined and at least one tag is present in a complex expression" | ||||
|         } | ||||
|  | @ -206,7 +216,7 @@ export class FromJSON { | |||
|             if (tag.indexOf("!~") >= 0) { | ||||
|                 const split = Utils.SplitFirst(tag, "!~"); | ||||
|                 if (split[1] === "*") { | ||||
|                     split[1] = "..*" | ||||
|                     throw `Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${tag} while parsing ${context})` | ||||
|                 } | ||||
|                 return new RegexTag( | ||||
|                     split[0], | ||||
|  | @ -214,6 +224,16 @@ export class FromJSON { | |||
|                     true | ||||
|                 ); | ||||
|             } | ||||
|             if (tag.indexOf("~~") >= 0) { | ||||
|                 const split = Utils.SplitFirst(tag, "~~"); | ||||
|                 if (split[1] === "*") { | ||||
|                     split[1] = "..*" | ||||
|                 } | ||||
|                 return new RegexTag( | ||||
|                         new RegExp("^" + split[0] + "$"), | ||||
|                     new RegExp("^" + split[1] + "$") | ||||
|                 ); | ||||
|             } | ||||
|             if (tag.indexOf("!=") >= 0) { | ||||
|                 const split = Utils.SplitFirst(tag, "!="); | ||||
|                 if (split[1] === "*") { | ||||
|  | @ -236,13 +256,16 @@ export class FromJSON { | |||
|                 ); | ||||
|             } | ||||
|             const split = Utils.SplitFirst(tag, "="); | ||||
|             if(split[1] == "*"){ | ||||
|                 throw `Error while parsing tag '${tag}' in ${context}: detected a wildcard on a normal value. Use a regex pattern instead` | ||||
|             } | ||||
|             return new Tag(split[0], split[1]) | ||||
|         } | ||||
|         if (json.and !== undefined) { | ||||
|             return new And(json.and.map(FromJSON.Tag)); | ||||
|             return new And(json.and.map(t => FromJSON.Tag(t, context))); | ||||
|         } | ||||
|         if (json.or !== undefined) { | ||||
|             return new Or(json.or.map(FromJSON.Tag)); | ||||
|             return new Or(json.or.map(t => FromJSON.Tag(t, context))); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -265,7 +288,7 @@ export class FromJSON { | |||
| 
 | ||||
|         console.log("Parsing layer", json) | ||||
|         const tr = FromJSON.Translation; | ||||
|         const overpassTags = FromJSON.Tag(json.overpassTags); | ||||
|         const overpassTags = FromJSON.Tag(json.overpassTags, "overpasstags for layer "+json.id); | ||||
|         const icon = FromJSON.TagRenderingWithDefault(json.icon, "icon", "./assets/bug.svg"); | ||||
|         const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center"); | ||||
|         const color = FromJSON.TagRenderingWithDefault(json.color, "color", "#0000ff"); | ||||
|  |  | |||
|  | @ -7,14 +7,23 @@ import BikeCafes from "../Layers/BikeCafes"; | |||
| 
 | ||||
| 
 | ||||
| export default class Cyclofix extends Layout { | ||||
| 
 | ||||
|     private static RememberTheDead(): boolean { | ||||
|         const now = new Date(); | ||||
|         const m = now.getMonth() + 1; | ||||
|         const day = new Date().getDay() + 1; | ||||
|         const date = day + "/" + m; | ||||
|         return (date === "31/10" || date === "1/11" || date === "2/11"); | ||||
|     } | ||||
| 
 | ||||
|     constructor() { | ||||
|         super( | ||||
|             "cyclofix", | ||||
|             ["en", "nl", "fr","gl"], | ||||
|             ["en", "nl", "fr", "gl"], | ||||
|             Translations.t.cyclofix.title, | ||||
|             ["bike_repair_station", new BikeShops(), "drinking_water", "bike_parking", new BikeOtherShops(), new BikeCafes(), | ||||
|                 // The first of november, we remember our dead
 | ||||
|                 ...(new Date().getMonth() + 1 == 11 && new Date().getDay() + 1 == 1 ? ["ghost_bike"] : [])], | ||||
|                 ...(Cyclofix.RememberTheDead() ? ["ghost_bike"] : [])], | ||||
|             16, | ||||
|             50.8465573, | ||||
|             4.3516970, | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ export abstract class TagsFilter { | |||
|     abstract matches(tags: { k: string, v: string }[]): boolean | ||||
|     abstract asOverpass(): string[] | ||||
|     abstract substituteValues(tags: any) : TagsFilter; | ||||
|     abstract isUsableAsAnswer() : boolean; | ||||
| 
 | ||||
|     matchesProperties(properties: Map<string, string>): boolean { | ||||
|         return this.matches(TagUtils.proprtiesToKV(properties)); | ||||
|  | @ -43,6 +44,10 @@ export class RegexTag extends TagsFilter { | |||
|         return r.source; | ||||
|     } | ||||
|      | ||||
|     isUsableAsAnswer(): boolean { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     matches(tags: { k: string; v: string }[]): boolean { | ||||
|         for (const tag of tags) { | ||||
|             if (RegexTag.doesMatch(tag.k, this.key)){ | ||||
|  | @ -58,7 +63,10 @@ export class RegexTag extends TagsFilter { | |||
|     } | ||||
|      | ||||
|     asHumanString() { | ||||
|         return `${RegexTag.source(this.key)}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`; | ||||
|         if (typeof this.key === "string") { | ||||
|             return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`; | ||||
|         } | ||||
|         return `~${this.key.source}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -78,7 +86,7 @@ export class Tag extends TagsFilter { | |||
|             throw "Invalid value"; | ||||
|         } | ||||
|         if(value === "*"){ | ||||
|          console.warn(`Got suspicious tag ${key}=*   ; did you mean ${key}!~*`) | ||||
|          console.warn(`Got suspicious tag ${key}=*   ; did you mean ${key}~* ?`) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -128,6 +136,10 @@ export class Tag extends TagsFilter { | |||
|         } | ||||
|         return this.key + "=" + v; | ||||
|     } | ||||
|      | ||||
|     isUsableAsAnswer(): boolean { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -171,6 +183,10 @@ export class Or extends TagsFilter { | |||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|"); | ||||
|     } | ||||
|      | ||||
|     isUsableAsAnswer(): boolean { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -231,6 +247,15 @@ export class And extends TagsFilter { | |||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&"); | ||||
|     } | ||||
|      | ||||
|     isUsableAsAnswer(): boolean { | ||||
|         for (const t of this.and) { | ||||
|             if(!t.isUsableAsAnswer()){ | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -261,4 +286,5 @@ export class TagUtils { | |||
|         } | ||||
|         return properties; | ||||
|     } | ||||
|      | ||||
| } | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ export default class SharePanel extends UIElement { | |||
|         const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`) | ||||
| 
 | ||||
|         this._panel = new Combine([ | ||||
|             "<h2>share</h2>", | ||||
|             "<h2>Share</h2>", | ||||
|             "Share the following link with friends:<br/>", | ||||
|             new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)), | ||||
|             "<h2>Publish on OSM Wiki</h2>", | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson"; | |||
| import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson"; | ||||
| import {UserDetails} from "../../Logic/Osm/OsmConnection"; | ||||
| import {State} from "../../State"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {FromJSON} from "../../Customizations/JSON/FromJSON"; | ||||
| 
 | ||||
| export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> { | ||||
| 
 | ||||
|  | @ -24,6 +26,8 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
|     private readonly _value: UIEventSource<TagRenderingConfigJson>; | ||||
|     public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; }; | ||||
| 
 | ||||
|     public readonly validText : UIElement; | ||||
|      | ||||
|     constructor(languages: UIEventSource<string[]>, | ||||
|                 currentlySelected: UIEventSource<SingleSetting<any>>, | ||||
|                 userDetails: UserDetails, | ||||
|  | @ -95,12 +99,24 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
|         ]; | ||||
| 
 | ||||
|         this.settingsTable = new SettingsTable(settings, currentlySelected); | ||||
|      | ||||
|          | ||||
|         this.validText = new VariableUiElement(value.map((json: TagRenderingConfigJson) => { | ||||
|             try{ | ||||
|                 FromJSON.TagRendering(json, options?.title ?? ""); | ||||
|                 return ""; | ||||
|             }catch(e){ | ||||
|                 return "<span class='alert'>"+e+"</span>" | ||||
|             } | ||||
|         })); | ||||
|      | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return new Combine([ | ||||
|             this.intro, | ||||
|             this.settingsTable]).Render(); | ||||
|             this.settingsTable, | ||||
|             this.validText]).Render(); | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<TagRenderingConfigJson> { | ||||
|  |  | |||
|  | @ -4,6 +4,9 @@ import {DropDown} from "./DropDown"; | |||
| import {TextField} from "./TextField"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {FromJSON} from "../../Customizations/JSON/FromJSON"; | ||||
| 
 | ||||
| export default class SingleTagInput extends InputElement<string> { | ||||
| 
 | ||||
|  | @ -13,17 +16,29 @@ export default class SingleTagInput extends InputElement<string> { | |||
|     private key: InputElement<string>; | ||||
|     private value: InputElement<string>; | ||||
|     private operator: DropDown<string> | ||||
|     private readonly helpMesage: UIElement; | ||||
| 
 | ||||
|     constructor(value: UIEventSource<string> = undefined) { | ||||
|         super(undefined); | ||||
|         this._value = value ?? new UIEventSource<string>(""); | ||||
| 
 | ||||
|         this.helpMesage = new VariableUiElement(this._value.map(tagDef => { | ||||
|                 try { | ||||
|                     FromJSON.Tag(tagDef, ""); | ||||
|                     return ""; | ||||
|                 } catch (e) { | ||||
|                     return `<br/><span class='alert'>${e}</span>` | ||||
|                 } | ||||
|             } | ||||
|         )); | ||||
| 
 | ||||
|         this.key = TextField.KeyInput(); | ||||
| 
 | ||||
|         this.value = new TextField<string>({ | ||||
|                 placeholder: "value - if blank, matches if key is NOT present", | ||||
|                 fromString: str => str, | ||||
|                 toString: str => str, | ||||
|                 value: new UIEventSource<string>("") | ||||
|             } | ||||
|         ); | ||||
|         this.operator = new DropDown<string>("", [ | ||||
|  | @ -85,9 +100,9 @@ export default class SingleTagInput extends InputElement<string> { | |||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return new Combine([ | ||||
|             this.key, this.operator, this.value | ||||
|         ]).SetStyle("display:flex") | ||||
|             .Render(); | ||||
|             this.key, this.operator, this.value, | ||||
|             this.helpMesage | ||||
|         ]).Render(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -161,7 +161,7 @@ | |||
|         { | ||||
|           "if": { | ||||
|             "and": [ | ||||
|               "operator~Natuurpunt" | ||||
|               "operator=Natuurpunt" | ||||
|             ] | ||||
|           }, | ||||
|           "then": { | ||||
|  |  | |||
|  | @ -252,7 +252,7 @@ | |||
|             "en": "What is the reference number of this public bookcase?", | ||||
|             "nl": "Wat is het referentienummer van dit boekenruilkastje?" | ||||
|           }, | ||||
|           "condition": "brand=*", | ||||
|           "condition": "brand~*", | ||||
|           "freeform": { | ||||
|             "key": "ref" | ||||
|           }, | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ | |||
|   "maintainer": "MapComlete", | ||||
|   "widenfactor": 0.05, | ||||
|   "roamingRenderings": [ | ||||
|     "pictures", | ||||
|     { | ||||
|       "question": "Is deze straat een fietsstraat?", | ||||
|       "mappings": [ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue