Zettelstore Client

Check-in [d5fd7ae0e0]
Login

Check-in [d5fd7ae0e0]

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

Overview
Comment:Remove usage of attrs.Attribute, in fovour of zsx.Attribute, same for sz.GoValue
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: d5fd7ae0e06d9de29886a1f09a751ac03eaf9df62d93036052693ea8f86faaeb
User & Date: stern 2025-03-17 18:01:35.667
Context
2025-03-18
14:48
Move package input to zsx; use zsx symbols ... (check-in: e7328338dc user: stern tags: trunk)
2025-03-17
18:01
Remove usage of attrs.Attribute, in fovour of zsx.Attribute, same for sz.GoValue ... (check-in: d5fd7ae0e0 user: stern tags: trunk)
17:42
Remove some unused code ... (check-in: ce441e6890 user: stern tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Deleted attrs/attrs.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
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
106
107
108
109
110
111
112
113
114
115
116
//-----------------------------------------------------------------------------
// Copyright (c) 2020-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.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------

// Package attrs stores attributes of zettel parts.
package attrs

import (
	"maps"
	"slices"
	"strings"
)

// Attributes store additional information about some node types.
type Attributes map[string]string

// IsEmpty returns true if there are no attributes.
func (a Attributes) IsEmpty() bool { return len(a) == 0 }

// DefaultAttribute is the value of the key of the default attribute
const DefaultAttribute = "-"

// HasDefault returns true, if the default attribute "-" has been set.
func (a Attributes) HasDefault() bool {
	if a != nil {
		_, ok := a[DefaultAttribute]
		return ok
	}
	return false
}

// RemoveDefault removes the default attribute
func (a Attributes) RemoveDefault() Attributes {
	if a != nil {
		a.Remove(DefaultAttribute)
	}
	return a
}

// Keys returns the sorted list of keys.
func (a Attributes) Keys() []string { return slices.Sorted(maps.Keys(a)) }

// Get returns the attribute value of the given key and a succes value.
func (a Attributes) Get(key string) (string, bool) {
	if a != nil {
		value, ok := a[key]
		return value, ok
	}
	return "", false
}

// Clone returns a duplicate of the attribute.
func (a Attributes) Clone() Attributes { return maps.Clone(a) }

// Set changes the attribute that a given key has now a given value.
func (a Attributes) Set(key, value string) Attributes {
	if a == nil {
		return map[string]string{key: value}
	}
	a[key] = value
	return a
}

// Remove the key from the attributes.
func (a Attributes) Remove(key string) Attributes {
	if a != nil {
		delete(a, key)
	}
	return a
}

// Add a value to an attribute key.
func (a Attributes) Add(key, value string) Attributes {
	if a == nil {
		return map[string]string{key: value}
	}
	values := a.Values(key)
	if !slices.Contains(values, value) {
		values = append(values, value)
		a[key] = strings.Join(values, " ")
	}
	return a
}

// Values are the space separated values of an attribute.
func (a Attributes) Values(key string) []string {
	if a != nil {
		if value, ok := a[key]; ok {
			return strings.Fields(value)
		}
	}
	return nil
}

// Has the attribute key a value?
func (a Attributes) Has(key, value string) bool {
	return slices.Contains(a.Values(key), value)
}

// AddClass adds a value to the class attribute.
func (a Attributes) AddClass(class string) Attributes { return a.Add("class", class) }

// GetClasses returns the class values as a string slice
func (a Attributes) GetClasses() []string { return a.Values("class") }

// HasClass returns true, if attributes contains the given class.
func (a Attributes) HasClass(s string) bool { return a.Has("class", s) }
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































































































































































































Deleted attrs/attrs_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
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//-----------------------------------------------------------------------------
// Copyright (c) 2020-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.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------

package attrs_test

import (
	"testing"

	"t73f.de/r/zsc/attrs"
)

func TestHasDefault(t *testing.T) {
	t.Parallel()
	attr := attrs.Attributes{}
	if attr.HasDefault() {
		t.Error("Should not have default attr")
	}
	attr = attrs.Attributes(map[string]string{"-": "value"})
	if !attr.HasDefault() {
		t.Error("Should have default attr")
	}
}

func TestAttrClone(t *testing.T) {
	t.Parallel()
	orig := attrs.Attributes{}
	clone := orig.Clone()
	if !clone.IsEmpty() {
		t.Error("Attrs must be empty")
	}

	orig = attrs.Attributes(map[string]string{"": "0", "-": "1", "a": "b"})
	clone = orig.Clone()
	if clone[""] != "0" || clone["-"] != "1" || clone["a"] != "b" || len(clone) != len(orig) {
		t.Error("Wrong cloned map")
	}
	clone["a"] = "c"
	if orig["a"] != "b" {
		t.Error("Aliased map")
	}
}

func TestHasClass(t *testing.T) {
	t.Parallel()
	testcases := []struct {
		classes string
		class   string
		exp     bool
	}{
		{"", "", false},
		{"x", "", false},
		{"x", "x", true},
		{"x", "y", false},
		{"abc def ghi", "abc", true},
		{"abc def ghi", "def", true},
		{"abc def ghi", "ghi", true},
		{"ab de gi", "b", false},
		{"ab de gi", "d", false},
	}
	for _, tc := range testcases {
		var a attrs.Attributes
		a = a.Set("class", tc.classes)
		got := a.HasClass(tc.class)
		if tc.exp != got {
			t.Errorf("%q.HasClass(%q)=%v, but got %v", tc.classes, tc.class, tc.exp, got)
		}
	}
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




























































































































































Changes to client/retrieve.go.
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	"net/http"

	"t73f.de/r/sx"
	"t73f.de/r/sx/sxreader"
	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/domain/id"
	"t73f.de/r/zsc/sexp"
	"t73f.de/r/zsc/sz"
)

var bsLF = []byte{'\n'}

// QueryZettel returns a list of all Zettel based on the given query.
//
// query is a search expression, as described in [Query the list of all zettel].







|







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	"net/http"

	"t73f.de/r/sx"
	"t73f.de/r/sx/sxreader"
	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/domain/id"
	"t73f.de/r/zsc/sexp"
	"t73f.de/r/zsx"
)

