Àüţĥöŕîñĝ Ṽöçàƃüļàŕîéš
Ţĥîš ĝüîđé çöṽéŕš îḿþļéḿéñţîñĝ àñđ éẋţéñđîñĝ ṽöçàƃüļàŕîéš, ţĥé šéḿàñţîç ţýþé šýšţéḿ ţĥàţ çļàššîƒîéš îñļîñé çöđéš. Ƒöŕ ŵĥàţ ṽöçàƃüļàŕîéš àŕé àñđ ŵĥý ţĥéý éẋîšţ, šéé ţĥé çöñçéþţ þàĝé: Ṽöçàƃüļàŕîéš.
Ṽöçàƃüļàŕý ƒîļé ƒöŕḿàţ
Éàçĥ ṽöçàƃüļàŕý îš à ĴŠÖÑ ƒîļé. Ţýþéš àŕé ķéýéđ ƃý à category:name îđéñţîƒîéŕ
àñđ çàŕŕý ŕéñđéŕîñĝ, đîšþļàý, çöļöŕ, àñđ çöñšţŕàîñţ ḿéţàđàţà:
{
"name": "my-vocabulary",
"version": "1.0",
"extends": "common-formatting",
"entity_prefix": "entity:",
"types": {
"category:type-name": {
"category": "category-name",
"label": "Human Readable Label",
"html": {
"open": "<tag>",
"close": "</tag>",
"placeholder": "<tag/>"
},
"display": {
"open": "[TAG]",
"close": "[/TAG]",
"placeholder": "[TAG/]"
},
"chipLabel": {
"open": "tag>",
"close": "/tag",
"placeholder": "tag"
},
"color": {
"bg": "rgba(59,130,246,0.15)",
"border": "rgba(59,130,246,0.5)",
"text": "rgb(59,130,246)"
},
"equiv": "",
"constraints": {
"deletable": true,
"cloneable": true,
"reorderable": true
}
}
},
"fallback": {
"html": { "open": "<span>", "close": "</span>", "placeholder": "<span/>" },
"display": { "open": "[?]", "close": "[/?]", "placeholder": "[?/]" },
"chipLabel": { "open": "?>", "close": "/?", "placeholder": "?" },
"color": {
"bg": "rgba(156,163,175,0.15)",
"border": "rgba(156,163,175,0.5)",
"text": "rgb(107,114,128)"
},
"constraints": { "deletable": true, "cloneable": true, "reorderable": true }
}
}
Ƒîéļđ ŕéƒéŕéñçé
| Ƒîéļđ | Ŕéǫüîŕéđ | Đéšçŕîþţîöñ |
|---|---|---|
name | Ýéš | Üñîǫüé ṽöçàƃüļàŕý ñàḿé |
version | Ýéš | Šéḿṽéŕ ṽéŕšîöñ šţŕîñĝ |
extends | Ñö | Þàŕéñţ ṽöçàƃüļàŕý ñàḿé (ţýþéš àŕé ḿéŕĝéđ) |
entity_prefix | Ñö | Þŕéƒîẋ ƒöŕ éñţîţý-ţýþé îñļîñé çöđéš (đéƒàüļţ "entity:") |
types | Ýéš | Ḿàþ öƒ ţýþé ñàḿé → SpanTypeInfo |
fallback | Ñö | Đéƒàüļţ ŕéñđéŕîñĝ ƒöŕ üñķñöŵñ ţýþéš |
Ţýþé ñàḿé çöñṽéñţîöñ
Ţýþé ñàḿéš ƒöļļöŵ ţĥé category:name þàţţéŕñ: fmt:bold, link:hyperlink,
code:variable, struct:break.
Çöñšţŕàîñţ šéḿàñţîçš
| Çöñšţŕàîñţ | true | false |
|---|---|---|
deletable | Ţŕàñšļàţöŕ ḿàý ŕéḿöṽé ţĥé ţàĝ | Ţàĝ ḿüšţ àþþéàŕ îñ ţŕàñšļàţîöñ (éñƒöŕçéđ) |
cloneable | Ţŕàñšļàţöŕ ḿàý đüþļîçàţé ţĥé ţàĝ | Ţàĝ çöüñţ ḿüšţ ñöţ éẋçééđ šöüŕçé çöüñţ |
reorderable | Ţŕàñšļàţöŕ ḿàý ŕéàŕŕàñĝé ţàĝ þöšîţîöñ | Ţàĝ þöšîţîöñ ŕéļàţîṽé ţö öţĥéŕš îš ļöçķéđ |
Üšîñĝ ṽöçàƃüļàŕîéš îñ à ƒöŕḿàţ ŕéàđéŕ
À ƒöŕḿàţ ŕéàđéŕ îñîţîàļîžéš à VocabularyRegistry àñđ üšéš îţ ţö þöþüļàţé
îñļîñé-çöđé ḿéţàđàţà àš îţ ƃüîļđš à Ƃļöçķ'š []model.Run šéǫüéñçé:
package myformat
import "github.com/neokapi/neokapi/core/model"
type Reader struct {
vocab *model.VocabularyRegistry
}
func NewReader() *Reader {
vocab := model.NewVocabularyRegistry()
_ = vocab.LoadDefaults() // common-formatting + rich-html + rich-jsx + code-tokens
return &Reader{vocab: vocab}
}
Îñļîñé çöñţéñţ îš à ƒļàţ []model.Run (šéé
Ƒ-02: Çöñţéñţ Ḿöđéļ). Àñ
öþéñîñĝ ţàĝ ƃéçöḿéš à PcOpenRun, îţš ḿàţçĥîñĝ çļöšé à PcCloseRun ŵîţĥ ţĥé
šàḿé ID, àñđ à šéļƒ-çļöšîñĝ çöñšţŕüçţ à PlaceholderRun. Ŵĥéñ ƃüîļđîñĝ öñé,
ļööķ üþ ţĥé ṽöçàƃüļàŕý éñţŕý àñđ þöþüļàţé ţĥé ŕéñđéŕîñĝ àñđ çöñšţŕàîñţ ƒîéļđš,
ḿîŕŕöŕîñĝ ţĥé þéŕ-ƒöŕḿàţ runBuilder ĥéļþéŕš (core/formats/*/run_builder.go):
// openRun builds the opening half of a paired code, e.g. <b> / <a href="…">.
func (r *Reader) openRun(semType, subType, id, nativeMarkup string) model.Run {
info := r.vocab.LookupOrFallback(semType)
return model.Run{PcOpen: &model.PcOpenRun{
ID: id, // shared with the matching PcClose
Type: semType, // "fmt:bold"
SubType: subType, // "html:b" or "md:strong"
Data: nativeMarkup, // original markup for roundtrip
Disp: info.Display.Open, // "[B]"
Equiv: info.Equiv, // "" (or "\n" for struct:break)
Constraints: &model.RunConstraints{
Deletable: info.Constraints.Deletable,
Cloneable: info.Constraints.Cloneable,
Reorderable: info.Constraints.Reorderable,
},
}}
}
// closeRun builds the matching close. PcCloseRun shares the opener's ID and
// replays its own native markup; it inherits the opener's constraints.
func (r *Reader) closeRun(semType, subType, id, nativeMarkup string) model.Run {
info := r.vocab.LookupOrFallback(semType)
return model.Run{PcClose: &model.PcCloseRun{
ID: id,
Type: semType,
SubType: subType,
Data: nativeMarkup, // "</b>"
Equiv: info.Equiv,
}}
}
// phRun builds a self-closing placeholder, e.g. <br/> or a variable token.
func (r *Reader) phRun(semType, subType, id, nativeMarkup string) model.Run {
info := r.vocab.LookupOrFallback(semType)
return model.Run{Ph: &model.PlaceholderRun{
ID: id,
Type: semType,
SubType: subType,
Data: nativeMarkup,
Disp: info.Display.Placeholder, // "[BR/]"
Equiv: info.Equiv, // "\n" for struct:break
Constraints: &model.RunConstraints{
Deletable: info.Constraints.Deletable,
Cloneable: info.Constraints.Cloneable,
Reorderable: info.Constraints.Reorderable,
},
}}
}
Ḿàþþîñĝ ñàţîṽé éļéḿéñţš ţö šéḿàñţîç ţýþéš
Éàçĥ ƒöŕḿàţ ḿàþš îţš ñàţîṽé çöñšţŕüçţš ţö šéḿàñţîç ţýþéš. Ţĥé ĤŢḾĻ ŕéàđéŕ ķéýš à ñàḿé → ţýþé ḿàþ öñ ţĥé éļéḿéñţ ñàḿé:
var htmlSemanticTypes = map[string]string{
"b": "fmt:bold", "strong": "fmt:bold",
"i": "fmt:italic", "em": "fmt:italic",
"u": "fmt:underline", "s": "fmt:strikethrough",
"a": "link:hyperlink", "code": "fmt:code",
"br": "struct:break", "img": "media:image",
"sub": "fmt:subscript", "sup": "fmt:superscript", "mark": "fmt:highlight",
}
Ţĥé Ḿàŕķđöŵñ ŕéàđéŕ ĥàš ñö šüçĥ ḿàþ. Îţ šŵîţçĥéš öñ ĝöļđḿàŕķ ÀŠŢ ñöđé ţýþéš àñđ
àššîĝñš ţĥé šéḿàñţîç ţýþé þéŕ ñöđé ƃéƒöŕé çàļļîñĝ r.vocab.LookupOrFallback(…),
ŕéšöļṽîñĝ ţö ţĥé šàḿé ṽöçàƃüļàŕý ţýþéš:
| Ḿàŕķđöŵñ çöñšţŕüçţ | Šéḿàñţîç ţýþé |
|---|---|
šţŕöñĝ éḿþĥàšîš (ast.Emphasis ļéṽéļ 2) | fmt:bold |
éḿþĥàšîš (ast.Emphasis ļéṽéļ 1) | fmt:italic |
| îñļîñé çöđé | fmt:code |
| ļîñķ | link:hyperlink |
| îḿàĝé | link:image |
À šöƒţ ļîñé ƃŕéàķ îš ñöţ à ŕüñ: îţ îš éḿîţţéđ àš îñļîñé ţéẋţ çöñţîñüàţîöñ (šéé
softBreakContinuation), ñöţ à struct:break þļàçéĥöļđéŕ.
ŠüƃŢýþé çöñṽéñţîöñš
Ţĥé SubType ƒîéļđ ŕéçöŕđš ƒöŕḿàţ-šþéçîƒîç þŕöṽéñàñçé üšîñĝ à þŕéƒîẋ
çöñṽéñţîöñ: html: (html:b, html:span), md: (md:strong), xlf:
(xlf:var), docx: (docx:w:b). Çüšţöḿ ƒöŕḿàţš šĥöüļđ üšé à ŕéṽéŕšé-đöḿàîñ
þŕéƒîẋ: com.acme:custom-tag.
Çŕéàţîñĝ à çüšţöḿ ṽöçàƃüļàŕý
1. Çŕéàţé ţĥé ĴŠÖÑ ƒîļé
Çŕéàţé à ĴŠÖÑ ƒîļé üñđéŕ core/model/vocabularies/:
{
"name": "my-domain",
"version": "1.0",
"extends": "common-formatting",
"types": {
"domain:widget": {
"category": "domain",
"label": "Widget",
"html": { "placeholder": "<span class=\"widget\"/>" },
"display": { "placeholder": "[WIDGET]" },
"chipLabel": { "placeholder": "wgt" },
"color": {
"bg": "rgba(168,85,247,0.15)",
"border": "rgba(168,85,247,0.5)",
"text": "rgb(168,85,247)"
},
"equiv": "",
"constraints": { "deletable": false, "cloneable": false, "reorderable": true }
}
}
}
2. Ļöàđ îţ îñţö ţĥé ŕéĝîšţŕý
LoadDefaults() ļöàđš ţĥé éḿƃéđđéđ ṽöçàƃüļàŕîéš. Ţö àđđ öñé àţ ŕüñţîḿé:
vocab := model.NewVocabularyRegistry()
vocab.LoadDefaults()
customData, _ := os.ReadFile("my-domain.json")
vocab.Load(customData)
3. Ḿàþ îţ îñ ýöüŕ ŕéàđéŕ
Àđđ ţĥé ñéŵ ţýþé ţö ýöüŕ ƒöŕḿàţ ŕéàđéŕ'š šéḿàñţîç ţýþé ḿàþþîñĝ:
var myFormatSemanticTypes = map[string]string{
"widget": "domain:widget",
}
ŠþàñÇļàššîƒý ţööļ
Ƒöŕ ƒöŕḿàţš ţĥàţ đö ñöţ þéŕƒöŕḿ ƒüļļ šéḿàñţîç çļàššîƒîçàţîöñ (ƒöŕ éẋàḿþļé, ŵĥéñ
çöñţéñţ àŕŕîṽéš ṽîà ţĥé Okapi ƃŕîđĝé), ţĥé span-classify ţööļ ŕéçļàššîƒîéš
ĝéñéŕîç code:markup îñļîñé-çöđé ŕüñš (Ph / PcOpen / PcClose) îñţö
þŕöþéŕ šéḿàñţîç ţýþéš:
tool := tools.NewSpanClassifyTool(&tools.SpanClassifyConfig{})
Îţ àþþļîéš šţŕàţéĝîéš îñ öŕđéŕ: çĥéçķ ţĥé ŕüñ'š SubType àĝàîñšţ ķñöŵñ Okapi
ţýþé šţŕîñĝš, þàŕšé Data ƒöŕ àñ ĤŢḾĻ éļéḿéñţ ñàḿé, ļööķ ţĥàţ ñàḿé üþ îñ ţĥé
šéḿàñţîç ţýþé ḿàþ, àñđ öţĥéŕŵîšé ļéàṽé ţĥé ŕüñ àš code:markup. Ţĥé ţööļ ñàḿé
îš ŕéţàîñéđ ƒöŕ ƃàçķŵàŕđš çöḿþàţîƃîļîţý ŵîţĥ éẋîšţîñĝ ƒļöŵ đéƒîñîţîöñš.
Ţéšţîñĝ ṽöçàƃüļàŕîéš
func TestMyVocabulary(t *testing.T) {
vocab := model.NewVocabularyRegistry()
require.NoError(t, vocab.LoadDefaults())
info := vocab.Lookup("fmt:bold")
require.NotNil(t, info)
assert.Equal(t, "formatting", info.Category)
assert.True(t, info.Constraints.Deletable)
unknown := vocab.LookupOrFallback("custom:unknown")
require.NotNil(t, unknown)
assert.True(t, unknown.Constraints.Deletable) // fallback rendering
}
Ƃéšţ þŕàçţîçéš
- Üšé éẋîšţîñĝ ţýþéš ŵĥéñ þöššîƃļé. Ḿàþ ţö
fmt:boldŕàţĥéŕ ţĥàñ çŕéàţîñĝmy-format:bold. - Šéţ çöñšţŕàîñţš çöñšéŕṽàţîṽéļý. Ḿàŕķ çöđé ţöķéñš ñöñ-đéļéţàƃļé; ƒöŕḿàţţîñĝ ƒüļļý ƒļéẋîƃļé.
- Ķééþ ṽöçàƃüļàŕîéš šḿàļļ. Öñļý àđđ ţýþéš ŵîţĥ đîšţîñçţ ŕéñđéŕîñĝ öŕ çöñšţŕàîñţ ñééđš.
- Ţéšţ ŕöüñđţŕîþ ƒîđéļîţý. Ṽöçàƃüļàŕý ţýþéš àƒƒéçţ ŕéñđéŕîñĝ, ƃüţ éàçĥ ŕüñ'š
Datađŕîṽéš öüţþüţ, šö ṽéŕîƒý ƃöţĥ. - Éẋţéñđ ŕàţĥéŕ ţĥàñ ŕéþļàçé. Üšé
extendsţö ƃüîļđ öñcommon-formatting.
Ŕéļàţéđ ŕéàđîñĝ
- Ṽöçàƃüļàŕîéš: ţĥé çöñçéþţ àñđ ƃüîļţ-îñ ṽöçàƃüļàŕîéš.
- Îḿþļéḿéñţîñĝ à Ƒöŕḿàţ: ƃüîļđîñĝ ŕéàđéŕš àñđ ŵŕîţéŕš.
- Îñļîñé Ƒöŕḿàţţîñĝ: ţĥé îñļîñé-çöđé ḿöđéļ îñ ţĥé çöñţéñţ ḿöđéļ.