Zettelstore Client

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From v0.13.0 To trunk

2023-09-25
14:57
Add constant for HTTP query key "tag" ... (Leaf check-in: 5971389287 user: stern tags: trunk)
2023-09-15
17:44
Fix typo ... (check-in: f535d4cb15 user: stern tags: trunk)
2023-08-07
15:17
Remove support for JSON encoding ... (check-in: 0628873da8 user: stern tags: trunk)
13:44
Version 0.13.0 ... (check-in: 92d8dd7df8 user: stern tags: trunk, release, v0.13.0)
10:22
client: use sx for creating/updating zettel ... (check-in: 4ce083f28b user: stern tags: trunk)

Changes to api/const.go.

36
37
38
39
40
41
42
43

44
45
46
47
48
49
50
51
52
53
54
55
	ZidInfoTemplate   = ZettelID("00000000010402")
	ZidFormTemplate   = ZettelID("00000000010403")
	ZidRenameTemplate = ZettelID("00000000010404")
	ZidDeleteTemplate = ZettelID("00000000010405")
	ZidErrorTemplate  = ZettelID("00000000010700")

	// WebUI sxn code zettel are in the range 19000..19999
	ZidSxnTemplate = ZettelID("00000000019100")


	// CSS-related zettel are in the range 20000..29999
	ZidBaseCSS    = ZettelID("00000000020001")
	ZidUserCSS    = ZettelID("00000000025001")
	ZidRoleCSSMap = ZettelID("00000000029000") // Maps roles to CSS zettel, which should be in the range 29001..29999.

	// WebUI JS zettel are in the range 30000..39999

	// WebUI image zettel are in the range 40000..49999
	ZidEmoji = ZettelID("00000000040001")

	// Other sxn code zettel are in the range 50000..59999







|
>


|
|
<







36
37
38
39
40
41
42
43
44
45
46
47
48

49
50
51
52
53
54
55
	ZidInfoTemplate   = ZettelID("00000000010402")
	ZidFormTemplate   = ZettelID("00000000010403")
	ZidRenameTemplate = ZettelID("00000000010404")
	ZidDeleteTemplate = ZettelID("00000000010405")
	ZidErrorTemplate  = ZettelID("00000000010700")

	// WebUI sxn code zettel are in the range 19000..19999
	ZidSxnStart = ZettelID("00000000019000")
	ZidSxnBase  = ZettelID("00000000019990")

	// CSS-related zettel are in the range 20000..29999
	ZidBaseCSS = ZettelID("00000000020001")
	ZidUserCSS = ZettelID("00000000025001")


	// WebUI JS zettel are in the range 30000..39999

	// WebUI image zettel are in the range 40000..49999
	ZidEmoji = ZettelID("00000000040001")

	// Other sxn code zettel are in the range 50000..59999
121
122
123
124
125
126
127

128
129
130
131
132
133
134

// Predefined Metadata values
const (
	ValueFalse             = "false"
	ValueTrue              = "true"
	ValueLangEN            = "en"
	ValueRoleConfiguration = "configuration"

	ValueRoleZettel        = "zettel"
	ValueSyntaxCSS         = "css"
	ValueSyntaxDraw        = "draw"
	ValueSyntaxGif         = "gif"
	ValueSyntaxHTML        = "html"
	ValueSyntaxMarkdown    = "markdown"
	ValueSyntaxMD          = "md"







>







121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

// Predefined Metadata values
const (
	ValueFalse             = "false"
	ValueTrue              = "true"
	ValueLangEN            = "en"
	ValueRoleConfiguration = "configuration"
	ValueRoleTag           = "tag"
	ValueRoleZettel        = "zettel"
	ValueSyntaxCSS         = "css"
	ValueSyntaxDraw        = "draw"
	ValueSyntaxGif         = "gif"
	ValueSyntaxHTML        = "html"
	ValueSyntaxMarkdown    = "markdown"
	ValueSyntaxMD          = "md"
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
	QueryKeyCommand   = "cmd"
	QueryKeyEncoding  = "enc"
	QueryKeyParseOnly = "parseonly"
	QueryKeyPart      = "part"
	QueryKeyPhrase    = "phrase"
	QueryKeyQuery     = "q"
	QueryKeySeed      = "_seed"

)

// Supported encoding values.
const (
	EncodingHTML  = "html"
	EncodingMD    = "md"
	EncodingSHTML = "shtml"
	EncodingSz    = "sz"
	EncodingText  = "text"
	EncodingZMK   = "zmk"

	EncodingPlain = "plain"
	EncodingData  = "data"
	EncodingJson  = "json"
)

var mapEncodingEnum = map[string]EncodingEnum{
	EncodingHTML:  EncoderHTML,
	EncodingMD:    EncoderMD,
	EncodingSHTML: EncoderSHTML,
	EncodingSz:    EncoderSz,
	EncodingText:  EncoderText,
	EncodingZMK:   EncoderZmk,

	EncodingPlain: EncoderPlain,
	EncodingData:  EncoderData,
	EncodingJson:  EncoderJson,
}
var mapEnumEncoding = map[EncodingEnum]string{}

func init() {
	for k, v := range mapEncodingEnum {
		mapEnumEncoding[v] = k
	}







>













<












<







164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

185
186
187
188
189
190
191
192
193
194
195
196

197
198
199
200
201
202
203
	QueryKeyCommand   = "cmd"
	QueryKeyEncoding  = "enc"
	QueryKeyParseOnly = "parseonly"
	QueryKeyPart      = "part"
	QueryKeyPhrase    = "phrase"
	QueryKeyQuery     = "q"
	QueryKeySeed      = "_seed"
	QueryKeyTag       = "tag"
)

// Supported encoding values.
const (
	EncodingHTML  = "html"
	EncodingMD    = "md"
	EncodingSHTML = "shtml"
	EncodingSz    = "sz"
	EncodingText  = "text"
	EncodingZMK   = "zmk"

	EncodingPlain = "plain"
	EncodingData  = "data"

)

var mapEncodingEnum = map[string]EncodingEnum{
	EncodingHTML:  EncoderHTML,
	EncodingMD:    EncoderMD,
	EncodingSHTML: EncoderSHTML,
	EncodingSz:    EncoderSz,
	EncodingText:  EncoderText,
	EncodingZMK:   EncoderZmk,

	EncodingPlain: EncoderPlain,
	EncodingData:  EncoderData,

}
var mapEnumEncoding = map[EncodingEnum]string{}

func init() {
	for k, v := range mapEncodingEnum {
		mapEnumEncoding[v] = k
	}
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
	EncoderSHTML
	EncoderSz
	EncoderText
	EncoderZmk

	EncoderPlain
	EncoderData
	EncoderJson
)

// String representation of an encoder key.
func (e EncodingEnum) String() string {
	if f, ok := mapEnumEncoding[e]; ok {
		return f
	}







<







222
223
224
225
226
227
228

229
230
231
232
233
234
235
	EncoderSHTML
	EncoderSz
	EncoderText
	EncoderZmk

	EncoderPlain
	EncoderData

)

// String representation of an encoder key.
func (e EncodingEnum) String() string {
	if f, ok := mapEnumEncoding[e]; ok {
		return f
	}

Changes to client/client_test.go.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
		}
		return
	}
}