var bsLF = []byte{'\n'}

// QueryZettel returns a list of all Zettel based on the given query.
//
// query is a search expression, as described in [Query the list of all zettel].
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
		return "", "", nil, err
	}
	hVals, err := sexp.ParseList(vals[2], "ys")
	if err != nil {
		return "", "", nil, err
	}
	metaList, err := parseMetaList(vals[3].(*sx.Pair))
	return sz.GoValue(qVals[1]), sz.GoValue(hVals[1]), metaList, err
}

func parseMetaList(metaPair *sx.Pair) ([]api.ZidMetaRights, error) {
	var result []api.ZidMetaRights
	for node := metaPair; !sx.IsNil(node); {
		elem, isPair := sx.GetPair(node)
		if !isPair {







|







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
		return "", "", nil, err
	}
	hVals, err := sexp.ParseList(vals[2], "ys")
	if err != nil {
		return "", "", nil, err
	}
	metaList, err := parseMetaList(vals[3].(*sx.Pair))
	return zsx.GoValue(qVals[1]), zsx.GoValue(hVals[1]), metaList, err
}

func parseMetaList(metaPair *sx.Pair) ([]api.ZidMetaRights, error) {
	var result []api.ZidMetaRights
	for node := metaPair; !sx.IsNil(node); {
		elem, isPair := sx.GetPair(node)
		if !isPair {
Changes to go.mod.
1
2
3
4
5
6
7
8
9

10
module t73f.de/r/zsc

go 1.24

require (
	t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3
	t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b
	t73f.de/r/webs v0.0.0-20250226210341-4a531b8bfb18
	t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7

)







|

>

1
2
3
4
5
6
7
8
9
10
11
module t73f.de/r/zsc

go 1.24

require (
	t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3
	t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b
	t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5
	t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7
	t73f.de/r/zsx v0.0.0-20250317174636-5665bf1b3c6d
)
Changes to go.sum.
1
2
3
4
5
6
7
8


t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3 h1:Jek4x1Qp59SWXI1enWVTeP1wxcVO96FuBpJBnnwOY98=
t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3/go.mod h1:hzg05uSCMk3D/DWaL0pdlowfL2aWQeGIfD1S04vV+Xg=
t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b h1:X+9mMDd3fKML5SPcQk4n28oDGFUwqjDiSmQrH2LHZwI=
t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b/go.mod h1:p+3JCSzNm9e+Yyub0ODRiLDeKaGVYWvBKYANZaAWYIA=
t73f.de/r/webs v0.0.0-20250226210341-4a531b8bfb18 h1:p7rOFBzP6FE/aYN5MUfmGDrKP1H1IFs6v19T7hm7rXI=
t73f.de/r/webs v0.0.0-20250226210341-4a531b8bfb18/go.mod h1:zk92hSKB4iWyT290+163seNzu350TA9XLATC9kOldqo=
t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7 h1:OuzHSfniY8UzLmo5zp1w23Kd9h7x9CSXP2jQ+kppeqU=
t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7/go.mod h1:T1vFcHoymUQcr7+vENBkS1yryZRZ3YB8uRtnMy8yRBA=






|
|


>
>
1
2
3
4
5
6
7
8
9
10
t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3 h1:Jek4x1Qp59SWXI1enWVTeP1wxcVO96FuBpJBnnwOY98=
t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3/go.mod h1:hzg05uSCMk3D/DWaL0pdlowfL2aWQeGIfD1S04vV+Xg=
t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b h1:X+9mMDd3fKML5SPcQk4n28oDGFUwqjDiSmQrH2LHZwI=
t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b/go.mod h1:p+3JCSzNm9e+Yyub0ODRiLDeKaGVYWvBKYANZaAWYIA=
t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5 h1:nnKfs/2i9n3S5VjbSj98odcwZKGcL96qPSIUATT/2P8=
t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5/go.mod h1:zk92hSKB4iWyT290+163seNzu350TA9XLATC9kOldqo=
t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7 h1:OuzHSfniY8UzLmo5zp1w23Kd9h7x9CSXP2jQ+kppeqU=
t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7/go.mod h1:T1vFcHoymUQcr7+vENBkS1yryZRZ3YB8uRtnMy8yRBA=
t73f.de/r/zsx v0.0.0-20250317174636-5665bf1b3c6d h1:FnzmQZRClXnNcqy/jq95c5KY1YVuQLLot0GpPvN/898=
t73f.de/r/zsx v0.0.0-20250317174636-5665bf1b3c6d/go.mod h1:hZlFM0YBLIMgh2iEdS0rnuxLfO7inpWmc+wyDeRptT8=
Changes to shtml/shtml.go.
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
	"net/url"
	"strconv"
	"strings"

	"t73f.de/r/sx"
	"t73f.de/r/sxwebs/sxhtml"
	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/attrs"
	"t73f.de/r/zsc/domain/meta"
	"t73f.de/r/zsc/sz"
)

// Evaluator will transform a s-expression that encodes the zettel AST into an s-expression
// that represents HTML.
type Evaluator struct {
	headingOffset int64
	unique        string







|
|
|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
	"net/url"
	"strconv"
	"strings"

	"t73f.de/r/sx"
	"t73f.de/r/sxwebs/sxhtml"
	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/domain/meta"
	"t73f.de/r/zsc/sz"
	"t73f.de/r/zsx"
)

// Evaluator will transform a s-expression that encodes the zettel AST into an s-expression
// that represents HTML.
type Evaluator struct {
	headingOffset int64
	unique        string
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// SetUnique sets a prefix to make several HTML ids unique.
func (ev *Evaluator) SetUnique(s string) { ev.unique = s }

// IsValidName returns true, if name is a valid symbol name.
func isValidName(s string) bool { return s != "" }

// EvaluateAttributes transforms the given attributes into a HTML s-expression.
func EvaluateAttributes(a attrs.Attributes) *sx.Pair {
	if len(a) == 0 {
		return nil
	}
	plist := sx.Nil()
	keys := a.Keys()
	for i := len(keys) - 1; i >= 0; i-- {
		key := keys[i]
		if key != attrs.DefaultAttribute && isValidName(key) {
			plist = plist.Cons(sx.Cons(sx.MakeSymbol(key), sx.MakeString(a[key])))
		}
	}
	if plist == nil {
		return nil
	}
	return plist.Cons(sxhtml.SymAttr)







|







|







56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// SetUnique sets a prefix to make several HTML ids unique.
func (ev *Evaluator) SetUnique(s string) { ev.unique = s }

// IsValidName returns true, if name is a valid symbol name.
func isValidName(s string) bool { return s != "" }

// EvaluateAttributes transforms the given attributes into a HTML s-expression.
func EvaluateAttributes(a zsx.Attributes) *sx.Pair {
	if len(a) == 0 {
		return nil
	}
	plist := sx.Nil()
	keys := a.Keys()
	for i := len(keys) - 1; i >= 0; i-- {
		key := keys[i]
		if key != zsx.DefaultAttribute && isValidName(key) {
			plist = plist.Cons(sx.Cons(sx.MakeSymbol(key), sx.MakeString(a[key])))
		}
	}
	if plist == nil {
		return nil
	}
	return plist.Cons(sxhtml.SymAttr)
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
func (env *Environment) Reset() {
	env.langStack.Reset()
	env.endnotes = nil
	env.quoteNesting = 0
}

// pushAttribute adds the current attributes to the environment.
func (env *Environment) pushAttributes(a attrs.Attributes) {
	if value, ok := a.Get("lang"); ok {
		env.langStack.Push(value)
	} else {
		env.langStack.Dup()
	}
}








|







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
func (env *Environment) Reset() {
	env.langStack.Reset()
	env.endnotes = nil
	env.quoteNesting = 0
}

// pushAttribute adds the current attributes to the environment.
func (env *Environment) pushAttributes(a zsx.Attributes) {
	if value, ok := a.Get("lang"); ok {
		env.langStack.Push(value)
	} else {
		env.langStack.Dup()
	}
}

234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
	}
	ev.fns[symVal] = fn
}

func (ev *Evaluator) bindMetadata() {
	ev.bind(sz.SymMeta, 0, ev.evalList)
	evalMetaString := func(args sx.Vector, env *Environment) sx.Object {
		a := make(attrs.Attributes, 2).
			Set("name", getSymbol(args[0], env).GetValue()).
			Set("content", getString(args[1], env).GetValue())
		return ev.EvaluateMeta(a)
	}
	ev.bind(sz.SymTypeCredential, 2, evalMetaString)
	ev.bind(sz.SymTypeEmpty, 2, evalMetaString)
	ev.bind(sz.SymTypeID, 2, evalMetaString)







|







234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
	}
	ev.fns[symVal] = fn
}

func (ev *Evaluator) bindMetadata() {
	ev.bind(sz.SymMeta, 0, ev.evalList)
	evalMetaString := func(args sx.Vector, env *Environment) sx.Object {
		a := make(zsx.Attributes, 2).
			Set("name", getSymbol(args[0], env).GetValue()).
			Set("content", getString(args[1], env).GetValue())
		return ev.EvaluateMeta(a)
	}
	ev.bind(sz.SymTypeCredential, 2, evalMetaString)
	ev.bind(sz.SymTypeEmpty, 2, evalMetaString)
	ev.bind(sz.SymTypeID, 2, evalMetaString)
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
			sb.WriteByte(' ')
			sb.WriteString(getString(obj, env).GetValue())
		}
		s := sb.String()
		if len(s) > 0 {
			s = s[1:]
		}
		a := make(attrs.Attributes, 2).
			Set("name", getSymbol(args[0], env).GetValue()).
			Set("content", s)
		return ev.EvaluateMeta(a)
	}
	ev.bind(sz.SymTypeIDSet, 2, evalMetaSet)
	ev.bind(sz.SymTypeTagSet, 2, evalMetaSet)
}

// EvaluateMeta returns HTML meta object for an attribute.
func (ev *Evaluator) EvaluateMeta(a attrs.Attributes) *sx.Pair {
	return sx.Nil().Cons(EvaluateAttributes(a)).Cons(SymMeta)
}

func (ev *Evaluator) bindBlocks() {
	ev.bind(sz.SymBlock, 0, ev.evalList)
	ev.bind(sz.SymPara, 0, func(args sx.Vector, env *Environment) sx.Object {
		return ev.evalSlice(args, env).Cons(SymP)







|









|







258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
			sb.WriteByte(' ')
			sb.WriteString(getString(obj, env).GetValue())
		}
		s := sb.String()
		if len(s) > 0 {
			s = s[1:]
		}
		a := make(zsx.Attributes, 2).
			Set("name", getSymbol(args[0], env).GetValue()).
			Set("content", s)
		return ev.EvaluateMeta(a)
	}
	ev.bind(sz.SymTypeIDSet, 2, evalMetaSet)
	ev.bind(sz.SymTypeTagSet, 2, evalMetaSet)
}

// EvaluateMeta returns HTML meta object for an attribute.
func (ev *Evaluator) EvaluateMeta(a zsx.Attributes) *sx.Pair {
	return sx.Nil().Cons(EvaluateAttributes(a)).Cons(SymMeta)
}

func (ev *Evaluator) bindBlocks() {
	ev.bind(sz.SymBlock, 0, ev.evalList)
	ev.bind(sz.SymPara, 0, func(args sx.Vector, env *Environment) sx.Object {
		return ev.evalSlice(args, env).Cons(SymP)
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
		}
		return sx.MakeList(headingSymbol, sx.MakeString("<MISSING TEXT>"))
	})
	ev.bind(sz.SymThematic, 0, func(args sx.Vector, env *Environment) sx.Object {
		result := sx.Nil()
		if len(args) > 0 {
			if attrList := getList(args[0], env); attrList != nil {
				result = result.Cons(EvaluateAttributes(sz.GetAttributes(attrList)))
			}
		}
		return result.Cons(SymHR)
	})

	ev.bind(sz.SymListOrdered, 1, ev.makeListFn(SymOL))
	ev.bind(sz.SymListUnordered, 1, ev.makeListFn(SymUL))







|







305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
		}
		return sx.MakeList(headingSymbol, sx.MakeString("<MISSING TEXT>"))
	})
	ev.bind(sz.SymThematic, 0, func(args sx.Vector, env *Environment) sx.Object {
		result := sx.Nil()
		if len(args) > 0 {
			if attrList := getList(args[0], env); attrList != nil {
				result = result.Cons(EvaluateAttributes(zsx.GetAttributes(attrList)))
			}
		}
		return result.Cons(SymHR)
	})

	ev.bind(sz.SymListOrdered, 1, ev.makeListFn(SymOL))
	ev.bind(sz.SymListUnordered, 1, ev.makeListFn(SymUL))
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
		return table.Cons(symTABLE)
	})
	ev.bind(sz.SymCell, 1, func(args sx.Vector, env *Environment) sx.Object {
		tdata := ev.evalSlice(args[1:], env)
		pattrs := getList(args[0], env)
		if alignPairs := pattrs.Assoc(sz.SymAttrAlign); alignPairs != nil {
			if salign, isString := sx.GetString(alignPairs.Cdr()); isString {
				a := sz.GetAttributes(pattrs.RemoveAssoc(sz.SymAttrAlign))
				// Since in Sz there are attributes of align:center|left|right, we can reuse the values.
				a = a.AddClass(salign.GetValue())
				tdata = tdata.Cons(EvaluateAttributes(a))
			}
		}
		return tdata
	})







