Add 'visit'-functionality to TagsFilter, add case invariant regextag
This commit is contained in:
parent
e6812f3577
commit
ec1c206f84
9 changed files with 131 additions and 69 deletions
|
@ -57,6 +57,9 @@ 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.*`
|
||||
|
||||
Regexes will match the newline character with `.` too - the `s`-flag is enabled by default.
|
||||
To enable case invariant matching, use `key~i~regex`
|
||||
|
||||
Equivalently, `key!~regex` can be used if you _don't_ want to match the regex in order to appear.
|
||||
|
||||
|
||||
|
@ -81,13 +84,15 @@ which we do not want.
|
|||
To mitigate this, use:
|
||||
|
||||
```json
|
||||
"mappings": [
|
||||
{
|
||||
"mappings": [
|
||||
{
|
||||
"if":"key:={some_other_key}"
|
||||
"then": "...",
|
||||
"hideInAnswer": "some_other_key="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
One can use `key!:=prefix-{other_key}-postfix` as well, to match if `key` is _not_ the same
|
||||
|
|
|
@ -5,6 +5,7 @@ import {Tag} from "./Tag";
|
|||
import {RegexTag} from "./RegexTag";
|
||||
|
||||
export class And extends TagsFilter {
|
||||
|
||||
public and: TagsFilter[]
|
||||
|
||||
constructor(and: TagsFilter[]) {
|
||||
|
@ -373,5 +374,9 @@ export class And extends TagsFilter {
|
|||
return !this.and.some(t => !t.isNegative());
|
||||
}
|
||||
|
||||
visit(f: (TagsFilter: any) => void) {
|
||||
f(this)
|
||||
this.and.forEach(sub => sub.visit(f))
|
||||
}
|
||||
|
||||
}
|
|
@ -52,10 +52,6 @@ export default class ComparingTag implements TagsFilter {
|
|||
return [];
|
||||
}
|
||||
|
||||
AsJson() {
|
||||
return this.asHumanString(false, false, {})
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
|
@ -63,4 +59,8 @@ export default class ComparingTag implements TagsFilter {
|
|||
isNegative(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
visit(f: (TagsFilter) => void) {
|
||||
f(this)
|
||||
}
|
||||
}
|
|
@ -261,6 +261,11 @@ export class Or extends TagsFilter {
|
|||
return this.or.some(t => t.isNegative());
|
||||
}
|
||||
|
||||
visit(f: (TagsFilter: any) => void) {
|
||||
f(this)
|
||||
this.or.forEach(t => t.visit(f))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,9 @@ export class RegexTag extends TagsFilter {
|
|||
*
|
||||
* // A regextag with a regex key should give correct output
|
||||
* new RegexTag(/a.*x/, /^..*$/).asOverpass() // => [ `[~"a.*x"~\"^..*$\"]` ]
|
||||
*
|
||||
* // A regextag with a case invariant flag should signal this to overpass
|
||||
* new RegexTag("key", /^.*value.*$/i).asOverpass() // => [ `["key"~\"^.*value.*$\",i]` ]
|
||||
*/
|
||||
asOverpass(): string[] {
|
||||
const inv =this.invert ? "!" : ""
|
||||
|
@ -57,7 +60,8 @@ export class RegexTag extends TagsFilter {
|
|||
// anything goes
|
||||
return [`[${inv}"${this.key}"]`]
|
||||
}
|
||||
return [`["${this.key}"${inv}~"${src}"]`]
|
||||
const modifier = this.value.ignoreCase ? ",i" : ""
|
||||
return [`["${this.key}"${inv}~"${src}"${modifier}]`]
|
||||
}else{
|
||||
// Normal key and normal value
|
||||
return [`["${this.key}"${inv}="${this.value}"]`];
|
||||
|
@ -256,10 +260,6 @@ export class RegexTag extends TagsFilter {
|
|||
return []
|
||||
}
|
||||
|
||||
AsJson() {
|
||||
return this.asHumanString()
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
|
@ -267,4 +267,8 @@ export class RegexTag extends TagsFilter {
|
|||
isNegative(): boolean {
|
||||
return this.invert;
|
||||
}
|
||||
|
||||
visit(f: (TagsFilter) => void) {
|
||||
f(this)
|
||||
}
|
||||
}
|
|
@ -82,10 +82,6 @@ export default class SubstitutingTag implements TagsFilter {
|
|||
return [{k: this._key, v: v}];
|
||||
}
|
||||
|
||||
AsJson() {
|
||||
return this._key + (this._invert ? '!' : '') + "=" + this._value
|
||||
}
|
||||
|
||||
optimize(): TagsFilter | boolean {
|
||||
return this;
|
||||
}
|
||||
|
@ -93,4 +89,8 @@ export default class SubstitutingTag implements TagsFilter {
|
|||
isNegative(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
visit(f: (TagsFilter: any) => void) {
|
||||
f(this)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import {Utils} from "../../Utils";
|
||||
import {RegexTag} from "./RegexTag";
|
||||
import {TagsFilter} from "./TagsFilter";
|
||||
|
||||
|
||||
export class Tag extends TagsFilter {
|
||||
public key: string
|
||||
public value: string
|
||||
|
||||
constructor(key: string, value: string) {
|
||||
super()
|
||||
this.key = key
|
||||
|
@ -23,6 +22,8 @@ export class Tag extends TagsFilter {
|
|||
|
||||
|
||||
/**
|
||||
* imort
|
||||
*
|
||||
* const tag = new Tag("key","value")
|
||||
* tag.matchesProperties({"key": "value"}) // => true
|
||||
* tag.matchesProperties({"key": "z"}) // => false
|
||||
|
@ -89,6 +90,9 @@ export class Tag extends TagsFilter {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* import {RegexTag} from "./RegexTag";
|
||||
*
|
||||
* // should handle advanced regexes
|
||||
* new Tag("key", "aaa").shadows(new RegexTag("key", /a+/)) // => true
|
||||
* new Tag("key","value").shadows(new RegexTag("key", /^..*$/, true)) // => false
|
||||
|
@ -129,4 +133,8 @@ export class Tag extends TagsFilter {
|
|||
isNegative(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
visit(f: (TagsFilter) => void) {
|
||||
f(this)
|
||||
}
|
||||
}
|
|
@ -57,10 +57,10 @@ export class TagUtils {
|
|||
}
|
||||
|
||||
/***
|
||||
* Creates a hash {key --> [values : string | Regex ]}, with all the values present in the tagsfilter
|
||||
* Creates a hash {key --> [values : string | RegexTag ]}, with all the values present in the tagsfilter
|
||||
*/
|
||||
static SplitKeys(tagsFilters: TagsFilter[], allowRegex = false) {
|
||||
const keyValues = {} // Map string -> string[]
|
||||
const keyValues = {} // Map string -> (string | RegexTag)[]
|
||||
tagsFilters = [...tagsFilters] // copy all, use as queue
|
||||
while (tagsFilters.length > 0) {
|
||||
const tagsFilter = tagsFilters.shift();
|
||||
|
@ -200,20 +200,29 @@ export class TagUtils {
|
|||
*
|
||||
* TagUtils.Tag("key=value") // => new Tag("key", "value")
|
||||
* TagUtils.Tag("key=") // => new Tag("key", "")
|
||||
* TagUtils.Tag("key!=") // => new RegexTag("key", /^..*$/)
|
||||
* TagUtils.Tag("key~*") // => new RegexTag("key", /^..*$/)
|
||||
* TagUtils.Tag("key!=") // => new RegexTag("key", /^..*$/s)
|
||||
* TagUtils.Tag("key~*") // => new RegexTag("key", /^..*$/s)
|
||||
* TagUtils.Tag("name~i~somename") // => new RegexTag("name", /^somename$/si)
|
||||
* TagUtils.Tag("key!=value") // => new RegexTag("key", "value", true)
|
||||
* TagUtils.Tag("vending~.*bicycle_tube.*") // => new RegexTag("vending", /^.*bicycle_tube.*$/)
|
||||
* TagUtils.Tag("x!~y") // => new RegexTag("x", /^y$/, true)
|
||||
* TagUtils.Tag("vending~.*bicycle_tube.*") // => new RegexTag("vending", /^.*bicycle_tube.*$/s)
|
||||
* TagUtils.Tag("x!~y") // => new RegexTag("x", /^y$/s, true)
|
||||
* TagUtils.Tag({"and": ["key=value", "x=y"]}) // => new And([new Tag("key","value"), new Tag("x","y")])
|
||||
* TagUtils.Tag("name~[sS]peelbos.*") // => new RegexTag("name", /^[sS]peelbos.*$/)
|
||||
* TagUtils.Tag("name~[sS]peelbos.*") // => new RegexTag("name", /^[sS]peelbos.*$/s)
|
||||
* TagUtils.Tag("survey:date:={_date:now}") // => new SubstitutingTag("survey:date", "{_date:now}")
|
||||
* TagUtils.Tag("xyz!~\\[\\]") // => new RegexTag("xyz", /^\[\]$/, true)
|
||||
* TagUtils.Tag("tags~(.*;)?amenity=public_bookcase(;.*)?") // => new RegexTag("tags", /^(.*;)?amenity=public_bookcase(;.*)?$/)
|
||||
* TagUtils.Tag("service:bicycle:.*~~*") // => new RegexTag(/^service:bicycle:.*$/, /^..*$/)
|
||||
* TagUtils.Tag("xyz!~\\[\\]") // => new RegexTag("xyz", /^\[\]$/s, true)
|
||||
* TagUtils.Tag("tags~(.*;)?amenity=public_bookcase(;.*)?") // => new RegexTag("tags", /^(.*;)?amenity=public_bookcase(;.*)?$/s)
|
||||
* TagUtils.Tag("service:bicycle:.*~~*") // => new RegexTag(/^service:bicycle:.*$/, /^..*$/s)
|
||||
* TagUtils.Tag("_first_comment~.*{search}.*") // => new RegexTag('_first_comment', /^.*{search}.*$/s)
|
||||
*
|
||||
* TagUtils.Tag("xyz<5").matchesProperties({xyz: 4}) // => true
|
||||
* TagUtils.Tag("xyz<5").matchesProperties({xyz: 5}) // => false
|
||||
*
|
||||
* // RegexTags must match values with newlines
|
||||
* TagUtils.Tag("note~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De aed bevindt zich op de 5de verdieping"}) // => true
|
||||
* TagUtils.Tag("note~i~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De AED bevindt zich op de 5de verdieping"}) // => true
|
||||
*
|
||||
* // Must match case insensitive
|
||||
* TagUtils.Tag("name~i~somename").matchesProperties({name: "SoMeName"}) // => true
|
||||
*/
|
||||
public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
||||
try {
|
||||
|
@ -247,6 +256,33 @@ export class TagUtils {
|
|||
return r
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the various parts of a regex tag
|
||||
*
|
||||
* TagUtils.parseRegexOperator("key~value") // => {invert: false, key: "key", value: "value", modifier: ""}
|
||||
* TagUtils.parseRegexOperator("key!~value") // => {invert: true, key: "key", value: "value", modifier: ""}
|
||||
* TagUtils.parseRegexOperator("key~i~value") // => {invert: false, key: "key", value: "value", modifier: "i"}
|
||||
* TagUtils.parseRegexOperator("key!~i~someweirdvalue~qsdf") // => {invert: true, key: "key", value: "someweirdvalue~qsdf", modifier: "i"}
|
||||
* TagUtils.parseRegexOperator("_image:0~value") // => {invert: false, key: "_image:0", value: "value", modifier: ""}
|
||||
* TagUtils.parseRegexOperator("key~*") // => {invert: false, key: "key", value: "*", modifier: ""}
|
||||
* TagUtils.parseRegexOperator("Brugs volgnummer~*") // => {invert: false, key: "Brugs volgnummer", value: "*", modifier: ""}
|
||||
* TagUtils.parseRegexOperator("socket:USB-A~*") // => {invert: false, key: "socket:USB-A", value: "*", modifier: ""}
|
||||
* TagUtils.parseRegexOperator("tileId~*") // => {invert: false, key: "tileId", value: "*", modifier: ""}
|
||||
*/
|
||||
public static parseRegexOperator(tag: string): {
|
||||
invert: boolean;
|
||||
key: string;
|
||||
value: string;
|
||||
modifier: "i" | "";
|
||||
} | null {
|
||||
const match = tag.match(/^([_a-zA-Z0-9: -]+)(!)?~([i]~)?(.*)$/);
|
||||
if (match == null) {
|
||||
return null;
|
||||
}
|
||||
const [_, key, invert, modifier, value] = match;
|
||||
return {key, value, invert: invert == "!", modifier: (modifier == "i~" ? "i" : "")};
|
||||
}
|
||||
|
||||
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
||||
|
||||
if (json === undefined) {
|
||||
|
@ -300,17 +336,6 @@ export class TagUtils {
|
|||
}
|
||||
}
|
||||
|
||||
if (tag.indexOf("!~") >= 0) {
|
||||
const split = Utils.SplitFirst(tag, "!~");
|
||||
if (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],
|
||||
new RegExp("^"+ split[1]+"$"),
|
||||
true
|
||||
);
|
||||
}
|
||||
if (tag.indexOf("~~") >= 0) {
|
||||
const split = Utils.SplitFirst(tag, "~~");
|
||||
if (split[1] === "*") {
|
||||
|
@ -318,9 +343,30 @@ export class TagUtils {
|
|||
}
|
||||
return new RegexTag(
|
||||
new RegExp("^" + split[0] + "$"),
|
||||
new RegExp("^" + split[1] + "$")
|
||||
new RegExp("^" + split[1] + "$", "s")
|
||||
);
|
||||
}
|
||||
|
||||
const withRegex = TagUtils.parseRegexOperator(tag)
|
||||
if(withRegex != null) {
|
||||
if (withRegex.value === "*" && withRegex.invert) {
|
||||
throw `Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${tag} while parsing ${context})`
|
||||
}
|
||||
if (withRegex.value === "") {
|
||||
throw "Detected a regextag with an empty regex; this is not allowed. Use '" + withRegex.key + "='instead (at " + context + ")"
|
||||
}
|
||||
|
||||
let value: string | RegExp = withRegex.value;
|
||||
if (value === "*") {
|
||||
value = "..*"
|
||||
}
|
||||
return new RegexTag(
|
||||
withRegex.key,
|
||||
new RegExp("^"+value+"$", "s"+withRegex.modifier),
|
||||
withRegex.invert
|
||||
);
|
||||
}
|
||||
|
||||
if (tag.indexOf("!:=") >= 0) {
|
||||
const split = Utils.SplitFirst(tag, "!:=");
|
||||
return new SubstitutingTag(split[0], split[1], true);
|
||||
|
@ -337,7 +383,7 @@ export class TagUtils {
|
|||
}
|
||||
if (split[1] === "") {
|
||||
split[1] = "..*"
|
||||
return new RegexTag(split[0], /^..*$/)
|
||||
return new RegexTag(split[0], /^..*$/s)
|
||||
}
|
||||
return new RegexTag(
|
||||
split[0],
|
||||
|
@ -345,22 +391,8 @@ export class TagUtils {
|
|||
true
|
||||
);
|
||||
}
|
||||
if (tag.indexOf("~") >= 0) {
|
||||
const split = Utils.SplitFirst(tag, "~");
|
||||
let value : string | RegExp = split[1]
|
||||
if (split[1] === "") {
|
||||
throw "Detected a regextag with an empty regex; this is not allowed. Use '" + split[0] + "='instead (at " + context + ")"
|
||||
}
|
||||
if (value === "*") {
|
||||
value = /^..*$/
|
||||
}else {
|
||||
value = new RegExp("^"+value+"$")
|
||||
}
|
||||
return new RegexTag(
|
||||
split[0],
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (tag.indexOf("=") >= 0) {
|
||||
|
||||
|
||||
|
@ -512,6 +544,4 @@ export class TagUtils {
|
|||
return listToFilter.some(tf => guards.some(guard => guard.shadows(tf)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@ export abstract class TagsFilter {
|
|||
* Returns all normal key/value pairs
|
||||
* Regex tags, substitutions, comparisons, ... are exempt
|
||||
*/
|
||||
abstract usedTags(): {key: string, value: string}[];
|
||||
abstract usedTags(): { key: string, value: string }[];
|
||||
|
||||
/**
|
||||
* Converts the tagsFilter into a list of key-values that should be uploaded to OSM.
|
||||
|
@ -52,4 +52,9 @@ export abstract class TagsFilter {
|
|||
*/
|
||||
abstract isNegative(): boolean
|
||||
|
||||
/**
|
||||
* Walks the entire tree, every tagsFilter will be passed into the function once
|
||||
*/
|
||||
abstract visit(f: ((TagsFilter) => void));
|
||||
|
||||
}
|
Loading…
Reference in a new issue