func TestGetSzZettel(t *testing.T) {
	c := getClient()
	sf := sx.MakeMappedFactory()
	var zetSyms sz.ZettelSymbols
	zetSyms.InitializeZettelSymbols(sf)
	value, err := c.GetEvaluatedSz(context.Background(), api.ZidDefaultHome, api.PartContent, sf)
	if err != nil {
		t.Error(err)
		return
	}







|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
		}
		return
	}
}

func TestGetSzZettel(t *testing.T) {
	c := getClient()
	sf := sx.MakeMappedFactory(1024)
	var zetSyms sz.ZettelSymbols
	zetSyms.InitializeZettelSymbols(sf)
	value, err := c.GetEvaluatedSz(context.Background(), api.ZidDefaultHome, api.PartContent, sf)
	if err != nil {
		t.Error(err)
		return
	}

Changes to go.mod.

1
2
3
4
5
module zettelstore.de/client.fossil

go 1.20

require zettelstore.de/sx.fossil v0.0.0-20230727172325-adec5a7ba284


|

|
1
2
3
4
5
module zettelstore.de/client.fossil

go 1.21

require zettelstore.de/sx.fossil v0.0.0-20230915173519-fa23195f2b56

Changes to go.sum.

1
2
zettelstore.de/sx.fossil v0.0.0-20230727172325-adec5a7ba284 h1:26xwZWEjdyL3wObczc/PKugv0EY6mgSH5NUe5kYFCt4=
zettelstore.de/sx.fossil v0.0.0-20230727172325-adec5a7ba284/go.mod h1:nsWXVrQG8RNKtoXzEMrWXNMdnpfIDU6Hb0pk56KpVKE=
|
|
1
2
zettelstore.de/sx.fossil v0.0.0-20230915173519-fa23195f2b56 h1:Y1cw0BCmxDZMImE5c5jGDIOz1XdTpG5/GBBjMkZZUo8=
zettelstore.de/sx.fossil v0.0.0-20230915173519-fa23195f2b56/go.mod h1:Uw3OLM1ufOM4Xe0G51mvkTDUv2okd+HyDBMx+0ZG7ME=

Changes to sexp/sexp.go.

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

	"zettelstore.de/client.fossil/api"
	"zettelstore.de/sx.fossil"
)

// EncodeZettel transforms zettel data into a sx object.
func EncodeZettel(zettel api.ZettelData) sx.Object {
	sf := sx.MakeMappedFactory()
	return sx.MakeList(
		sf.MustMake("zettel"),
		meta2sz(zettel.Meta, sf),
		sx.MakeList(sf.MustMake("rights"), sx.Int64(int64(zettel.Rights))),
		sx.MakeList(sf.MustMake("encoding"), sx.MakeString(zettel.Encoding)),
		sx.MakeList(sf.MustMake("content"), sx.MakeString(zettel.Content)),
	)
}

func ParseZettel(obj sx.Object) (api.ZettelData, error) {
	vals, err := ParseList(obj, "ypppp")
	if err != nil {
		return api.ZettelData{}, err







|




|
|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

	"zettelstore.de/client.fossil/api"
	"zettelstore.de/sx.fossil"
)

// EncodeZettel transforms zettel data into a sx object.
func EncodeZettel(zettel api.ZettelData) sx.Object {
	sf := sx.MakeMappedFactory(1024)
	return sx.MakeList(
		sf.MustMake("zettel"),
		meta2sz(zettel.Meta, sf),
		sx.MakeList(sf.MustMake("rights"), sx.Int64(int64(zettel.Rights))),
		sx.MakeList(sf.MustMake("encoding"), sx.String(zettel.Encoding)),
		sx.MakeList(sf.MustMake("content"), sx.String(zettel.Content)),
	)
}

func ParseZettel(obj sx.Object) (api.ZettelData, error) {
	vals, err := ParseList(obj, "ypppp")
	if err != nil {
		return api.ZettelData{}, err
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
		Encoding: encVals[1].(sx.String).String(),
		Content:  contentVals[1].(sx.String).String(),
	}, nil
}

// EncodeMetaRights translates metadata/rights into a sx object.
func EncodeMetaRights(mr api.MetaRights) *sx.Pair {
	sf := sx.MakeMappedFactory()
	return sx.MakeList(
		sf.MustMake("list"),
		meta2sz(mr.Meta, sf),
		sx.MakeList(sf.MustMake("rights"), sx.Int64(int64(mr.Rights))),
	)
}

func meta2sz(m api.ZettelMeta, sf sx.SymbolFactory) sx.Object {
	result := sx.Nil().Cons(sf.MustMake("meta"))
	curr := result
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		val := sx.MakeList(sf.MustMake(k), sx.MakeString(m[k]))
		curr = curr.AppendBang(val)
	}
	return result
}