|







389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
		return table.Cons(symTABLE)
	})
	ev.bind(sz.SymCell, 1, func(args sx.Vector, env *Environment) sx.Object {
		tdata := ev.evalSlice(args[1:], env)
		pattrs := getList(args[0], env)
		if alignPairs := pattrs.Assoc(sz.SymAttrAlign); alignPairs != nil {
			if salign, isString := sx.GetString(alignPairs.Cdr()); isString {
				a := zsx.GetAttributes(pattrs.RemoveAssoc(sz.SymAttrAlign))
				// Since in Sz there are attributes of align:center|left|right, we can reuse the values.
				a = a.AddClass(salign.GetValue())
				tdata = tdata.Cons(EvaluateAttributes(a))
			}
		}
		return tdata
	})
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
				result.Add(cite.Cons(symCITE))
			}
		}
		return result.List()
	}
}

func evalVerbatim(a attrs.Attributes, s sx.String) sx.Object {
	a = setProgLang(a)
	code := sx.Nil().Cons(s)
	if al := EvaluateAttributes(a); al != nil {
		code = code.Cons(al)
	}
	code = code.Cons(symCODE)
	return sx.Nil().Cons(code).Cons(symPRE)







|







521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
				result.Add(cite.Cons(symCITE))
			}
		}
		return result.List()
	}
}

