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", (() => {