// ParseMeta translates the given list to metadata.
func ParseMeta(pair *sx.Pair) (api.ZettelMeta, error) {







|
















|







74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
		Encoding: encVals[1].(sx.String).String(),
		Content:  contentVals[1].(sx.String).String(),
	}, nil
}

// EncodeMetaRights translates metadata/rights into a sx object.
func EncodeMetaRights(mr api.MetaRights) *sx.Pair {
	sf := sx.MakeMappedFactory(1024)
	return sx.MakeList(
		sf.MustMake("list"),
		meta2sz(mr.Meta, sf),
		sx.MakeList(sf.MustMake("rights"), sx.Int64(int64(mr.Rights))),
	)
}

func meta2sz(m api.ZettelMeta, sf sx.SymbolFactory) sx.Object {
	result := sx.Nil().Cons(sf.MustMake("meta"))
	curr := result
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		val := sx.MakeList(sf.MustMake(k), sx.String(m[k]))
		curr = curr.AppendBang(val)
	}
	return result
}

// ParseMeta translates the given list to metadata.
func ParseMeta(pair *sx.Pair) (api.ZettelMeta, error) {
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
			return nil, ErrNoSpec
		}
		var val sx.Object
		var ok bool
		car := node.Car()
		switch spec[i] {
		case 'b':
			val, ok = sx.GetBoolean(car)
		case 'i':
			val, ok = car.(sx.Int64)
		case 'o':
			val, ok = car, true
		case 'p':
			val, ok = sx.GetPair(car)
		case 's':







|







153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
			return nil, ErrNoSpec
		}
		var val sx.Object
		var ok bool
		car := node.Car()
		switch spec[i] {
		case 'b':
			val, ok = sx.MakeBoolean(!sx.IsNil(car)), true
		case 'i':
			val, ok = car.(sx.Int64)
		case 'o':
			val, ok = car, true
		case 'p':
			val, ok = sx.GetPair(car)
		case 's':

Changes to sexp/sexp_test.go.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	"testing"

	"zettelstore.de/client.fossil/sexp"
	"zettelstore.de/sx.fossil"
)

func TestParseObject(t *testing.T) {
	if elems, err := sexp.ParseList(sx.MakeString("a"), "s"); err == nil {
		t.Error("expected an error, but got: ", elems)
	}
	if elems, err := sexp.ParseList(sx.Nil(), ""); err != nil {
		t.Error(err)
	} else if len(elems) != 0 {
		t.Error("Must be empty, but got:", elems)
	}
	if elems, err := sexp.ParseList(sx.Nil(), "b"); err == nil {
		t.Error("expected error, but got: ", elems)
	}

	if elems, err := sexp.ParseList(sx.MakeList(sx.MakeString("a")), "ss"); err == nil {
		t.Error("expected error, but got: ", elems)
	}
	if elems, err := sexp.ParseList(sx.MakeList(sx.MakeString("a")), ""); err == nil {
		t.Error("expected error, but got: ", elems)
	}
	if elems, err := sexp.ParseList(sx.MakeList(sx.MakeString("a")), "b"); err == nil {
		t.Error("expected error, but got: ", elems)
	}
	if elems, err := sexp.ParseList(sx.Cons(sx.Nil(), sx.MakeString("a")), "ps"); err == nil {
		t.Error("expected error, but got: ", elems)
	}

	if elems, err := sexp.ParseList(sx.MakeList(sx.MakeString("a")), "s"); err != nil {
		t.Error(err)
	} else if len(elems) != 1 {
		t.Error("length == 1, but got: ", elems)
	} else {
		_ = elems[0].(sx.String)
	}

}







|











|


|


|
|

|



|








14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	"testing"

	"zettelstore.de/client.fossil/sexp"
	"zettelstore.de/sx.fossil"
)

func TestParseObject(t *testing.T) {
	if elems, err := sexp.ParseList(sx.String("a"), "s"); err == nil {
		t.Error("expected an error, but got: ", elems)
	}
	if elems, err := sexp.ParseList(sx.Nil(), ""); err != nil {
		t.Error(err)
	} else if len(elems) != 0 {
		t.Error("Must be empty, but got:", elems)
	}
	if elems, err := sexp.ParseList(sx.Nil(), "b"); err == nil {
		t.Error("expected error, but got: ", elems)
	}

	if elems, err := sexp.ParseList(sx.MakeList(sx.String("a")), "ss"); err == nil {
		t.Error("expected error, but got: ", elems)
	}
	if elems, err := sexp.ParseList(sx.MakeList(sx.String("a")), ""); err == nil {
		t.Error("expected error, but got: ", elems)
	}
	if _, err := sexp.ParseList(sx.MakeList(sx.String("a")), "b"); err != nil {
		t.Error("expected [1], but got error: ", err)
	}
	if elems, err := sexp.ParseList(sx.Cons(sx.Nil(), sx.String("a")), "ps"); err == nil {
		t.Error("expected error, but got: ", elems)
	}

	if elems, err := sexp.ParseList(sx.MakeList(sx.String("a")), "s"); err != nil {
		t.Error(err)
	} else if len(elems) != 1 {
		t.Error("length == 1, but got: ", elems)
	} else {
		_ = elems[0].(sx.String)
	}

}

