Add more checks on parsing from JSON, fix of those issues on the builtin themes

This commit is contained in:
Pieter Vander Vennet 2020-09-08 00:33:05 +02:00
parent 8da0893c05
commit 97ec893479
9 changed files with 117 additions and 29 deletions

View file

@ -107,6 +107,7 @@ export class FromJSON {
} }
throw `Tagrendering ${propertyName} is undefined...` throw `Tagrendering ${propertyName} is undefined...`
} }
if (typeof json === "string") { 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); let template = FromJSON.Translation(json.render);
@ -165,28 +168,35 @@ export class FromJSON {
} }
} }
const mappings = json.mappings?.map(mapping => ( const mappings = json.mappings?.map((mapping, i) => {
{ const k = FromJSON.Tag(mapping.if, `IN mapping #${i} of tagrendering ${propertyName}`)
k: FromJSON.Tag(mapping.if),
txt: FromJSON.Translation(mapping.then), if (question !== undefined && !mapping.hideInAnswer && !k.isUsableAsAnswer()) {
hideInAnswer: mapping.hideInAnswer 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)){ if (template === undefined && (mappings === undefined || mappings.length === 0)) {
console.error("Empty tagrendering detected: no mappings nor template given", json) console.error(`Empty tagrendering detected in ${propertyName}: no mappings nor template given`, json)
throw `Empty tagrendering ${propertyName} detected: no mappings nor template given` throw `Empty tagrendering ${propertyName} detected: no mappings nor template given`
} }
let rendering = new TagRenderingOptions({ let rendering = new TagRenderingOptions({
question: FromJSON.Translation(json.question), question: question,
freeform: freeform, freeform: freeform,
mappings: mappings mappings: mappings
}); });
if (json.condition) { if (json.condition) {
return rendering.OnlyShowIf(FromJSON.Tag(json.condition)); return rendering.OnlyShowIf(FromJSON.Tag(json.condition, `In tagrendering ${propertyName}.condition`));
} }
return rendering; return rendering;
@ -197,7 +207,7 @@ export class FromJSON {
return new Tag(tag[0], tag[1]); 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){ 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" 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) { if (tag.indexOf("!~") >= 0) {
const split = Utils.SplitFirst(tag, "!~"); const split = Utils.SplitFirst(tag, "!~");
if (split[1] === "*") { 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( return new RegexTag(
split[0], split[0],
@ -214,6 +224,16 @@ export class FromJSON {
true 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) { if (tag.indexOf("!=") >= 0) {
const split = Utils.SplitFirst(tag, "!="); const split = Utils.SplitFirst(tag, "!=");
if (split[1] === "*") { if (split[1] === "*") {
@ -236,13 +256,16 @@ export class FromJSON {
); );
} }
const split = Utils.SplitFirst(tag, "="); 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]) return new Tag(split[0], split[1])
} }
if (json.and !== undefined) { 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) { 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) console.log("Parsing layer", json)
const tr = FromJSON.Translation; 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 icon = FromJSON.TagRenderingWithDefault(json.icon, "icon", "./assets/bug.svg");
const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center"); const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center");
const color = FromJSON.TagRenderingWithDefault(json.color, "color", "#0000ff"); const color = FromJSON.TagRenderingWithDefault(json.color, "color", "#0000ff");

View file

@ -7,14 +7,23 @@ import BikeCafes from "../Layers/BikeCafes";
export default class Cyclofix extends Layout { 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() { constructor() {
super( super(
"cyclofix", "cyclofix",
["en", "nl", "fr","gl"], ["en", "nl", "fr", "gl"],
Translations.t.cyclofix.title, Translations.t.cyclofix.title,
["bike_repair_station", new BikeShops(), "drinking_water", "bike_parking", new BikeOtherShops(), new BikeCafes(), ["bike_repair_station", new BikeShops(), "drinking_water", "bike_parking", new BikeOtherShops(), new BikeCafes(),
// The first of november, we remember our dead // 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, 16,
50.8465573, 50.8465573,
4.3516970, 4.3516970,

View file

@ -4,6 +4,7 @@ export abstract class TagsFilter {
abstract matches(tags: { k: string, v: string }[]): boolean abstract matches(tags: { k: string, v: string }[]): boolean
abstract asOverpass(): string[] abstract asOverpass(): string[]
abstract substituteValues(tags: any) : TagsFilter; abstract substituteValues(tags: any) : TagsFilter;
abstract isUsableAsAnswer() : boolean;
matchesProperties(properties: Map<string, string>): boolean { matchesProperties(properties: Map<string, string>): boolean {
return this.matches(TagUtils.proprtiesToKV(properties)); return this.matches(TagUtils.proprtiesToKV(properties));
@ -42,6 +43,10 @@ export class RegexTag extends TagsFilter {
} }
return r.source; return r.source;
} }
isUsableAsAnswer(): boolean {
return false;
}
matches(tags: { k: string; v: string }[]): boolean { matches(tags: { k: string; v: string }[]): boolean {
for (const tag of tags) { for (const tag of tags) {
@ -58,7 +63,10 @@ export class RegexTag extends TagsFilter {
} }
asHumanString() { 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"; throw "Invalid value";
} }
if(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; return this.key + "=" + v;
} }
isUsableAsAnswer(): boolean {
return true;
}
} }
@ -171,6 +183,10 @@ export class Or extends TagsFilter {
asHumanString(linkToWiki: boolean, shorten: boolean) { asHumanString(linkToWiki: boolean, shorten: boolean) {
return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|"); 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) { asHumanString(linkToWiki: boolean, shorten: boolean) {
return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&"); 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; return properties;
} }
} }

View file

@ -19,7 +19,7 @@ export default class SharePanel extends UIElement {
const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`) const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`)
this._panel = new Combine([ this._panel = new Combine([
"<h2>share</h2>", "<h2>Share</h2>",
"Share the following link with friends:<br/>", "Share the following link with friends:<br/>",
new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)), new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)),
"<h2>Publish on OSM Wiki</h2>", "<h2>Publish on OSM Wiki</h2>",

View file

@ -14,6 +14,8 @@ import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson";
import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson"; import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson";
import {UserDetails} from "../../Logic/Osm/OsmConnection"; import {UserDetails} from "../../Logic/Osm/OsmConnection";
import {State} from "../../State"; import {State} from "../../State";
import {VariableUiElement} from "../Base/VariableUIElement";
import {FromJSON} from "../../Customizations/JSON/FromJSON";
export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> { export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
@ -24,6 +26,8 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
private readonly _value: UIEventSource<TagRenderingConfigJson>; private readonly _value: UIEventSource<TagRenderingConfigJson>;
public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; }; public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; };
public readonly validText : UIElement;
constructor(languages: UIEventSource<string[]>, constructor(languages: UIEventSource<string[]>,
currentlySelected: UIEventSource<SingleSetting<any>>, currentlySelected: UIEventSource<SingleSetting<any>>,
userDetails: UserDetails, userDetails: UserDetails,
@ -95,12 +99,24 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
]; ];
this.settingsTable = new SettingsTable(settings, currentlySelected); 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 { InnerRender(): string {
return new Combine([ return new Combine([
this.intro, this.intro,
this.settingsTable]).Render(); this.settingsTable,
this.validText]).Render();
} }
GetValue(): UIEventSource<TagRenderingConfigJson> { GetValue(): UIEventSource<TagRenderingConfigJson> {

View file

@ -4,6 +4,9 @@ import {DropDown} from "./DropDown";
import {TextField} from "./TextField"; import {TextField} from "./TextField";
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import {Utils} from "../../Utils"; 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> { export default class SingleTagInput extends InputElement<string> {
@ -13,17 +16,29 @@ export default class SingleTagInput extends InputElement<string> {
private key: InputElement<string>; private key: InputElement<string>;
private value: InputElement<string>; private value: InputElement<string>;
private operator: DropDown<string> private operator: DropDown<string>
private readonly helpMesage: UIElement;
constructor(value: UIEventSource<string> = undefined) { constructor(value: UIEventSource<string> = undefined) {
super(undefined); super(undefined);
this._value = value ?? new UIEventSource<string>(""); 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.key = TextField.KeyInput();
this.value = new TextField<string>({ this.value = new TextField<string>({
placeholder: "value - if blank, matches if key is NOT present", placeholder: "value - if blank, matches if key is NOT present",
fromString: str => str, fromString: str => str,
toString: str => str, toString: str => str,
value: new UIEventSource<string>("")
} }
); );
this.operator = new DropDown<string>("", [ this.operator = new DropDown<string>("", [
@ -85,9 +100,9 @@ export default class SingleTagInput extends InputElement<string> {
InnerRender(): string { InnerRender(): string {
return new Combine([ return new Combine([
this.key, this.operator, this.value this.key, this.operator, this.value,
]).SetStyle("display:flex") this.helpMesage
.Render(); ]).Render();
} }

View file

@ -161,7 +161,7 @@
{ {
"if": { "if": {
"and": [ "and": [
"operator~Natuurpunt" "operator=Natuurpunt"
] ]
}, },
"then": { "then": {

View file

@ -252,7 +252,7 @@
"en": "What is the reference number of this public bookcase?", "en": "What is the reference number of this public bookcase?",
"nl": "Wat is het referentienummer van dit boekenruilkastje?" "nl": "Wat is het referentienummer van dit boekenruilkastje?"
}, },
"condition": "brand=*", "condition": "brand~*",
"freeform": { "freeform": {
"key": "ref" "key": "ref"
}, },

View file

@ -12,7 +12,6 @@
"maintainer": "MapComlete", "maintainer": "MapComlete",
"widenfactor": 0.05, "widenfactor": 0.05,
"roamingRenderings": [ "roamingRenderings": [
"pictures",
{ {
"question": "Is deze straat een fietsstraat?", "question": "Is deze straat een fietsstraat?",
"mappings": [ "mappings": [