func evalVerbatim(a zsx.Attributes, s sx.String) sx.Object {
	a = setProgLang(a)
	code := sx.Nil().Cons(s)
	if al := EvaluateAttributes(a); al != nil {
		code = code.Cons(al)
	}
	code = code.Cons(symCODE)
	return sx.Nil().Cons(code).Cons(symPRE)
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
		}
		return result.Cons(SymSPAN)
	})
	ev.bind(sz.SymMark, 3, func(args sx.Vector, env *Environment) sx.Object {
		result := ev.evalSlice(args[3:], env)
		if !ev.noLinks {
			if fragment := getString(args[2], env).GetValue(); fragment != "" {
				a := attrs.Attributes{"id": fragment + ev.unique}
				return result.Cons(EvaluateAttributes(a)).Cons(SymA)
			}
		}
		return result.Cons(SymSPAN)
	})
	ev.bind(sz.SymEndnote, 1, func(args sx.Vector, env *Environment) sx.Object {
		a := GetAttributes(args[0], env)







|







617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
		}
		return result.Cons(SymSPAN)
	})
	ev.bind(sz.SymMark, 3, func(args sx.Vector, env *Environment) sx.Object {
		result := ev.evalSlice(args[3:], env)
		if !ev.noLinks {
			if fragment := getString(args[2], env).GetValue(); fragment != "" {
				a := zsx.Attributes{"id": fragment + ev.unique}
				return result.Cons(EvaluateAttributes(a)).Cons(SymA)
			}
		}
		return result.Cons(SymSPAN)
	})
	ev.bind(sz.SymEndnote, 1, func(args sx.Vector, env *Environment) sx.Object {
		a := GetAttributes(args[0], env)
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
		return res.Cons(SymSPAN)
	}
	return res.Cons(sxhtml.SymListSplice)
}