Changes to shtml/shtml.go.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
	"strings"

	"zettelstore.de/client.fossil/api"
	"zettelstore.de/client.fossil/attrs"
	"zettelstore.de/client.fossil/sz"
	"zettelstore.de/client.fossil/text"
	"zettelstore.de/sx.fossil"
	"zettelstore.de/sx.fossil/sxbuiltins/quote"
	"zettelstore.de/sx.fossil/sxeval"
	"zettelstore.de/sx.fossil/sxhtml"
)

// Transformer will transform a s-expression that encodes the zettel AST into an s-expression
// that represents HTML.
type Transformer struct {







<







18
19
20
21
22
23
24

25
26
27
28
29
30
31
	"strings"

	"zettelstore.de/client.fossil/api"
	"zettelstore.de/client.fossil/attrs"
	"zettelstore.de/client.fossil/sz"
	"zettelstore.de/client.fossil/text"
	"zettelstore.de/sx.fossil"

	"zettelstore.de/sx.fossil/sxeval"
	"zettelstore.de/sx.fossil/sxhtml"
)

// Transformer will transform a s-expression that encodes the zettel AST into an s-expression
// that represents HTML.
type Transformer struct {
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
	noteHx  *sx.Pair // Endnote as SxHTML
	attrs   *sx.Pair // attrs a-list
}

