Matching
Matching is the part of path that Formily relies on heavily for field linkage, effects, and queries. Once this section is clear, the matchAliasGroup logic in core becomes much easier to read.
Interactive demo
This playground lets you compare match, Path.match, matchAliasGroup, and includes side by side.
<script setup lang="ts">
import { Path } from '@silver-formily/path'
import { computed, ref } from 'vue'
const pattern = ref('aa.*(!bb)')
const name = ref('kk.mm.aa.cc')
const alias = ref('aa.cc')
const includeSource = ref('a.b')
const includeTarget = ref('a.b')
const result = computed(() => {
try {
const parsed = Path.parse(pattern.value)
return {
directName: parsed.match(name.value),
directAlias: parsed.match(alias.value),
aliasGroup: parsed.matchAliasGroup(name.value, alias.value),
matcher: Path.match(pattern.value)(name.value),
includes: Path.parse(includeSource.value).includes(includeTarget.value),
score: parsed.matchScore,
error: '',
}
}
catch (error) {
return {
directName: false,
directAlias: false,
aliasGroup: false,
matcher: false,
includes: false,
score: 0,
error: error instanceof Error ? error.message : String(error),
}
}
})
const presets = [
{
text: 'alias success',
pattern: 'aa.*(!bb)',
name: 'kk.mm.aa.cc',
alias: 'aa.cc',
},
{
text: 'alias reject',
pattern: 'aa.*(!bb)',
name: 'kk.mm.aa.bb',
alias: 'aa.bb',
},
{
text: 'exclude score',
pattern: 'aa.bb.*(11,22,33).*(!aa,bb,cc)',
name: 'aa.bb.11.mm',
alias: 'aa.cc.dd.bb.11.mm',
},
]
function usePreset(item: { text: string, pattern: string, name: string, alias: string }) {
pattern.value = item.pattern
name.value = item.name
alias.value = item.alias
}
</script>
<template>
<div class="playground">
<div class="tagRow">
<button v-for="item in presets" :key="item.text" class="btn secondary" @click="usePreset(item)">
{{ item.text }}
</button>
</div>
<div class="grid">
<label class="field">
<span class="fieldLabel">Pattern</span>
<input v-model="pattern" class="input">
</label>
<label class="field">
<span class="fieldLabel">Name</span>
<input v-model="name" class="input">
</label>
<label class="field">
<span class="fieldLabel">Alias</span>
<input v-model="alias" class="input">
</label>
</div>
<div class="grid">
<label class="field">
<span class="fieldLabel">includes source</span>
<input v-model="includeSource" class="input">
</label>
<label class="field">
<span class="fieldLabel">includes target</span>
<input v-model="includeTarget" class="input">
</label>
</div>
<div v-if="result.error" class="errorBox">
{{ result.error }}
</div>
<div class="panelGrid">
<div class="panel">
<div class="panelTitle">
match(name)
</div>
<div class="panelBody">
<span :class="result.directName ? 'statusTrue' : 'statusFalse'">{{ result.directName }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
match(alias)
</div>
<div class="panelBody">
<span :class="result.directAlias ? 'statusTrue' : 'statusFalse'">{{ result.directAlias }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
matchAliasGroup
</div>
<div class="panelBody">
<span :class="result.aliasGroup ? 'statusTrue' : 'statusFalse'">{{ result.aliasGroup }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
Path.match(pattern)(name)
</div>
<div class="panelBody">
<span :class="result.matcher ? 'statusTrue' : 'statusFalse'">{{ result.matcher }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
includes(source, target)
</div>
<div class="panelBody">
<span :class="result.includes ? 'statusTrue' : 'statusFalse'">{{ result.includes }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
last matchScore
</div>
<div class="panelBody metricValue">
{{ result.score }}
</div>
</div>
</div>
</div>
</template>
<style scoped src="../../shared/path-demo.css"></style>match
Both sides do not have to be plain paths. Two common forms are:
Path.parse('a.b.c').match('*')
// true
Path.parse('*').match('a.b.c')
// true
Path.parse('aa.1.cc').match('aa.*.cc')
// trueIf both sides are match patterns, path throws because the matching direction is ambiguous.
Path.parse('*').match('aa.*.cc')
// throw ErrorPath.match
If you want to reuse the same matcher, the static helper is more ergonomic:
const isTarget = Path.match('aa.*(!bb,cc)')
isTarget('aa.dd')
// true
isTarget('aa.bb')
// falseIf you want to compile once and reuse many times, this is usually cleaner than repeatedly calling parse(...).match(...).
transform
transform is useful when you want to extract an index or identifier from a path and build a new one:
Path.parse('aa.1.cc').transform(/\d+/, index => `aa.${Number(index) + 1}.cc`)
// 'aa.2.cc'The static helper supports the same flow:
Path.transform('aa.0.bb', /\d+/, index => `aa.${Number(index) + 1}.bb`)
// 'aa.1.bb'matchScore
After each match, a Path instance updates its matchScore. You usually do not use it directly, but matchAliasGroup uses it to resolve cases involving exclude patterns.
matchAliasGroup
This method is especially important in core. It evaluates both the field name and the alias against the same pattern.
const pattern = Path.parse('aa.*(!bb)')
pattern.matchAliasGroup('kk.mm.aa.cc', 'aa.cc')
// true
pattern.matchAliasGroup('kk.mm.aa.bb', 'aa.bb')
// falseIn the simplest case, you can read it as “match the real name and alias against the same pattern”:
Path.parse('aa.bb.cc').matchAliasGroup('aa.bb.cc', 'aa.cc')
// trueWhen exclude patterns are involved, the result depends more on matchScore:
const excludePattern = Path.parse('aa.bb.*(11,22,33).*(!aa,bb,cc)')
excludePattern.matchAliasGroup('aa.bb.11.mm', 'aa.cc.dd.bb.11.mm')
// trueincludes
includes is stricter than match. It checks whether one plain path contains another as a prefix:
Path.parse('a.b').includes('a.b')
// true
Path.parse('a.b').includes('a.c')
// false
Path.parse('a.b').includes('a.b.c')
// falseIf either side is a match pattern in the wrong position, it may throw. includes is a prefix check, not a generic matcher.