From 97ec8934794f306c9988074a848eaa76e8339321 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 8 Sep 2020 00:33:05 +0200 Subject: [PATCH] Add more checks on parsing from JSON, fix of those issues on the builtin themes --- Customizations/JSON/FromJSON.ts | 55 ++++++++++++++------ Customizations/Layouts/Cyclofix.ts | 13 ++++- Logic/Tags.ts | 30 ++++++++++- UI/CustomGenerator/SharePanel.ts | 2 +- UI/CustomGenerator/TagRenderingPanel.ts | 18 ++++++- UI/Input/SingleTagInput.ts | 23 ++++++-- assets/layers/bird_hide/birdhides.json | 2 +- assets/themes/bookcases/Bookcases.json | 2 +- assets/themes/cyclestreets/cyclestreets.json | 1 - 9 files changed, 117 insertions(+), 29 deletions(-) diff --git a/Customizations/JSON/FromJSON.ts b/Customizations/JSON/FromJSON.ts index c1c77f1..cbba4b6 100644 --- a/Customizations/JSON/FromJSON.ts +++ b/Customizations/JSON/FromJSON.ts @@ -107,6 +107,7 @@ export class FromJSON { } throw `Tagrendering ${propertyName} is undefined...` } + if (typeof json === "string") { @@ -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"); diff --git a/Customizations/Layouts/Cyclofix.ts b/Customizations/Layouts/Cyclofix.ts index f70e971..4f66595 100644 --- a/Customizations/Layouts/Cyclofix.ts +++ b/Customizations/Layouts/Cyclofix.ts @@ -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, diff --git a/Logic/Tags.ts b/Logic/Tags.ts index 8cc2b9d..df8a280 100644 --- a/Logic/Tags.ts +++ b/Logic/Tags.ts @@ -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): boolean { return this.matches(TagUtils.proprtiesToKV(properties)); @@ -42,6 +43,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) { @@ -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; } + } diff --git a/UI/CustomGenerator/SharePanel.ts b/UI/CustomGenerator/SharePanel.ts index 68527d0..5fcd6ac 100644 --- a/UI/CustomGenerator/SharePanel.ts +++ b/UI/CustomGenerator/SharePanel.ts @@ -19,7 +19,7 @@ export default class SharePanel extends UIElement { const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`) this._panel = new Combine([ - "

share

", + "

Share

", "Share the following link with friends:
", new VariableUiElement(liveUrl.map(url => `${url}`)), "

Publish on OSM Wiki

", diff --git a/UI/CustomGenerator/TagRenderingPanel.ts b/UI/CustomGenerator/TagRenderingPanel.ts index a51ad55..aa81aa5 100644 --- a/UI/CustomGenerator/TagRenderingPanel.ts +++ b/UI/CustomGenerator/TagRenderingPanel.ts @@ -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 { @@ -24,6 +26,8 @@ export default class TagRenderingPanel extends InputElement; public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; }; + public readonly validText : UIElement; + constructor(languages: UIEventSource, currentlySelected: UIEventSource>, userDetails: UserDetails, @@ -95,12 +99,24 @@ export default class TagRenderingPanel extends InputElement { + try{ + FromJSON.TagRendering(json, options?.title ?? ""); + return ""; + }catch(e){ + return ""+e+"" + } + })); + } InnerRender(): string { return new Combine([ this.intro, - this.settingsTable]).Render(); + this.settingsTable, + this.validText]).Render(); } GetValue(): UIEventSource { diff --git a/UI/Input/SingleTagInput.ts b/UI/Input/SingleTagInput.ts index 5e998cd..82d44a9 100644 --- a/UI/Input/SingleTagInput.ts +++ b/UI/Input/SingleTagInput.ts @@ -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 { @@ -13,17 +16,29 @@ export default class SingleTagInput extends InputElement { private key: InputElement; private value: InputElement; private operator: DropDown + private readonly helpMesage: UIElement; constructor(value: UIEventSource = undefined) { super(undefined); this._value = value ?? new UIEventSource(""); - + + this.helpMesage = new VariableUiElement(this._value.map(tagDef => { + try { + FromJSON.Tag(tagDef, ""); + return ""; + } catch (e) { + return `
${e}` + } + } + )); + this.key = TextField.KeyInput(); this.value = new TextField({ placeholder: "value - if blank, matches if key is NOT present", fromString: str => str, toString: str => str, + value: new UIEventSource("") } ); this.operator = new DropDown("", [ @@ -85,9 +100,9 @@ export default class SingleTagInput extends InputElement { InnerRender(): string { return new Combine([ - this.key, this.operator, this.value - ]).SetStyle("display:flex") - .Render(); + this.key, this.operator, this.value, + this.helpMesage + ]).Render(); } diff --git a/assets/layers/bird_hide/birdhides.json b/assets/layers/bird_hide/birdhides.json index fe83252..86bcc6e 100644 --- a/assets/layers/bird_hide/birdhides.json +++ b/assets/layers/bird_hide/birdhides.json @@ -161,7 +161,7 @@ { "if": { "and": [ - "operator~Natuurpunt" + "operator=Natuurpunt" ] }, "then": { diff --git a/assets/themes/bookcases/Bookcases.json b/assets/themes/bookcases/Bookcases.json index 1adbf85..8f20c80 100644 --- a/assets/themes/bookcases/Bookcases.json +++ b/assets/themes/bookcases/Bookcases.json @@ -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" }, diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 7e85c45..4da116b 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -12,7 +12,6 @@ "maintainer": "MapComlete", "widenfactor": 0.05, "roamingRenderings": [ - "pictures", { "question": "Is deze straat een fietsstraat?", "mappings": [