diff --git a/Customizations/JSON/FromJSON.ts b/Customizations/JSON/FromJSON.ts index b583cca..ce48951 100644 --- a/Customizations/JSON/FromJSON.ts +++ b/Customizations/JSON/FromJSON.ts @@ -5,6 +5,7 @@ import {Or} from "../../Logic/Tags/Or"; import {And} from "../../Logic/Tags/And"; import {Tag} from "../../Logic/Tags/Tag"; import {TagsFilter} from "../../Logic/Tags/TagsFilter"; +import SubstitutingTag from "../../Logic/Tags/SubstitutingTag"; export class FromJSON { @@ -12,18 +13,19 @@ export class FromJSON { const tag = Utils.SplitFirst(json, "="); return new Tag(tag[0], tag[1]); } + public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { - try{ + try { return this.TagUnsafe(json, context); - }catch(e){ - console.error("Could not parse tag", json,"in context",context,"due to ", e) + } catch (e) { + console.error("Could not parse tag", json, "in context", context, "due to ", e) throw e; } } private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { - if (json === undefined) { + if (json === undefined) { throw `Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression` } if (typeof (json) == "string") { @@ -49,6 +51,11 @@ export class FromJSON { new RegExp("^" + split[1] + "$") ); } + if(tag.indexOf(":=") >= 0){ + const split = Utils.SplitFirst(tag, ":="); + return new SubstitutingTag(split[0], split[1]); + } + if (tag.indexOf("!=") >= 0) { const split = Utils.SplitFirst(tag, "!="); if (split[1] === "*") { diff --git a/Docs/Tags_format.md b/Docs/Tags_format.md index 4ea7c9c..f6968f6 100644 --- a/Docs/Tags_format.md +++ b/Docs/Tags_format.md @@ -35,3 +35,32 @@ Regex equals A tag can also be tested against a regex with `key~regex`. Note that this regex __must match__ the entire value. If the value is allowed to appear anywhere as substring, use `key~.*regex.*` Equivalently, `key!~regex` can be used if you _don't_ want to match the regex in order to appear. + + +Using other tags as variables +----------------------------- + +**This is an advanced feature - use with caution** + +Some tags are automatically set or calculated - see [CalculatedTags](CalculatedTags.md) for an entire overview. +If one wants to apply such a value as tag, use a substituting-tag such, for example`survey:date:={_date:now}`. Note that the separator between key and value here is `:=`. +The text between `{` and `}` is interpreted as a key, and the respective value is substituted into the string. + +One can also append, e.g. `key:={some_key} fixed text {some_other_key}`. + +An assigning tag _cannot_ be used to query OpenStreetMap/Overpass. + +If using a key or variable which might not be defined, add a condition in the mapping to hide the option. +This is because, if `some_other_key` is not defined, one might actually upload the literal text `key={some_other_key}` to OSM - which we do not want. + +To mitigate this, use: + +``` +"mappings": [ +{ + "if":"key:={some_other_key}" + "then": "...", + "hideInAnswer": "some_other_key=" +} +] +``` \ No newline at end of file diff --git a/Folder.DotSettings.user b/Folder.DotSettings.user index e9e707c..1732b9a 100644 --- a/Folder.DotSettings.user +++ b/Folder.DotSettings.user @@ -1,2 +1,5 @@  - ShowAndRun \ No newline at end of file + ShowAndRun + <SessionState ContinuousTestingMode="0" IsActive="True" Name="Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Nothing /> +</SessionState> \ No newline at end of file diff --git a/Logic/Tags/And.ts b/Logic/Tags/And.ts index 9ca6e27..209ad50 100644 --- a/Logic/Tags/And.ts +++ b/Logic/Tags/And.ts @@ -46,14 +46,6 @@ export class And extends TagsFilter { return allChoices; } - substituteValues(tags: any): TagsFilter { - const newChoices = []; - for (const c of this.and) { - newChoices.push(c.substituteValues(tags)); - } - return new And(newChoices); - } - asHumanString(linkToWiki: boolean, shorten: boolean) { return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&"); } diff --git a/Logic/Tags/Or.ts b/Logic/Tags/Or.ts index c05e2d1..1510b37 100644 --- a/Logic/Tags/Or.ts +++ b/Logic/Tags/Or.ts @@ -30,14 +30,6 @@ export class Or extends TagsFilter { return choices; } - substituteValues(tags: any): TagsFilter { - const newChoices = []; - for (const c of this.or) { - newChoices.push(c.substituteValues(tags)); - } - return new Or(newChoices); - } - asHumanString(linkToWiki: boolean, shorten: boolean) { return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|"); } diff --git a/Logic/Tags/SubstitutingTag.ts b/Logic/Tags/SubstitutingTag.ts new file mode 100644 index 0000000..9aecf2e --- /dev/null +++ b/Logic/Tags/SubstitutingTag.ts @@ -0,0 +1,59 @@ +import {TagsFilter} from "./TagsFilter"; + +/** + * The substituting-tag uses the tags of a feature a variables and replaces them. + * + * e.g. key:={other_key}_{ref} will match an object that has at least 'key'. + * If {other_key} is _not_ defined, it will not be substituted. + * + * The 'key' is always fixed and should not contain substitutions. + * This cannot be used to query features + */ +export default class SubstitutingTag implements TagsFilter { + private readonly _key: string; + private readonly _value: string; + + constructor(key: string, value: string) { + this._key = key; + this._value = value; + } + + private static substituteString(template: string, dict: any) { + for (const k in dict) { + template = template.replace(new RegExp("\\{" + k + "\\}", 'g'), dict[k]) + } + return template; + } + + asHumanString(linkToWiki: boolean, shorten: boolean) { + return this._key + ":=" + this._value; + } + + asOverpass(): string[] { + throw "A variable with substitution can not be used to query overpass" + } + + isEquivalent(other: TagsFilter): boolean { + if (!(other instanceof SubstitutingTag)) { + return false; + } + return other._key === this._key && other._value === this._value; + } + + isUsableAsAnswer(): boolean { + return true; + } + + matchesProperties(properties: any): boolean { + const value = properties[this._key]; + if (value === undefined || value === "") { + return false; + } + const expectedValue = SubstitutingTag.substituteString(this._value, properties); + return value === expectedValue; + } + + usedKeys(): string[] { + return [this._key]; + } +} \ No newline at end of file diff --git a/Logic/Tags/TagsFilter.ts b/Logic/Tags/TagsFilter.ts index 3283986..0f91c74 100644 --- a/Logic/Tags/TagsFilter.ts +++ b/Logic/Tags/TagsFilter.ts @@ -2,8 +2,6 @@ export abstract class TagsFilter { abstract asOverpass(): string[] - abstract substituteValues(tags: any): TagsFilter; - abstract isUsableAsAnswer(): boolean; abstract isEquivalent(other: TagsFilter): boolean; @@ -13,12 +11,5 @@ export abstract class TagsFilter { abstract asHumanString(linkToWiki: boolean, shorten: boolean); abstract usedKeys(): string[]; - - public matches(tags: { k: string, v: string }[]) { - const properties = {}; - for (const kv of tags) { - properties[kv.k] = kv.v; - } - return this.matchesProperties(properties); - } + } \ No newline at end of file diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 8b3dab2..5fa3263 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -78,6 +78,14 @@ new T("Tags", [ equal(nameStartsWith.matchesProperties({"name": "speelbos Sint-Anna"}), true) equal(nameStartsWith.matchesProperties({"name": "Sint-Anna"}), false) equal(nameStartsWith.matchesProperties({"name": ""}), false) + + + const assign = FromJSON.Tag("survey:date:={_date:now}") + equal(assign.matchesProperties({"survey:date":"2021-03-29", "_date:now":"2021-03-29"}), true); + equal(assign.matchesProperties({"survey:date":"2021-03-29", "_date:now":"2021-01-01"}), false); + equal(assign.matchesProperties({"survey:date":"2021-03-29"}), false); + equal(assign.matchesProperties({"_date:now":"2021-03-29"}), false); + equal(assign.matchesProperties({"some_key":"2021-03-29"}), false); })], ["Is equivalent test", (() => {