var visibleReplacer = strings.NewReplacer(" ", "\u2423")

func evalLiteral(args sx.Vector, a attrs.Attributes, sym *sx.Symbol, env *Environment) sx.Object {
	if a == nil {
		a = GetAttributes(args[0], env)
	}
	a = setProgLang(a)
	literal := getString(args[1], env).GetValue()
	if a.HasDefault() {
		a = a.RemoveDefault()
		literal = visibleReplacer.Replace(literal)
	}
	res := sx.Nil().Cons(sx.MakeString(literal))
	if len(a) > 0 {
		res = res.Cons(EvaluateAttributes(a))
	}
	return res.Cons(sym)
}
func setProgLang(a attrs.Attributes) attrs.Attributes {
	if val, found := a.Get(""); found {
		a = a.AddClass("language-" + val).Remove("")
	}
	return a
}

func (ev *Evaluator) evalHTML(args sx.Vector, env *Environment) sx.Object {
	if s := getString(ev.Eval(args[1], env), env); s.GetValue() != "" && IsSafe(s.GetValue()) {
		return sx.Nil().Cons(s).Cons(sxhtml.SymNoEscape)
	}
	return nil
}

func evalBLOB(a attrs.Attributes, description *sx.Pair, syntax, data sx.String) sx.Object {
	if data.GetValue() == "" {
		return sx.Nil()
	}
	switch syntax.GetValue() {
	case "":
		return sx.Nil()
	case meta.ValueSyntaxSVG:







|















|













|







733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
		return res.Cons(SymSPAN)
	}
	return res.Cons(sxhtml.SymListSplice)
}

