Index: api/api.go ================================================================== --- api/api.go +++ api/api.go @@ -44,11 +44,11 @@ const ( ZettelCanNone ZettelRights = 1 << iota ZettelCanCreate // Current user is allowed to create a new zettel ZettelCanRead // Requesting user is allowed to read the zettel ZettelCanWrite // Requesting user is allowed to update the zettel - ZettelCanRename // Requesting user is allowed to provide the zettel with a new identifier + placeholder_1 // Was assigned to rename right, which is now removed ZettelCanDelete // Requesting user is allowed to delete the zettel ZettelMaxRight // Sentinel value ) // MetaRights contains the metadata of a zettel, and its rights. Index: api/const.go ================================================================== --- api/const.go +++ api/const.go @@ -47,11 +47,10 @@ ZidLoginTemplate = ZettelID("00000000010200") // -> 000t ZidListTemplate = ZettelID("00000000010300") // -> 000u ZidZettelTemplate = ZettelID("00000000010401") // -> 000v ZidInfoTemplate = ZettelID("00000000010402") // -> 000w ZidFormTemplate = ZettelID("00000000010403") // -> 000x - ZidRenameTemplate = ZettelID("00000000010404") // -> 001z ZidDeleteTemplate = ZettelID("00000000010405") // -> 000y ZidErrorTemplate = ZettelID("00000000010700") // -> 000z // WebUI sxn code zettel are in the range 19000..19999 ZidSxnStart = ZettelID("00000000019000") // -> 000q @@ -175,12 +174,10 @@ ValueVisibilityPublic = "public" ) // Additional HTTP constants. const ( - MethodMove = "MOVE" // HTTP method for renaming a zettel - HeaderAccept = "Accept" HeaderContentType = "Content-Type" HeaderDestination = "Destination" HeaderLocation = "Location" ) Index: attrs/attrs.go ================================================================== --- attrs/attrs.go +++ attrs/attrs.go @@ -13,10 +13,11 @@ // Package attrs stores attributes of zettel parts. package attrs import ( + "slices" "strings" "t73f.de/r/zsc/maps" ) @@ -85,44 +86,41 @@ delete(a, key) } return a } -// AddClass adds a value to the class attribute. -func (a Attributes) AddClass(class string) Attributes { - if a == nil { - return map[string]string{"class": class} - } - classes := a.GetClasses() - for _, cls := range classes { - if cls == class { - return a - } - } - classes = append(classes, class) - a["class"] = strings.Join(classes, " ") - return a -} - -// GetClasses returns the class values as a string slice -func (a Attributes) GetClasses() []string { - if a == nil { - return nil - } - classes, ok := a["class"] - if !ok { - return nil - } - return strings.Fields(classes) -} - -// HasClass returns true, if attributes contains the given class. -func (a Attributes) HasClass(s string) bool { - if a == nil { - return false - } - classes, found := a["class"] - if !found { - return false - } - return strings.Contains(" "+classes+" ", " "+s+" ") -} +// 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) } Index: attrs/attrs_test.go ================================================================== --- attrs/attrs_test.go +++ attrs/attrs_test.go @@ -55,11 +55,11 @@ testcases := []struct { classes string class string exp bool }{ - {"", "", true}, + {"", "", false}, {"x", "", false}, {"x", "x", true}, {"x", "y", false}, {"abc def ghi", "abc", true}, {"abc def ghi", "def", true}, Index: client/client.go ================================================================== --- client/client.go +++ client/client.go @@ -139,30 +139,31 @@ req.Header.Add("Authorization", c.tokenType+" "+c.token) } resp, err := c.client.Do(req) if err != nil { if resp != nil && resp.Body != nil { - resp.Body.Close() + _ = resp.Body.Close() } return nil, err } return resp, err } func (c *Client) buildAndExecuteRequest( - ctx context.Context, method string, ub *api.URLBuilder, body io.Reader, h http.Header) (*http.Response, error) { + ctx context.Context, + method string, + ub *api.URLBuilder, + body io.Reader, +) (*http.Response, error) { req, err := c.newRequest(ctx, method, ub, body) if err != nil { return nil, err } err = c.updateToken(ctx) if err != nil { return nil, err } - for key, val := range h { - req.Header[key] = append(req.Header[key], val...) - } return c.executeRequest(req) } // SetAuth sets authentication data. func (c *Client) SetAuth(username, password string) { @@ -232,11 +233,11 @@ } // CreateZettel creates a new zettel and returns its URL. func (c *Client) CreateZettel(ctx context.Context, data []byte) (api.ZettelID, error) { ub := c.NewURLBuilder('z') - resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, bytes.NewBuffer(data), nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, bytes.NewBuffer(data)) if err != nil { return api.InvalidZID, err } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { @@ -257,11 +258,11 @@ var buf bytes.Buffer if _, err := sx.Print(&buf, sexp.EncodeZettel(data)); err != nil { return api.InvalidZID, err } ub := c.NewURLBuilder('z').AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, &buf, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, &buf) if err != nil { return api.InvalidZID, err } defer resp.Body.Close() rdr := sxreader.MakeReader(resp.Body) @@ -278,11 +279,11 @@ var bsLF = []byte{'\n'} // QueryZettel returns a list of all Zettel. func (c *Client) QueryZettel(ctx context.Context, query string) ([][]byte, error) { ub := c.NewURLBuilder('z').AppendQuery(query) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return nil, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) @@ -304,11 +305,11 @@ } // QueryZettelData returns a list of zettel metadata. func (c *Client) QueryZettelData(ctx context.Context, query string) (string, string, []api.ZidMetaRights, error) { ub := c.NewURLBuilder('z').AppendKVQuery(api.QueryKeyEncoding, api.EncodingData).AppendQuery(query) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return "", "", nil, err } defer resp.Body.Close() rdr := sxreader.MakeReader(resp.Body) @@ -450,11 +451,11 @@ func (c *Client) fetchTagOrRoleZettel(ctx context.Context, key, val string) (api.ZettelID, error) { if c.client.CheckRedirect == nil { panic("client does not allow to track redirect") } ub := c.NewURLBuilder('z').AppendKVQuery(key, val) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return api.InvalidZID, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) @@ -480,11 +481,11 @@ func (c *Client) GetZettel(ctx context.Context, zid api.ZettelID, part string) ([]byte, error) { ub := c.NewURLBuilder('z').SetZid(zid) if part != "" && part != api.PartContent { ub.AppendKVQuery(api.QueryKeyPart, part) } - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return nil, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) @@ -501,11 +502,11 @@ // GetZettelData returns a zettel as a struct of its parts. func (c *Client) GetZettelData(ctx context.Context, zid api.ZettelID) (api.ZettelData, error) { ub := c.NewURLBuilder('z').SetZid(zid) ub.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) ub.AppendKVQuery(api.QueryKeyPart, api.PartZettel) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err == nil { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return api.ZettelData{}, statusToError(resp) } @@ -533,11 +534,11 @@ ub.AppendKVQuery(api.QueryKeyEncoding, enc.String()) ub.AppendKVQuery(api.QueryKeyPart, api.PartContent) if parseOnly { ub.AppendKVQuery(api.QueryKeyParseOnly, "") } - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return nil, err } defer resp.Body.Close() switch resp.StatusCode { @@ -566,11 +567,11 @@ ub.AppendKVQuery(api.QueryKeyPart, part) } if parseOnly { ub.AppendKVQuery(api.QueryKeyParseOnly, "") } - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { @@ -582,11 +583,11 @@ // GetMetaData returns the metadata of a zettel. func (c *Client) GetMetaData(ctx context.Context, zid api.ZettelID) (api.MetaRights, error) { ub := c.NewURLBuilder('z').SetZid(zid) ub.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) ub.AppendKVQuery(api.QueryKeyPart, api.PartMeta) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return api.MetaRights{}, err } defer resp.Body.Close() rdr := sxreader.MakeReader(resp.Body) @@ -622,11 +623,11 @@ } // UpdateZettel updates an existing zettel. func (c *Client) UpdateZettel(ctx context.Context, zid api.ZettelID, data []byte) error { ub := c.NewURLBuilder('z').SetZid(zid) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, bytes.NewBuffer(data), nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, bytes.NewBuffer(data)) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { @@ -640,30 +641,11 @@ var buf bytes.Buffer if _, err := sx.Print(&buf, sexp.EncodeZettel(data)); err != nil { return err } ub := c.NewURLBuilder('z').SetZid(zid).AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, &buf, nil) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusNoContent { - return statusToError(resp) - } - return nil -} - -// RenameZettel renames a zettel. -// -// This function is deprecated and will be removed in v0.19 (or later). -func (c *Client) RenameZettel(ctx context.Context, oldZid, newZid api.ZettelID) error { - ub := c.NewURLBuilder('z').SetZid(oldZid) - h := http.Header{ - api.HeaderDestination: {c.NewURLBuilder('z').SetZid(newZid).String()}, - } - resp, err := c.buildAndExecuteRequest(ctx, api.MethodMove, ub, nil, h) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, &buf) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { @@ -673,11 +655,11 @@ } // DeleteZettel deletes a zettel with the given identifier. func (c *Client) DeleteZettel(ctx context.Context, zid api.ZettelID) error { ub := c.NewURLBuilder('z').SetZid(zid) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodDelete, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodDelete, ub, nil) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { @@ -687,11 +669,11 @@ } // ExecuteCommand will execute a given command at the Zettelstore. func (c *Client) ExecuteCommand(ctx context.Context, command api.Command) error { ub := c.NewURLBuilder('x').AppendKVQuery(api.QueryKeyCommand, string(command)) - resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, nil) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { @@ -700,11 +682,11 @@ return nil } // GetVersionInfo returns version information. func (c *Client) GetVersionInfo(ctx context.Context) (VersionInfo, error) { - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, c.NewURLBuilder('x'), nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, c.NewURLBuilder('x'), nil) if err != nil { return VersionInfo{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { @@ -753,11 +735,11 @@ return api.InvalidZID, fmt.Errorf("invalid identifier for application %v: %v", appname, val) } // Get executes a GET request to the given URL and returns the read data. func (c *Client) Get(ctx context.Context, ub *api.URLBuilder) ([]byte, error) { - resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) if err != nil { return nil, err } defer resp.Body.Close() switch resp.StatusCode { Index: go.mod ================================================================== --- go.mod +++ go.mod @@ -1,9 +1,9 @@ module t73f.de/r/zsc -go 1.22 +go 1.23 require ( - t73f.de/r/sx v0.0.0-20240513163553-ec4fcc6539ca - t73f.de/r/sxwebs v0.0.0-20240613142113-66fc5a284245 - t73f.de/r/webs v0.0.0-20240617100047-8730e9917915 + t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 + t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94 + t73f.de/r/webs v0.0.0-20240814085020-19dac746d568 ) Index: go.sum ================================================================== --- go.sum +++ go.sum @@ -1,6 +1,6 @@ -t73f.de/r/sx v0.0.0-20240513163553-ec4fcc6539ca h1:vvDqiuUfBLf+t/gpiSyqIFAdvZ7FLigOH38bqMY+v8k= -t73f.de/r/sx v0.0.0-20240513163553-ec4fcc6539ca/go.mod h1:G9pD1j2R6y9ZkPBb81mSnmwaAvTOg7r6jKp/OF7WeFA= -t73f.de/r/sxwebs v0.0.0-20240613142113-66fc5a284245 h1:raE7KUgoGsp2DzXOko9dDXEsSJ/VvoXCDYeICx7i6uo= -t73f.de/r/sxwebs v0.0.0-20240613142113-66fc5a284245/go.mod h1:ErPBVUyE2fOktL/8M7lp/PR93wP/o9RawMajB1uSqj8= -t73f.de/r/webs v0.0.0-20240617100047-8730e9917915 h1:rwUaPBIH3shrUIkmw51f4RyCplsCU+ISZHailsLiHTE= -t73f.de/r/webs v0.0.0-20240617100047-8730e9917915/go.mod h1:UGAAtul0TK5ACeZ6zTS3SX6GqwMFXxlUpHiV8oqNq5w= +t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 h1:ug4hohM6pK28M8Uo0o3+XvjBure2wfEtuCnHVIdqBZY= +t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5/go.mod h1:VRvsWoBErPKvMieDMMk1hsh1tb9sA4ijEQWGw/TbtQ0= +t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94 h1:gLneaEyYotvcY/dDznzdcSXK1RqsJVi2AfeYDc1iVwM= +t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94/go.mod h1:83W3QFkmrniIKv6R+Xq+imvbSolhoutTnNhW0ErJoco= +t73f.de/r/webs v0.0.0-20240814085020-19dac746d568 h1:Pa+vO2r++qhcShv0p7t/gIrJ1DHPMn4gopEXLxDmoRg= +t73f.de/r/webs v0.0.0-20240814085020-19dac746d568/go.mod h1:NSoOON8be62MfQZzlCApK27Jt2zhIa6Vrmo9RJ4tOnQ= Index: input/input.go ================================================================== --- input/input.go +++ input/input.go @@ -95,17 +95,11 @@ } return false } // IsEOLEOS returns true if char is either EOS or EOL. -func IsEOLEOS(ch rune) bool { - switch ch { - case EOS, '\n', '\r': - return true - } - return false -} +func IsEOLEOS(ch rune) bool { return ch == EOS || ch == '\n' || ch == '\r' } // EatEOL transforms both "\r" and "\r\n" into "\n". func (inp *Input) EatEOL() { switch inp.Ch { case '\r': @@ -124,10 +118,17 @@ if inp.Pos != pos { inp.readPos = pos inp.Next() } } + +// SkipSpace reads while the current character is not a space character. +func (inp *Input) SkipSpace() { + for ch := inp.Ch; IsSpace(ch); { + ch = inp.Next() + } +} // SkipToEOL reads until the next end-of-line. func (inp *Input) SkipToEOL() { for { switch inp.Ch { Index: input/runes.go ================================================================== --- input/runes.go +++ input/runes.go @@ -23,5 +23,8 @@ case '\n', '\r', EOS: return false } return unicode.IsSpace(ch) } + +// IsSpace returns true if current character is a whitespace. +func (inp *Input) IsSpace() bool { return IsSpace(inp.Ch) } Index: shtml/const.go ================================================================== --- shtml/const.go +++ shtml/const.go @@ -59,10 +59,11 @@ symSUP = sx.MakeSymbol("sup") symTABLE = sx.MakeSymbol("table") symTBODY = sx.MakeSymbol("tbody") symTHEAD = sx.MakeSymbol("thead") symTD = sx.MakeSymbol("td") + symTH = sx.MakeSymbol("th") symTR = sx.MakeSymbol("tr") SymUL = sx.MakeSymbol("ul") ) // Symbols for HTML attribute keys ADDED shtml/lang.go Index: shtml/lang.go ================================================================== --- /dev/null +++ shtml/lang.go @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// 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. +// +// SPDX-License-Identifier: EUPL-1.2 +// SPDX-FileCopyrightText: 2023-present Detlef Stern +//----------------------------------------------------------------------------- + +package shtml + +import ( + "strings" + + "t73f.de/r/zsc/api" +) + +// LangStack is a stack to store the nesting of "lang" attribute values. +// It is used to generate typographically correct quotes. +type LangStack []string + +// NewLangStack creates a new language stack. +func NewLangStack(lang string) LangStack { + ls := make([]string, 1, 16) + ls[0] = lang + return ls +} + +// Reset restores the language stack to its initial value. +func (ls *LangStack) Reset() { + *ls = (*ls)[0:1] +} + +// Push adds a new language value. +func (ls *LangStack) Push(lang string) { + *ls = append(*ls, lang) +} + +// Pop removes the topmost language value. +func (ls *LangStack) Pop() { + *ls = (*ls)[0 : len(*ls)-1] +} + +// Top returns the topmost language value. +func (ls *LangStack) Top() string { + return (*ls)[len(*ls)-1] +} + +// Dup duplicates the topmost language value. +func (ls *LangStack) Dup() { + *ls = append(*ls, (*ls)[len(*ls)-1]) +} + +// QuoteInfo contains language specific data about quotes. +type QuoteInfo struct { + primLeft, primRight string + secLeft, secRight string + nbsp bool +} + +// GetPrimary returns the primary left and right quote entity. +func (qi *QuoteInfo) GetPrimary() (string, string) { + return qi.primLeft, qi.primRight +} + +// GetSecondary returns the secondary left and right quote entity. +func (qi *QuoteInfo) GetSecondary() (string, string) { + return qi.secLeft, qi.secRight +} + +// GetQuotes returns quotes based on a nesting level. +func (qi *QuoteInfo) GetQuotes(level uint) (string, string) { + if level%2 == 0 { + return qi.GetPrimary() + } + return qi.GetSecondary() +} + +// GetNBSp returns true, if there must be a non-breaking space between the +// quote entities and the quoted text. +func (qi *QuoteInfo) GetNBSp() bool { return qi.nbsp } + +var langQuotes = map[string]*QuoteInfo{ + "": {""", """, """, """, false}, + api.ValueLangEN: {"“", "”", "‘", "’", false}, + "de": {"„", "“", "‚", "‘", false}, + "fr": {"«", "»", "‹", "›", true}, +} + +// GetQuoteInfo returns language specific data about quotes. +func GetQuoteInfo(lang string) *QuoteInfo { + langFields := strings.FieldsFunc(lang, func(r rune) bool { return r == '-' || r == '_' }) + for len(langFields) > 0 { + langSup := strings.Join(langFields, "-") + quotes, ok := langQuotes[langSup] + if ok { + return quotes + } + langFields = langFields[0 : len(langFields)-1] + } + return langQuotes[""] +} Index: shtml/shtml.go ================================================================== --- shtml/shtml.go +++ shtml/shtml.go @@ -52,25 +52,25 @@ ev.bindInlines() return ev } // SetUnique sets a prefix to make several HTML ids unique. -func (tr *Evaluator) SetUnique(s string) { tr.unique = s } +func (ev *Evaluator) SetUnique(s string) { ev.unique = s } // IsValidName returns true, if name is a valid symbol name. -func (tr *Evaluator) IsValidName(s string) bool { return s != "" } +func isValidName(s string) bool { return s != "" } // EvaluateAttrbute transforms the given attributes into a HTML s-expression. -func (tr *Evaluator) EvaluateAttrbute(a attrs.Attributes) *sx.Pair { +func EvaluateAttrbute(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 && tr.IsValidName(key) { + if key != attrs.DefaultAttribute && isValidName(key) { plist = plist.Cons(sx.Cons(sx.MakeSymbol(key), sx.MakeString(a[key]))) } } if plist == nil { return nil @@ -115,11 +115,11 @@ } return result.List(), nil } // Endnotes returns a SHTML object with all collected endnotes. -func (ev *Evaluator) Endnotes(env *Environment) *sx.Pair { +func Endnotes(env *Environment) *sx.Pair { if env.err != nil || len(env.endnotes) == 0 { return nil } var result sx.ListBuilder @@ -153,11 +153,11 @@ } // Environment where sz objects are evaluated to shtml objects type Environment struct { err error - langStack []string + langStack LangStack endnotes []endnoteInfo quoteNesting uint } type endnoteInfo struct { noteID string // link id @@ -166,15 +166,13 @@ noteHx *sx.Pair // Endnote as SxHTML } // MakeEnvironment builds a new evaluation environment. func MakeEnvironment(lang string) Environment { - langStack := make([]string, 1, 16) - langStack[0] = lang return Environment{ err: nil, - langStack: langStack, + langStack: NewLangStack(lang), endnotes: nil, quoteNesting: 0, } } @@ -181,32 +179,34 @@ // GetError returns the last error found. func (env *Environment) GetError() error { return env.err } // Reset the environment. func (env *Environment) Reset() { - env.langStack = env.langStack[0:1] + env.langStack.Reset() env.endnotes = nil env.quoteNesting = 0 } -// PushAttribute adds the current attributes to the environment. +// pushAttribute adds the current attributes to the environment. func (env *Environment) pushAttributes(a attrs.Attributes) { if value, ok := a.Get("lang"); ok { - env.langStack = append(env.langStack, value) + env.langStack.Push(value) } else { - env.langStack = append(env.langStack, env.getLanguage()) + env.langStack.Dup() } } -// popAttributes removes the current attributes from the envrionment -func (env *Environment) popAttributes() { - env.langStack = env.langStack[0 : len(env.langStack)-1] -} - -// getLanguage returns the current language -func (env *Environment) getLanguage() string { - return env.langStack[len(env.langStack)-1] +// popAttributes removes the current attributes from the envrionment. +func (env *Environment) popAttributes() { env.langStack.Pop() } + +// getLanguage returns the current language. +func (env *Environment) getLanguage() string { return env.langStack.Top() } + +func (env *Environment) getQuotes() (string, string, bool) { + qi := GetQuoteInfo(env.getLanguage()) + leftQ, rightQ := qi.GetQuotes(env.quoteNesting) + return leftQ, rightQ, qi.GetNBSp() } // EvalFn is a function to be called for evaluation. type EvalFn func(sx.Vector, *Environment) sx.Object @@ -237,11 +237,11 @@ 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", ev.getSymbol(args[0], env).GetValue()). + 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) @@ -261,27 +261,27 @@ s := sb.String() if len(s) > 0 { s = s[1:] } a := make(attrs.Attributes, 2). - Set("name", ev.getSymbol(args[0], env).GetValue()). + 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) ev.bind(sz.SymTypeZettelmarkup, 2, func(args sx.Vector, env *Environment) sx.Object { a := make(attrs.Attributes, 2). - Set("name", ev.getSymbol(args[0], env).GetValue()). + Set("name", getSymbol(args[0], env).GetValue()). Set("content", text.EvaluateInlineString(getList(args[1], env))) return ev.EvaluateMeta(a) }) } // EvaluateMeta returns HTML meta object for an attribute. func (ev *Evaluator) EvaluateMeta(a attrs.Attributes) *sx.Pair { - return sx.Nil().Cons(ev.EvaluateAttrbute(a)).Cons(SymMeta) + return sx.Nil().Cons(EvaluateAttrbute(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 { @@ -288,36 +288,36 @@ return ev.evalSlice(args, env).Cons(SymP) }) ev.bind(sz.SymHeading, 5, func(args sx.Vector, env *Environment) sx.Object { nLevel := getInt64(args[0], env) if nLevel <= 0 { - env.err = fmt.Errorf("%v is a negative level", nLevel) + env.err = fmt.Errorf("%v is a negative heading level", nLevel) return sx.Nil() } level := strconv.FormatInt(nLevel+ev.headingOffset, 10) headingSymbol := sx.MakeSymbol("h" + level) - a := ev.GetAttributes(args[1], env) + a := GetAttributes(args[1], env) env.pushAttributes(a) defer env.popAttributes() if fragment := getString(args[3], env).GetValue(); fragment != "" { a = a.Set("id", ev.unique+fragment) } if result, _ := ev.EvaluateList(args[4:], env); result != nil { if len(a) > 0 { - result = result.Cons(ev.EvaluateAttrbute(a)) + result = result.Cons(EvaluateAttrbute(a)) } return result.Cons(headingSymbol) } return sx.MakeList(headingSymbol, sx.MakeString("")) }) 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(ev.EvaluateAttrbute(sz.GetAttributes(attrList))) + result = result.Cons(EvaluateAttrbute(sz.GetAttributes(attrList))) } } return result.Cons(SymHR) }) @@ -362,18 +362,18 @@ }) ev.bind(sz.SymTable, 1, func(args sx.Vector, env *Environment) sx.Object { thead := sx.Nil() if header := getList(args[0], env); !sx.IsNil(header) { - thead = sx.Nil().Cons(ev.evalTableRow(header, env)).Cons(symTHEAD) + thead = sx.Nil().Cons(ev.evalTableRow(symTH, header, env)).Cons(symTHEAD) } var tbody sx.ListBuilder if len(args) > 1 { tbody.Add(symTBODY) for _, row := range args[1:] { - tbody.Add(ev.evalTableRow(getList(row, env), env)) + tbody.Add(ev.evalTableRow(symTD, getList(row, env), env)) } } table := sx.Nil() if !tbody.IsEmpty() { @@ -395,37 +395,37 @@ ev.bind(sz.SymRegionBlock, 2, ev.makeRegionFn(SymDIV, true)) ev.bind(sz.SymRegionQuote, 2, ev.makeRegionFn(symBLOCKQUOTE, false)) ev.bind(sz.SymRegionVerse, 2, ev.makeRegionFn(SymDIV, false)) ev.bind(sz.SymVerbatimComment, 1, func(args sx.Vector, env *Environment) sx.Object { - if ev.GetAttributes(args[0], env).HasDefault() { + if GetAttributes(args[0], env).HasDefault() { if len(args) > 1 { if s := getString(args[1], env); s.GetValue() != "" { return sx.Nil().Cons(s).Cons(sxhtml.SymBlockComment) } } } return nil }) ev.bind(sz.SymVerbatimEval, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalVerbatim(ev.GetAttributes(args[0], env).AddClass("zs-eval"), getString(args[1], env)) + return evalVerbatim(GetAttributes(args[0], env).AddClass("zs-eval"), getString(args[1], env)) }) ev.bind(sz.SymVerbatimHTML, 2, ev.evalHTML) ev.bind(sz.SymVerbatimMath, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalVerbatim(ev.GetAttributes(args[0], env).AddClass("zs-math"), getString(args[1], env)) + return evalVerbatim(GetAttributes(args[0], env).AddClass("zs-math"), getString(args[1], env)) }) ev.bind(sz.SymVerbatimProg, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) content := getString(args[1], env) if a.HasDefault() { content = sx.MakeString(visibleReplacer.Replace(content.GetValue())) } - return ev.evalVerbatim(a, content) + return evalVerbatim(a, content) }) ev.bind(sz.SymVerbatimZettel, 0, nilFn) ev.bind(sz.SymBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalBLOB(getList(args[0], env), getString(args[1], env), getString(args[2], env)) + return evalBLOB(getList(args[0], env), getString(args[1], env), getString(args[2], env)) }) ev.bind(sz.SymTransclude, 2, func(args sx.Vector, env *Environment) sx.Object { ref, isPair := sx.GetPair(args[1]) if !isPair { return sx.Nil() @@ -433,13 +433,13 @@ refKind := ref.Car() if sx.IsNil(refKind) { return sx.Nil() } if refValue := getString(ref.Tail().Car(), env); refValue.GetValue() != "" { - if refSym, isRefSym := sx.GetSymbol(refKind); isRefSym && refSym.IsEqual(sz.SymRefStateExternal) { - a := ev.GetAttributes(args[0], env).Set("src", refValue.GetValue()).AddClass("external") - return sx.Nil().Cons(sx.Nil().Cons(ev.EvaluateAttrbute(a)).Cons(SymIMG)).Cons(SymP) + if refSym, isRefSym := sx.GetSymbol(refKind); isRefSym && refSym.IsEqualSymbol(sz.SymRefStateExternal) { + a := GetAttributes(args[0], env).Set("src", refValue.GetValue()).AddClass("external") + return sx.Nil().Cons(sx.Nil().Cons(EvaluateAttrbute(a)).Cons(SymIMG)).Cons(SymP) } return sx.MakeList( sxhtml.SymInlineComment, sx.MakeString("transclude"), refKind, @@ -473,34 +473,34 @@ result.Add(elem) } return result.List() } -func (ev *Evaluator) evalTableRow(pairs *sx.Pair, env *Environment) *sx.Pair { +func (ev *Evaluator) evalTableRow(sym *sx.Symbol, pairs *sx.Pair, env *Environment) *sx.Pair { if pairs == nil { return nil } var row sx.ListBuilder row.Add(symTR) for pair := pairs; pair != nil; pair = pair.Tail() { - row.Add(ev.Eval(pair.Car(), env)) + row.Add(sx.Cons(sym, ev.Eval(pair.Car(), env))) } return row.List() } func (ev *Evaluator) makeCellFn(align string) EvalFn { return func(args sx.Vector, env *Environment) sx.Object { tdata := ev.evalSlice(args, env) if align != "" { - tdata = tdata.Cons(ev.EvaluateAttrbute(attrs.Attributes{"class": align})) + tdata = tdata.Cons(EvaluateAttrbute(attrs.Attributes{"class": align})) } - return tdata.Cons(symTD) + return tdata } } func (ev *Evaluator) makeRegionFn(sym *sx.Symbol, genericToClass bool) EvalFn { return func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() if genericToClass { if val, found := a.Get(""); found { a = a.Remove("").AddClass(val) @@ -507,11 +507,11 @@ } } var result sx.ListBuilder result.Add(sym) if len(a) > 0 { - result.Add(ev.EvaluateAttrbute(a)) + result.Add(EvaluateAttrbute(a)) } if region, isPair := sx.GetPair(args[1]); isPair { if evalRegion := ev.EvalPairList(region, env); evalRegion != nil { result.ExtendBang(evalRegion) } @@ -523,14 +523,14 @@ } return result.List() } } -func (ev *Evaluator) evalVerbatim(a attrs.Attributes, s sx.String) sx.Object { +func evalVerbatim(a attrs.Attributes, s sx.String) sx.Object { a = setProgLang(a) code := sx.Nil().Cons(s) - if al := ev.EvaluateAttrbute(a); al != nil { + if al := EvaluateAttrbute(a); al != nil { code = code.Cons(al) } code = code.Cons(symCODE) return sx.Nil().Cons(code).Cons(symPRE) } @@ -540,11 +540,11 @@ ev.bind(sz.SymText, 1, func(args sx.Vector, env *Environment) sx.Object { return getString(args[0], env) }) ev.bind(sz.SymSoft, 0, func(sx.Vector, *Environment) sx.Object { return sx.MakeString(" ") }) ev.bind(sz.SymHard, 0, func(sx.Vector, *Environment) sx.Object { return sx.Nil().Cons(symBR) }) ev.bind(sz.SymLinkInvalid, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() var inline *sx.Pair if len(args) > 2 { inline = ev.evalSlice(args[2:], env) @@ -553,87 +553,72 @@ inline = sx.Nil().Cons(ev.Eval(args[1], env)) } return inline.Cons(SymSPAN) }) evalHREF := func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() refValue := getString(args[1], env) return ev.evalLink(a.Set("href", refValue.GetValue()), refValue, args[2:], env) } ev.bind(sz.SymLinkZettel, 2, evalHREF) ev.bind(sz.SymLinkSelf, 2, evalHREF) ev.bind(sz.SymLinkFound, 2, evalHREF) ev.bind(sz.SymLinkBroken, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() refValue := getString(args[1], env) return ev.evalLink(a.AddClass("broken"), refValue, args[2:], env) }) ev.bind(sz.SymLinkHosted, 2, evalHREF) ev.bind(sz.SymLinkBased, 2, evalHREF) ev.bind(sz.SymLinkQuery, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() refValue := getString(args[1], env) query := "?" + api.QueryKeyQuery + "=" + url.QueryEscape(refValue.GetValue()) return ev.evalLink(a.Set("href", query), refValue, args[2:], env) }) ev.bind(sz.SymLinkExternal, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() refValue := getString(args[1], env) - return ev.evalLink(a.Set("href", refValue.GetValue()).AddClass("external"), refValue, args[2:], env) + return ev.evalLink(a.Set("href", refValue.GetValue()).Add("rel", "external"), refValue, args[2:], env) }) ev.bind(sz.SymEmbed, 3, func(args sx.Vector, env *Environment) sx.Object { ref := getList(args[1], env) - syntax := getString(args[2], env).GetValue() - if syntax == api.ValueSyntaxSVG { - embedAttr := sx.MakeList( - sxhtml.SymAttr, - sx.Cons(SymAttrType, sx.MakeString("image/svg+xml")), - sx.Cons(SymAttrSrc, sx.MakeString("/"+getString(ref.Tail(), env).GetValue()+".svg")), - ) - return sx.MakeList( - SymFIGURE, - sx.MakeList( - SymEMBED, - embedAttr, - ), - ) - } - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) a = a.Set("src", getString(ref.Tail().Car(), env).GetValue()) if len(args) > 3 { var sb strings.Builder flattenText(&sb, sx.MakeList(args[3:]...)) if d := sb.String(); d != "" { a = a.Set("alt", d) } } - return sx.MakeList(SymIMG, ev.EvaluateAttrbute(a)) + return sx.MakeList(SymIMG, EvaluateAttrbute(a)) }) ev.bind(sz.SymEmbedBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { - a, syntax, data := ev.GetAttributes(args[0], env), getString(args[1], env), getString(args[2], env) + a, syntax, data := GetAttributes(args[0], env), getString(args[1], env), getString(args[2], env) summary, hasSummary := a.Get(api.KeySummary) if !hasSummary { summary = "" } - return ev.evalBLOB( + return evalBLOB( sx.MakeList(sxhtml.SymListSplice, sx.MakeString(summary)), syntax, data, ) }) ev.bind(sz.SymCite, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() result := sx.Nil() if key := getString(args[1], env); key.GetValue() != "" { if len(args) > 2 { @@ -640,11 +625,11 @@ result = ev.evalSlice(args[2:], env).Cons(sx.MakeString(", ")) } result = result.Cons(key) } if len(a) > 0 { - result = result.Cons(ev.EvaluateAttrbute(a)) + result = result.Cons(EvaluateAttrbute(a)) } if result == nil { return nil } return result.Cons(SymSPAN) @@ -652,22 +637,22 @@ 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(ev.EvaluateAttrbute(a)).Cons(SymA) + return result.Cons(EvaluateAttrbute(a)).Cons(SymA) } } return result.Cons(SymSPAN) }) ev.bind(sz.SymEndnote, 1, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() attrPlist := sx.Nil() if len(a) > 0 { - if attrs := ev.EvaluateAttrbute(a); attrs != nil { + if attrs := EvaluateAttrbute(a); attrs != nil { attrPlist = attrs.Tail() } } noteNum := strconv.Itoa(len(env.endnotes) + 1) @@ -692,11 +677,11 @@ ev.bind(sz.SymFormatStrong, 1, ev.makeFormatFn(SymSTRONG)) ev.bind(sz.SymFormatSub, 1, ev.makeFormatFn(symSUB)) ev.bind(sz.SymFormatSuper, 1, ev.makeFormatFn(symSUP)) ev.bind(sz.SymLiteralComment, 1, func(args sx.Vector, env *Environment) sx.Object { - if ev.GetAttributes(args[0], env).HasDefault() { + if GetAttributes(args[0], env).HasDefault() { if len(args) > 1 { if s := getString(ev.Eval(args[1], env), env); s.GetValue() != "" { return sx.Nil().Cons(s).Cons(sxhtml.SymInlineComment) } } @@ -703,124 +688,90 @@ } return sx.Nil() }) ev.bind(sz.SymLiteralHTML, 2, ev.evalHTML) ev.bind(sz.SymLiteralInput, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalLiteral(args, nil, symKBD, env) + return evalLiteral(args, nil, symKBD, env) }) ev.bind(sz.SymLiteralMath, 2, func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env).AddClass("zs-math") - return ev.evalLiteral(args, a, symCODE, env) + a := GetAttributes(args[0], env).AddClass("zs-math") + return evalLiteral(args, a, symCODE, env) }) ev.bind(sz.SymLiteralOutput, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalLiteral(args, nil, symSAMP, env) + return evalLiteral(args, nil, symSAMP, env) }) ev.bind(sz.SymLiteralProg, 2, func(args sx.Vector, env *Environment) sx.Object { - return ev.evalLiteral(args, nil, symCODE, env) + return evalLiteral(args, nil, symCODE, env) }) ev.bind(sz.SymLiteralZettel, 0, nilFn) } func (ev *Evaluator) makeFormatFn(sym *sx.Symbol) EvalFn { return func(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() if val, hasClass := a.Get(""); hasClass { a = a.Remove("").AddClass(val) } res := ev.evalSlice(args[1:], env) if len(a) > 0 { - res = res.Cons(ev.EvaluateAttrbute(a)) + res = res.Cons(EvaluateAttrbute(a)) } return res.Cons(sym) } } -type quoteData struct { - primLeft, primRight string - secLeft, secRight string - nbsp bool -} - -var langQuotes = map[string]quoteData{ - "": {""", """, """, """, false}, - api.ValueLangEN: {"“", "”", "‘", "’", false}, - "de": {"„", "“", "‚", "‘", false}, - "fr": {"«", "»", "‹", "›", true}, -} - -func getQuoteData(lang string) quoteData { - langFields := strings.FieldsFunc(lang, func(r rune) bool { return r == '-' || r == '_' }) - for len(langFields) > 0 { - langSup := strings.Join(langFields, "-") - quotes, ok := langQuotes[langSup] - if ok { - return quotes - } - langFields = langFields[0 : len(langFields)-1] - } - return langQuotes[""] -} - -func getQuotes(data *quoteData, env *Environment) (string, string) { - if env.quoteNesting%2 == 0 { - return data.primLeft, data.primRight - } - return data.secLeft, data.secRight -} - func (ev *Evaluator) evalQuote(args sx.Vector, env *Environment) sx.Object { - a := ev.GetAttributes(args[0], env) + a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() if val, hasClass := a.Get(""); hasClass { a = a.Remove("").AddClass(val) } - quotes := getQuoteData(env.getLanguage()) - leftQ, rightQ := getQuotes("es, env) + leftQ, rightQ, withNbsp := env.getQuotes() env.quoteNesting++ res := ev.evalSlice(args[1:], env) env.quoteNesting-- lastPair := res.LastPair() if lastPair.IsNil() { res = sx.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(leftQ), sx.MakeString(rightQ)), sx.Nil()) } else { - if quotes.nbsp { + if withNbsp { lastPair.AppendBang(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(" "), sx.MakeString(rightQ))) res = res.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(leftQ), sx.MakeString(" "))) } else { lastPair.AppendBang(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(rightQ))) res = res.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(leftQ))) } } if len(a) > 0 { - res = res.Cons(ev.EvaluateAttrbute(a)) + res = res.Cons(EvaluateAttrbute(a)) return res.Cons(SymSPAN) } return res.Cons(sxhtml.SymListSplice) } var visibleReplacer = strings.NewReplacer(" ", "\u2423") -func (ev *Evaluator) evalLiteral(args sx.Vector, a attrs.Attributes, sym *sx.Symbol, env *Environment) sx.Object { +func evalLiteral(args sx.Vector, a attrs.Attributes, sym *sx.Symbol, env *Environment) sx.Object { if a == nil { - a = ev.GetAttributes(args[0], env) + 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(ev.EvaluateAttrbute(a)) + res = res.Cons(EvaluateAttrbute(a)) } return res.Cons(sym) } func setProgLang(a attrs.Attributes) attrs.Attributes { if val, found := a.Get(""); found { @@ -834,11 +785,11 @@ return sx.Nil().Cons(s).Cons(sxhtml.SymNoEscape) } return nil } -func (ev *Evaluator) evalBLOB(description *sx.Pair, syntax, data sx.String) sx.Object { +func evalBLOB(description *sx.Pair, syntax, data sx.String) sx.Object { if data.GetValue() == "" { return sx.Nil() } switch syntax.GetValue() { case "": @@ -948,14 +899,14 @@ result = sx.Nil().Cons(refValue) } if ev.noLinks { return result.Cons(SymSPAN) } - return result.Cons(ev.EvaluateAttrbute(a)).Cons(SymA) + return result.Cons(EvaluateAttrbute(a)).Cons(SymA) } -func (ev *Evaluator) getSymbol(obj sx.Object, env *Environment) *sx.Symbol { +func getSymbol(obj sx.Object, env *Environment) *sx.Symbol { if env.err == nil { if sym, ok := sx.GetSymbol(obj); ok { return sym } env.err = fmt.Errorf("%v/%T is not a symbol", obj, obj) @@ -992,11 +943,11 @@ return -1017 } // GetAttributes evaluates the given arg in the given environment and returns // the contained attributes. -func (ev *Evaluator) GetAttributes(arg sx.Object, env *Environment) attrs.Attributes { +func GetAttributes(arg sx.Object, env *Environment) attrs.Attributes { return sz.GetAttributes(getList(arg, env)) } var unsafeSnippets = []string{ " 7 { delims = 7 } level := int64(delims - 2) var attrs *sx.Pair @@ -349,12 +350,13 @@ func (cp *zmkP) parseNestedList() (res *sx.Pair, success bool) { kinds := cp.parseNestedListKinds() if len(kinds) == 0 { return nil, false } - cp.skipSpace() - if !kinds[len(kinds)-1].IsEqual(sz.SymListQuote) && input.IsEOLEOS(cp.inp.Ch) { + inp := cp.inp + inp.SkipSpace() + if !kinds[len(kinds)-1].IsEqual(sz.SymListQuote) && input.IsEOLEOS(inp.Ch) { return nil, false } if len(kinds) < len(cp.lists) { cp.lists = cp.lists[:len(kinds)] @@ -445,11 +447,11 @@ inp := cp.inp if inp.Next() != ' ' { return nil, false } inp.Next() - cp.skipSpace() + inp.SkipSpace() descrl := cp.descrl if descrl == nil { descrl = sx.Cons(sz.SymDescription, nil) cp.descrl = descrl res = descrl @@ -484,11 +486,11 @@ inp := cp.inp if inp.Next() != ' ' { return nil, false } inp.Next() - cp.skipSpace() + inp.SkipSpace() descrl := cp.descrl lastPair, pos := lastPairPos(descrl) if descrl == nil || pos <= 0 { // No term given return nil, false Index: sz/zmk/inline.go ================================================================== --- sz/zmk/inline.go +++ sz/zmk/inline.go @@ -148,11 +148,11 @@ } func (cp *zmkP) parseReference(openCh, closeCh rune) (ref string, text *sx.Pair, _ bool) { inp := cp.inp inp.Next() - cp.skipSpace() + inp.SkipSpace() if inp.Ch == openCh { // Additional opening chars result in a fail return "", nil, false } var is sx.Vector @@ -182,11 +182,11 @@ } inp.SetPos(pos) } } - cp.skipSpace() + inp.SkipSpace() pos = inp.Pos if !cp.readReferenceToClose(closeCh) { return "", nil, false } ref = strings.TrimSpace(string(inp.Src[pos:inp.Pos])) @@ -328,13 +328,13 @@ // Problematisch ist, dass hier noch nicht mn.Fragment und mn.Slug gesetzt werden. // Evtl. muss es ein PreMark-Symbol geben } func (cp *zmkP) parseLinkLikeRest() (*sx.Pair, bool) { - cp.skipSpace() var ins sx.Vector inp := cp.inp + inp.SkipSpace() for inp.Ch != ']' { in := cp.parseInline() if in == nil { return nil, false } @@ -357,11 +357,11 @@ } for inp.Ch == '%' { inp.Next() } attrs := cp.parseInlineAttributes() - cp.skipSpace() + inp.SkipSpace() pos := inp.Pos for { if input.IsEOLEOS(inp.Ch) { return sx.MakeList( sz.SymLiteralComment, @@ -459,11 +459,11 @@ } } } func createLiteralNode(sym *sx.Symbol, attrs *sx.Pair, content string) *sx.Pair { - if sym.IsEqual(sz.SymLiteralZettel) { + if sym.IsEqualSymbol(sz.SymLiteralZettel) { if p := attrs.Assoc(sx.MakeString("")); p != nil { if val, isString := sx.GetString(p.Cdr()); isString && val.GetValue() == api.ValueSyntaxHTML { sym = sz.SymLiteralHTML attrs = attrs.RemoveAssoc(sx.MakeString("")) } Index: sz/zmk/post-processor.go ================================================================== --- sz/zmk/post-processor.go +++ sz/zmk/post-processor.go @@ -322,11 +322,11 @@ elem = cellTail.Head() if elem.Car().IsEqual(sz.SymText) { if s, isString := sx.GetString(elem.Tail().Car()); isString && s.GetValue() != "" { str := s.GetValue() cellAlign := getCellAlignment(str[len(str)-1]) - if !cellAlign.IsEqual(sz.SymCell) { + if !cellAlign.IsEqualSymbol(sz.SymCell) { elem.SetCdr(sx.Cons(sx.MakeString(str[0:len(str)-1]), nil)) } align[cellCount-1] = cellAlign cell.SetCar(cellAlign) } @@ -368,11 +368,11 @@ elem := cellTail.Head() if elem.Car().IsEqual(sz.SymText) { if s, isString := sx.GetString(elem.Tail().Car()); isString && s.GetValue() != "" { str := s.GetValue() cellAlign := getCellAlignment(str[0]) - if !cellAlign.IsEqual(sz.SymCell) { + if !cellAlign.IsEqualSymbol(sz.SymCell) { elem.SetCdr(sx.Cons(sx.MakeString(str[1:]), nil)) cell.SetCar(cellAlign) } } } Index: sz/zmk/ref.go ================================================================== --- sz/zmk/ref.go +++ sz/zmk/ref.go @@ -28,11 +28,11 @@ } if strings.HasPrefix(s, api.QueryPrefix) { return makePairRef(sz.SymRefStateQuery, s[len(api.QueryPrefix):]) } if state, ok := localState(s); ok { - if state.IsEqual(sz.SymRefStateBased) { + if state.IsEqualSymbol(sz.SymRefStateBased) { s = s[1:] } _, err := url.Parse(s) if err == nil { return makePairRef(state, s) Index: sz/zmk/zmk.go ================================================================== --- sz/zmk/zmk.go +++ sz/zmk/zmk.go @@ -189,11 +189,11 @@ if pos < inp.Pos { return attrMap{"": string(inp.Src[pos:inp.Pos])}.asPairAssoc() } // No immediate name: skip spaces - cp.skipSpace() + inp.SkipSpace() return cp.parseInlineAttributes() } func (cp *zmkP) parseInlineAttributes() *sx.Pair { inp := cp.inp @@ -273,14 +273,8 @@ return } } } -func (cp *zmkP) skipSpace() { - for inp := cp.inp; inp.Ch == ' '; { - inp.Next() - } -} - func isNameRune(ch rune) bool { return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '-' || ch == '_' } Index: sz/zmk/zmk_test.go ================================================================== --- sz/zmk/zmk_test.go +++ sz/zmk/zmk_test.go @@ -52,27 +52,32 @@ for tcn, tc := range tcs { t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() ast := parseInput(tc.source, isBlock) + sz.Walk(astWalker{}, ast, nil) got := ast.String() if tc.want != got { st.Errorf("\nwant=%q\n got=%q", tc.want, got) } }) } } -func parseInput(src string, asBlock bool) sx.Sequence { +func parseInput(src string, asBlock bool) *sx.Pair { inp := input.NewInput([]byte(src)) if asBlock { bl := zmk.ParseBlocks(inp) return bl } il := zmk.ParseInlines(inp) return il } +type astWalker struct{} + +func (astWalker) Visit(node *sx.Pair, env *sx.Pair) sx.Object { return sx.MakeBoolean(true) } + func TestEOL(t *testing.T) { t.Parallel() for _, isBlock := range []bool{true, false} { checkTcs(t, isBlock, TestCases{ {"", "()"}, Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -1,9 +1,12 @@ Change Log

Changes for Version 0.19.0 (pending)

+ * Remove support for rename operation; removed all associated constants + * Make quote handling in shtml public, to be used by other encodeers + * shtml generates external links with rel attribute

Changes for Version 0.18.0 (2024-07-11)

* Add client method GetApplicationZid to retrieve the zettel identifier of an configuration zettel for a specific application.