// NewTransformer creates a new transformer object.
func NewTransformer(headingOffset int, sf sx.SymbolFactory) *Transformer {
	if sf == nil {
		sf = sx.MakeMappedFactory()
	}
	return &Transformer{
		sf:            sf,
		rebinder:      nil,
		headingOffset: int64(headingOffset),
		symAttr:       sf.MustMake(sxhtml.NameSymAttr),
		symClass:      sf.MustMake("class"),







|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
	noteHx  *sx.Pair // Endnote as SxHTML
	attrs   *sx.Pair // attrs a-list
}

// NewTransformer creates a new transformer object.
func NewTransformer(headingOffset int, sf sx.SymbolFactory) *Transformer {
	if sf == nil {
		sf = sx.MakeMappedFactory(128)
	}
	return &Transformer{
		sf:            sf,
		rebinder:      nil,
		headingOffset: int64(headingOffset),
		symAttr:       sf.MustMake(sxhtml.NameSymAttr),
		symClass:      sf.MustMake("class"),
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
		return nil
	}
	plist := sx.Nil()
	keys := a.Keys()
	for i := len(keys) - 1; i >= 0; i-- {
		key := keys[i]
		if key != attrs.DefaultAttribute && tr.IsValidName(key) {
			plist = plist.Cons(sx.Cons(tr.Make(key), sx.MakeString(a[key])))
		}
	}
	if plist == nil {
		return nil
	}
	return plist.Cons(tr.symAttr)
}







|







89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
		return nil
	}
	plist := sx.Nil()
	keys := a.Keys()
	for i := len(keys) - 1; i >= 0; i-- {
		key := keys[i]
		if key != attrs.DefaultAttribute && tr.IsValidName(key) {
			plist = plist.Cons(sx.Cons(tr.Make(key), sx.String(a[key])))
		}
	}
	if plist == nil {
		return nil
	}
	return plist.Cons(tr.symAttr)
}
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
func (tr *Transformer) Transform(lst *sx.Pair) (*sx.Pair, error) {
	astSF := sx.FindSymbolFactory(lst)
	if astSF != nil {
		if astSF == tr.sf {
			panic("Invalid AST SymbolFactory")
		}
	} else {
		astSF = sx.MakeMappedFactory()
	}
	astEnv := sx.MakeRootEnvironment()
	engine := sxeval.MakeEngine(astSF, astEnv)
	quote.InstallQuoteSyntax(astEnv, astSF.MustMake(sz.NameSymQuote))
	te := TransformEnv{
		tr:      tr,
		astSF:   astSF,
		astEnv:  astEnv,
		err:     nil,
		textEnc: text.NewEncoder(astSF),
	}







|

|

|







111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
func (tr *Transformer) Transform(lst *sx.Pair) (*sx.Pair, error) {
	astSF := sx.FindSymbolFactory(lst)
	if astSF != nil {
		if astSF == tr.sf {
			panic("Invalid AST SymbolFactory")
		}
	} else {
		astSF = sx.MakeMappedFactory(1024)
	}
	astEnv := sxeval.MakeRootEnvironment(128) // approx: number of bindings in te.initialize()
	engine := sxeval.MakeEngine(astSF, astEnv)
	engine.SetQuote(astSF.MustMake(sz.NameSymQuote))
	te := TransformEnv{
		tr:      tr,
		astSF:   astSF,
		astEnv:  astEnv,
		err:     nil,
		textEnc: text.NewEncoder(astSF),
	}
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

// Endnotes returns a SHTML object with all collected endnotes.
func (tr *Transformer) Endnotes() *sx.Pair {
	if len(tr.endnotes) == 0 {
		return nil
	}
	result := sx.Nil().Cons(tr.Make("ol"))
	currResult := result.AppendBang(sx.Nil().Cons(sx.Cons(tr.symClass, sx.MakeString("zs-endnotes"))).Cons(tr.symAttr))
	for i, fni := range tr.endnotes {
		noteNum := strconv.Itoa(i + 1)
		noteID := tr.unique + noteNum

		attrs := fni.attrs.Cons(sx.Cons(tr.symClass, sx.MakeString("zs-endnote"))).
			Cons(sx.Cons(tr.Make("value"), sx.MakeString(noteNum))).
			Cons(sx.Cons(tr.Make("id"), sx.MakeString("fn:"+noteID))).
			Cons(sx.Cons(tr.Make("role"), sx.MakeString("doc-endnote"))).
			Cons(tr.symAttr)

		backref := sx.Nil().Cons(sx.MakeString("\u21a9\ufe0e")).
			Cons(sx.Nil().
				Cons(sx.Cons(tr.symClass, sx.MakeString("zs-endnote-backref"))).
				Cons(sx.Cons(tr.Make("href"), sx.MakeString("#fnref:"+noteID))).
				Cons(sx.Cons(tr.Make("role"), sx.MakeString("doc-backlink"))).
				Cons(tr.symAttr)).
			Cons(tr.symA)

		li := sx.Nil().Cons(tr.Make("li"))
		li.AppendBang(attrs).
			ExtendBang(fni.noteHx).
			AppendBang(sx.MakeString(" ")).AppendBang(backref)
		currResult = currResult.AppendBang(li)
	}
	tr.endnotes = nil
	return result
}

// TransformEnv is the environment where the actual transformation takes places.
type TransformEnv struct {
	tr          *Transformer
	astSF       sx.SymbolFactory
	astEnv      sx.Environment
	err         error
	textEnc     *text.Encoder
	symNoEscape *sx.Symbol
	symAttr     *sx.Symbol
	symA        *sx.Symbol
	symSpan     *sx.Symbol
	symP        *sx.Symbol







|




|
|
|
|


|

|
|
|






|










|







158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

// Endnotes returns a SHTML object with all collected endnotes.
func (tr *Transformer) Endnotes() *sx.Pair {
	if len(tr.endnotes) == 0 {
		return nil
	}
	result := sx.Nil().Cons(tr.Make("ol"))
	currResult := result.AppendBang(sx.Nil().Cons(sx.Cons(tr.symClass, sx.String("zs-endnotes"))).Cons(tr.symAttr))
	for i, fni := range tr.endnotes {
		noteNum := strconv.Itoa(i + 1)
		noteID := tr.unique + noteNum

		attrs := fni.attrs.Cons(sx.Cons(tr.symClass, sx.String("zs-endnote"))).
			Cons(sx.Cons(tr.Make("value"), sx.String(noteNum))).
			Cons(sx.Cons(tr.Make("id"), sx.String("fn:"+noteID))).
			Cons(sx.Cons(tr.Make("role"), sx.String("doc-endnote"))).
			Cons(tr.symAttr)

		backref := sx.Nil().Cons(sx.String("\u21a9\ufe0e")).
			Cons(sx.Nil().
				Cons(sx.Cons(tr.symClass, sx.String("zs-endnote-backref"))).
				Cons(sx.Cons(tr.Make("href"), sx.String("#fnref:"+noteID))).
				Cons(sx.Cons(tr.Make("role"), sx.String("doc-backlink"))).
				Cons(tr.symAttr)).
			Cons(tr.symA)

		li := sx.Nil().Cons(tr.Make("li"))
		li.AppendBang(attrs).
			ExtendBang(fni.noteHx).
			AppendBang(sx.String(" ")).AppendBang(backref)
		currResult = currResult.AppendBang(li)
	}
	tr.endnotes = nil
	return result
}

// TransformEnv is the environment where the actual transformation takes places.
type TransformEnv struct {
	tr          *Transformer
	astSF       sx.SymbolFactory
	astEnv      sxeval.Environment
	err         error
	textEnc     *text.Encoder
	symNoEscape *sx.Symbol
	symAttr     *sx.Symbol
	symA        *sx.Symbol
	symSpan     *sx.Symbol
	symP        *sx.Symbol
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

		if result, isPair := sx.GetPair(args[4]); isPair && result != nil {
			if len(a) > 0 {
				result = result.Cons(te.transformAttribute(a))
			}
			return result.Cons(te.Make("h" + level))
		}
		return sx.MakeList(te.Make("h"+level), sx.MakeString("<MISSING TEXT>"))
	})
	te.bind(sz.NameSymThematic, 0, func(args []sx.Object) sx.Object {
		result := sx.Nil()
		if len(args) > 0 {
			if attrList := te.getList(args[0]); attrList != nil {
				result = result.Cons(te.transformAttribute(sz.GetAttributes(attrList)))
			}







|







288
289
290
291
292
293
294
295
296
297
298
299
300
301
302

		if result, isPair := sx.GetPair(args[4]); isPair && result != nil {
			if len(a) > 0 {
				result = result.Cons(te.transformAttribute(a))
			}
			return result.Cons(te.Make("h" + level))
		}
		return sx.MakeList(te.Make("h"+level), sx.String("<MISSING TEXT>"))
	})
	te.bind(sz.NameSymThematic, 0, func(args []sx.Object) sx.Object {
		result := sx.Nil()
		if len(args) > 0 {
			if attrList := te.getList(args[0]); attrList != nil {
				result = result.Cons(te.transformAttribute(sz.GetAttributes(attrList)))
			}
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
	te.bind(sz.NameSymRegionQuote, 2, te.makeRegionFn(te.Make("blockquote"), false))
	te.bind(sz.NameSymRegionVerse, 2, te.makeRegionFn(te.Make("div"), false))

	te.bind(sz.NameSymVerbatimComment, 1, func(args []sx.Object) sx.Object {
		if te.getAttributes(args[0]).HasDefault() {
			if len(args) > 1 {
				if s := te.getString(args[1]); s != "" {
					t := sx.MakeString(s.String())
					return sx.Nil().Cons(t).Cons(te.Make(sxhtml.NameSymBlockComment))
				}
			}
		}
		return nil
	})

	te.bind(sz.NameSymVerbatimEval, 2, func(args []sx.Object) sx.Object {
		return te.transformVerbatim(te.getAttributes(args[0]).AddClass("zs-eval"), te.getString(args[1]))
	})
	te.bind(sz.NameSymVerbatimHTML, 2, te.transformHTML)
	te.bind(sz.NameSymVerbatimMath, 2, func(args []sx.Object) sx.Object {
		return te.transformVerbatim(te.getAttributes(args[0]).AddClass("zs-math"), te.getString(args[1]))
	})
	te.bind(sz.NameSymVerbatimProg, 2, func(args []sx.Object) sx.Object {
		a := te.getAttributes(args[0])
		content := te.getString(args[1])
		if a.HasDefault() {
			content = sx.MakeString(visibleReplacer.Replace(content.String()))
		}
		return te.transformVerbatim(a, content)
	})
	te.bind(sz.NameSymVerbatimZettel, 0, func([]sx.Object) sx.Object { return sx.Nil() })

	te.bind(sz.NameSymBLOB, 3, func(args []sx.Object) sx.Object {
		return te.transformBLOB(te.getList(args[0]), te.getString(args[1]), te.getString(args[2]))







|


















|







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
	te.bind(sz.NameSymRegionQuote, 2, te.makeRegionFn(te.Make("blockquote"), false))
	te.bind(sz.NameSymRegionVerse, 2, te.makeRegionFn(te.Make("div"), false))

	te.bind(sz.NameSymVerbatimComment, 1, func(args []sx.Object) sx.Object {
		if te.getAttributes(args[0]).HasDefault() {
			if len(args) > 1 {
				if s := te.getString(args[1]); s != "" {
					t := sx.String(s.String())
					return sx.Nil().Cons(t).Cons(te.Make(sxhtml.NameSymBlockComment))
				}
			}
		}
		return nil
	})

	te.bind(sz.NameSymVerbatimEval, 2, func(args []sx.Object) sx.Object {
		return te.transformVerbatim(te.getAttributes(args[0]).AddClass("zs-eval"), te.getString(args[1]))
	})
	te.bind(sz.NameSymVerbatimHTML, 2, te.transformHTML)
	te.bind(sz.NameSymVerbatimMath, 2, func(args []sx.Object) sx.Object {
		return te.transformVerbatim(te.getAttributes(args[0]).AddClass("zs-math"), te.getString(args[1]))
	})
	te.bind(sz.NameSymVerbatimProg, 2, func(args []sx.Object) sx.Object {
		a := te.getAttributes(args[0])
		content := te.getString(args[1])
		if a.HasDefault() {
			content = sx.String(visibleReplacer.Replace(content.String()))
		}
		return te.transformVerbatim(a, content)
	})
	te.bind(sz.NameSymVerbatimZettel, 0, func([]sx.Object) sx.Object { return sx.Nil() })

	te.bind(sz.NameSymBLOB, 3, func(args []sx.Object) sx.Object {
		return te.transformBLOB(te.getList(args[0]), te.getString(args[1]), te.getString(args[2]))
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
		if refValue := te.getString(ref.Tail().Car()); refValue != "" {
			if te.astSF.MustMake(sz.NameSymRefStateExternal).IsEqual(refKind) {
				a := te.getAttributes(args[0]).Set("src", refValue.String()).AddClass("external")
				return sx.Nil().Cons(sx.Nil().Cons(te.transformAttribute(a)).Cons(te.Make("img"))).Cons(te.symP)
			}
			return sx.MakeList(
				te.Make(sxhtml.NameSymInlineComment),
				sx.MakeString("transclude"),
				refKind,
				sx.MakeString("->"),
				refValue,
			)
		}
		return sx.MakeList(args...)
	})
}








|

|







425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
		if refValue := te.getString(ref.Tail().Car()); refValue != "" {
			if te.astSF.MustMake(sz.NameSymRefStateExternal).IsEqual(refKind) {
				a := te.getAttributes(args[0]).Set("src", refValue.String()).AddClass("external")
				return sx.Nil().Cons(sx.Nil().Cons(te.transformAttribute(a)).Cons(te.Make("img"))).Cons(te.symP)
			}
			return sx.MakeList(
				te.Make(sxhtml.NameSymInlineComment),
				sx.String("transclude"),
				refKind,
				sx.String("->"),
				refValue,
			)
		}
		return sx.MakeList(args...)
	})
}

514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
}

func (te *TransformEnv) bindInlines() {
	te.bind(sz.NameSymInline, 0, listArgs)
	te.bind(sz.NameSymText, 1, func(args []sx.Object) sx.Object { return te.getString(args[0]) })
	te.bind(sz.NameSymSpace, 0, func(args []sx.Object) sx.Object {
		if len(args) == 0 {
			return sx.MakeString(" ")
		}
		return te.getString(args[0])
	})
	te.bind(sz.NameSymSoft, 0, func([]sx.Object) sx.Object { return sx.MakeString(" ") })
	brSym := te.Make("br")
	te.bind(sz.NameSymHard, 0, func([]sx.Object) sx.Object { return sx.Nil().Cons(brSym) })

	te.bind(sz.NameSymLinkInvalid, 2, func(args []sx.Object) sx.Object {
		// a := te.getAttributes(args)
		var inline *sx.Pair
		if len(args) > 2 {







|



|







513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
}

func (te *TransformEnv) bindInlines() {
	te.bind(sz.NameSymInline, 0, listArgs)
	te.bind(sz.NameSymText, 1, func(args []sx.Object) sx.Object { return te.getString(args[0]) })
	te.bind(sz.NameSymSpace, 0, func(args []sx.Object) sx.Object {
		if len(args) == 0 {
			return sx.String(" ")
		}
		return te.getString(args[0])
	})
	te.bind(sz.NameSymSoft, 0, func([]sx.Object) sx.Object { return sx.String(" ") })
	brSym := te.Make("br")
	te.bind(sz.NameSymHard, 0, func([]sx.Object) sx.Object { return sx.Nil().Cons(brSym) })

	te.bind(sz.NameSymLinkInvalid, 2, func(args []sx.Object) sx.Object {
		// a := te.getAttributes(args)
		var inline *sx.Pair
		if len(args) > 2 {
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581

	te.bind(sz.NameSymEmbed, 3, func(args []sx.Object) sx.Object {
		ref := te.getList(args[1])
		syntax := te.getString(args[2])
		if syntax == api.ValueSyntaxSVG {
			embedAttr := sx.MakeList(
				te.symAttr,
				sx.Cons(te.Make("type"), sx.MakeString("image/svg+xml")),
				sx.Cons(te.Make("src"), sx.MakeString("/"+te.getString(ref.Tail()).String()+".svg")),
			)
			return sx.MakeList(
				te.Make("figure"),
				sx.MakeList(
					te.Make("embed"),
					embedAttr,
				),







|
|







565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580

	te.bind(sz.NameSymEmbed, 3, func(args []sx.Object) sx.Object {
		ref := te.getList(args[1])
		syntax := te.getString(args[2])
		if syntax == api.ValueSyntaxSVG {
			embedAttr := sx.MakeList(
				te.symAttr,
				sx.Cons(te.Make("type"), sx.String("image/svg+xml")),
				sx.Cons(te.Make("src"), sx.String("/"+te.getString(ref.Tail()).String()+".svg")),
			)
			return sx.MakeList(
				te.Make("figure"),
				sx.MakeList(
					te.Make("embed"),
					embedAttr,
				),
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
		}
		return sx.MakeList(te.Make("img"), te.transformAttribute(a))
	})
	te.bind(sz.NameSymEmbedBLOB, 3, func(args []sx.Object) sx.Object {
		a, syntax, data := te.getAttributes(args[0]), te.getString(args[1]), te.getString(args[2])
		summary, _ := a.Get(api.KeySummary)
		return te.transformBLOB(
			sx.MakeList(te.astSF.MustMake(sz.NameSymInline), sx.MakeString(summary)),
			syntax,
			data,
		)
	})

	te.bind(sz.NameSymCite, 2, func(args []sx.Object) sx.Object {
		result := sx.Nil()
		if key := te.getString(args[1]); key != "" {
			if len(args) > 2 {
				result = sx.MakeList(args[2:]...).Cons(sx.MakeString(", "))
			}
			result = result.Cons(key)
		}
		if a := te.getAttributes(args[0]); len(a) > 0 {
			result = result.Cons(te.transformAttribute(a))
		}
		if result == nil {







|









|







589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
		}
		return sx.MakeList(te.Make("img"), te.transformAttribute(a))
	})
	te.bind(sz.NameSymEmbedBLOB, 3, func(args []sx.Object) sx.Object {
		a, syntax, data := te.getAttributes(args[0]), te.getString(args[1]), te.getString(args[2])
		summary, _ := a.Get(api.KeySummary)
		return te.transformBLOB(
			sx.MakeList(te.astSF.MustMake(sz.NameSymInline), sx.String(summary)),
			syntax,
			data,
		)
	})

	te.bind(sz.NameSymCite, 2, func(args []sx.Object) sx.Object {
		result := sx.Nil()
		if key := te.getString(args[1]); key != "" {
			if len(args) > 2 {
				result = sx.MakeList(args[2:]...).Cons(sx.String(", "))
			}
			result = result.Cons(key)
		}
		if a := te.getAttributes(args[0]); len(a) > 0 {
			result = result.Cons(te.transformAttribute(a))
		}
		if result == nil {
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
		text, isPair := sx.GetPair(args[1])
		if !isPair {
			return sx.Nil()
		}
		te.tr.endnotes = append(te.tr.endnotes, endnoteInfo{noteAST: text, noteHx: nil, attrs: attrPlist})
		noteNum := strconv.Itoa(len(te.tr.endnotes))
		noteID := te.tr.unique + noteNum
		hrefAttr := sx.Nil().Cons(sx.Cons(te.Make("role"), sx.MakeString("doc-noteref"))).
			Cons(sx.Cons(te.Make("href"), sx.MakeString("#fn:"+noteID))).
			Cons(sx.Cons(te.tr.symClass, sx.MakeString("zs-noteref"))).
			Cons(te.symAttr)
		href := sx.Nil().Cons(sx.MakeString(noteNum)).Cons(hrefAttr).Cons(te.symA)
		supAttr := sx.Nil().Cons(sx.Cons(te.Make("id"), sx.MakeString("fnref:"+noteID))).Cons(te.symAttr)
		return sx.Nil().Cons(href).Cons(supAttr).Cons(te.Make("sup"))
	})

	te.bind(sz.NameSymFormatDelete, 1, te.makeFormatFn("del"))
	te.bind(sz.NameSymFormatEmph, 1, te.makeFormatFn("em"))
	te.bind(sz.NameSymFormatInsert, 1, te.makeFormatFn("ins"))
	te.bind(sz.NameSymFormatQuote, 1, te.transformQuote)







|
|
|

|
|







638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
		text, isPair := sx.GetPair(args[1])
		if !isPair {
			return sx.Nil()
		}
		te.tr.endnotes = append(te.tr.endnotes, endnoteInfo{noteAST: text, noteHx: nil, attrs: attrPlist})
		noteNum := strconv.Itoa(len(te.tr.endnotes))
		noteID := te.tr.unique + noteNum
		hrefAttr := sx.Nil().Cons(sx.Cons(te.Make("role"), sx.String("doc-noteref"))).
			Cons(sx.Cons(te.Make("href"), sx.String("#fn:"+noteID))).
			Cons(sx.Cons(te.tr.symClass, sx.String("zs-noteref"))).
			Cons(te.symAttr)
		href := sx.Nil().Cons(sx.String(noteNum)).Cons(hrefAttr).Cons(te.symA)
		supAttr := sx.Nil().Cons(sx.Cons(te.Make("id"), sx.String("fnref:"+noteID))).Cons(te.symAttr)
		return sx.Nil().Cons(href).Cons(supAttr).Cons(te.Make("sup"))
	})

	te.bind(sz.NameSymFormatDelete, 1, te.makeFormatFn("del"))
	te.bind(sz.NameSymFormatEmph, 1, te.makeFormatFn("em"))
	te.bind(sz.NameSymFormatInsert, 1, te.makeFormatFn("ins"))
	te.bind(sz.NameSymFormatQuote, 1, te.transformQuote)
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
	}
	a = setProgLang(a)
	literal := te.getString(args[1]).String()
	if a.HasDefault() {
		a = a.RemoveDefault()
		literal = visibleReplacer.Replace(literal)
	}
	res := sx.Nil().Cons(sx.MakeString(literal))
	if len(a) > 0 {
		res = res.Cons(te.transformAttribute(a))
	}
	return res.Cons(sym)
}

func setProgLang(a attrs.Attributes) attrs.Attributes {







|







734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
	}
	a = setProgLang(a)
	literal := te.getString(args[1]).String()
	if a.HasDefault() {
		a = a.RemoveDefault()
		literal = visibleReplacer.Replace(literal)
	}
	res := sx.Nil().Cons(sx.String(literal))
	if len(a) > 0 {
		res = res.Cons(te.transformAttribute(a))
	}
	return res.Cons(sym)
}

func setProgLang(a attrs.Attributes) attrs.Attributes {
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
	}
	switch syntax {
	case "":
		return sx.Nil()
	case api.ValueSyntaxSVG:
		return sx.Nil().Cons(sx.Nil().Cons(data).Cons(te.symNoEscape)).Cons(te.symP)
	default:
		imgAttr := sx.Nil().Cons(sx.Cons(te.Make("src"), sx.MakeString("data:image/"+syntax.String()+";base64,"+data.String())))
		var sb strings.Builder
		te.flattenText(&sb, description)
		if d := sb.String(); d != "" {
			imgAttr = imgAttr.Cons(sx.Cons(te.Make("alt"), sx.MakeString(d)))
		}
		return sx.Nil().Cons(sx.Nil().Cons(imgAttr.Cons(te.symAttr)).Cons(te.Make("img"))).Cons(te.symP)
	}
}

func (te *TransformEnv) flattenText(sb *strings.Builder, lst *sx.Pair) {
	for elem := lst; elem != nil; elem = elem.Tail() {







|



|







765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
	}
	switch syntax {
	case "":
		return sx.Nil()
	case api.ValueSyntaxSVG:
		return sx.Nil().Cons(sx.Nil().Cons(data).Cons(te.symNoEscape)).Cons(te.symP)
	default:
		imgAttr := sx.Nil().Cons(sx.Cons(te.Make("src"), sx.String("data:image/"+syntax.String()+";base64,"+data.String())))
		var sb strings.Builder
		te.flattenText(&sb, description)
		if d := sb.String(); d != "" {
			imgAttr = imgAttr.Cons(sx.Cons(te.Make("alt"), sx.String(d)))
		}
		return sx.Nil().Cons(sx.Nil().Cons(imgAttr.Cons(te.symAttr)).Cons(te.Make("img"))).Cons(te.symP)
	}
}

func (te *TransformEnv) flattenText(sb *strings.Builder, lst *sx.Pair) {
	for elem := lst; elem != nil; elem = elem.Tail() {

Changes to sz/const_test.go.

1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20


21
22
23
24
25

26

//-----------------------------------------------------------------------------
// Copyright (c) 2023-present Detlef Stern
//
// This file is part of zettelstore-client.
//
// Zettelstore client is licensed under the latest version of the EUPL
// (European Union Public License). Please see file LICENSE.txt for your rights
// and obligations under this license.
//-----------------------------------------------------------------------------

package sz_test

import (

	"testing"

	"zettelstore.de/client.fossil/sz"
	"zettelstore.de/sx.fossil"
)

func BenchmarkInitializeZettelSymbols(b *testing.B) {


	sf := sx.MakeMappedFactory()
	for i := 0; i < b.N; i++ {
		var zs sz.ZettelSymbols
		zs.InitializeZettelSymbols(sf)
	}

}














>






|
>
>
|
|
|
|
|
>
|
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//-----------------------------------------------------------------------------
// Copyright (c) 2023-present Detlef Stern
//
// This file is part of zettelstore-client.
//
// Zettelstore client is licensed under the latest version of the EUPL
// (European Union Public License). Please see file LICENSE.txt for your rights
// and obligations under this license.
//-----------------------------------------------------------------------------

package sz_test

import (
	"strconv"
	"testing"

	"zettelstore.de/client.fossil/sz"
	"zettelstore.de/sx.fossil"
)

func BenchmarkInitZttlSyms(b *testing.B) {
	for i := 0; i < 20; i++ {
		b.Run("hint-"+strconv.Itoa(i), func(b *testing.B) {
			sf := sx.MakeMappedFactory(1 << i)
			for i := 0; i < b.N; i++ {
				var zs sz.ZettelSymbols
				zs.InitializeZettelSymbols(sf)
			}
		})
	}
}