var visibleReplacer = strings.NewReplacer(" ", "\u2423")

func evalLiteral(args sx.Vector, a zsx.Attributes, sym *sx.Symbol, env *Environment) sx.Object {
	if a == nil {
		a = GetAttributes(args[0], env)
	}
	a = setProgLang(a)
	literal := getString(args[1], env).GetValue()
	if a.HasDefault() {
		a = a.RemoveDefault()
		literal = visibleReplacer.Replace(literal)
	}
	res := sx.Nil().Cons(sx.MakeString(literal))
	if len(a) > 0 {
		res = res.Cons(EvaluateAttributes(a))
	}
	return res.Cons(sym)
}
func setProgLang(a zsx.Attributes) zsx.Attributes {
	if val, found := a.Get(""); found {
		a = a.AddClass("language-" + val).Remove("")
	}
	return a
}

func (ev *Evaluator) evalHTML(args sx.Vector, env *Environment) sx.Object {
	if s := getString(ev.Eval(args[1], env), env); s.GetValue() != "" && IsSafe(s.GetValue()) {
		return sx.Nil().Cons(s).Cons(sxhtml.SymNoEscape)
	}
	return nil
}

func evalBLOB(a zsx.Attributes, description *sx.Pair, syntax, data sx.String) sx.Object {
	if data.GetValue() == "" {
		return sx.Nil()
	}
	switch syntax.GetValue() {
	case "":
		return sx.Nil()
	case meta.ValueSyntaxSVG:
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
	}
	if env.err == nil {
		return result.List()
	}
	return nil
}

func (ev *Evaluator) evalLink(a attrs.Attributes, refValue string, inline sx.Vector, env *Environment) sx.Object {
	result := ev.evalSlice(inline, env)
	if len(inline) == 0 {
		result = sx.Nil().Cons(sx.MakeString(refValue))
	}
	if ev.noLinks {
		return result.Cons(SymSPAN)
	}







|







869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
	}
	if env.err == nil {
		return result.List()
	}
	return nil
}

func (ev *Evaluator) evalLink(a zsx.Attributes, refValue string, inline sx.Vector, env *Environment) sx.Object {
	result := ev.evalSlice(inline, env)
	if len(inline) == 0 {
		result = sx.Nil().Cons(sx.MakeString(refValue))
	}
	if ev.noLinks {
		return result.Cons(SymSPAN)
	}
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
	}
	env.err = fmt.Errorf("%v/%T is not a number", val, val)
	return -1017
}

// GetAttributes evaluates the given arg in the given environment and returns
// the contained attributes.
func GetAttributes(arg sx.Object, env *Environment) attrs.Attributes {
	return sz.GetAttributes(getList(arg, env))
}

// GetReference returns the reference symbol and the reference value of a reference pair.
func GetReference(val sx.Object, env *Environment) (*sx.Symbol, string) {
	if env.err == nil {
		if p := getList(val, env); env.err == nil {
			sym, val := sz.GetReference(p)







|
|







920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
	}
	env.err = fmt.Errorf("%v/%T is not a number", val, val)
	return -1017
}

// GetAttributes evaluates the given arg in the given environment and returns
// the contained attributes.
func GetAttributes(arg sx.Object, env *Environment) zsx.Attributes {
	return zsx.GetAttributes(getList(arg, env))
}

// GetReference returns the reference symbol and the reference value of a reference pair.
func GetReference(val sx.Object, env *Environment) (*sx.Symbol, string) {
	if env.err == nil {
		if p := getList(val, env); env.err == nil {
			sym, val := sz.GetReference(p)
Changes to sz/sz.go.
9
10
11
12
13
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
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2022-present Detlef Stern
//-----------------------------------------------------------------------------

// Package sz contains zettel data handling as sx expressions.
package sz

import (
	"t73f.de/r/sx"
	"t73f.de/r/zsc/attrs"
)

// GetAttributes traverses a s-expression list and returns an attribute structure.
func GetAttributes(seq *sx.Pair) (result attrs.Attributes) {
	for obj := range seq.Values() {
		pair, isPair := sx.GetPair(obj)
		if !isPair || pair == nil {
			continue
		}
		key := pair.Car()
		if !key.IsAtom() {
			continue
		}
		val := pair.Cdr()
		if tail, isTailPair := sx.GetPair(val); isTailPair {
			val = tail.Car()
		}
		if !val.IsAtom() {
			continue
		}
		result = result.Set(GoValue(key), GoValue(val))
	}
	return result
}

// GoValue returns the string value of the sx.Object suitable for Go processing.
func GoValue(obj sx.Object) string {
	switch o := obj.(type) {
	case sx.String:
		return o.GetValue()
	case *sx.Symbol:
		return o.GetValue()
	}
	return obj.String()
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
9
10
11
12
13
14
15







































//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2022-present Detlef Stern
//-----------------------------------------------------------------------------

// Package sz contains zettel data handling as sx expressions.
package sz