DELETED .github/dependabot.yml
Index: .github/dependabot.yml
==================================================================
--- .github/dependabot.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-# To get started with Dependabot version updates, you'll need to specify which
-# package ecosystems to update and where the package manifests are located.
-# Please see the documentation for all configuration options:
-# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
-
-version: 2
-updates:
- - package-ecosystem: "gomod" # See documentation for possible values
- directory: "/" # Location of package manifests
- schedule:
- interval: "daily"
- rebase-strategy: "disabled"
Index: VERSION
==================================================================
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
-0.22.0
+0.23.0-dev
Index: cmd/cmd_file.go
==================================================================
--- cmd/cmd_file.go
+++ cmd/cmd_file.go
@@ -45,19 +45,19 @@
Content: zettel.NewContent(inp.Src[inp.Pos:]),
},
string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)),
nil,
)
+ parser.Clean(z.Blocks)
encdr := encoder.Create(
api.Encoder(enc),
&encoder.CreateParameter{Lang: string(m.GetDefault(meta.KeyLang, meta.ValueLangEN))})
if encdr == nil {
fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc)
return 2, nil
}
- _, err = encdr.WriteZettel(os.Stdout, z)
- if err != nil {
+ if err = encdr.WriteZettel(os.Stdout, z); err != nil {
return 2, err
}
fmt.Println()
return 0, nil
Index: cmd/cmd_run.go
==================================================================
--- cmd/cmd_run.go
+++ cmd/cmd_run.go
@@ -93,43 +93,45 @@
if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" {
const assetPrefix = "/assets/"
webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir))))
webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir))
}
+
+ const isAPI = true
// Web user interface
if !authManager.IsReadonly() {
- webSrv.AddListRoute('c', server.MethodGet, wui.MakeGetZettelFromListHandler(&ucQuery, &ucEvaluate, ucListRoles, ucListSyntax))
- webSrv.AddListRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
- webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler(
- ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax))
- webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
- webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel, ucGetAllZettel))
- webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete))
- webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax))
- webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate))
- }
- webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh))
- webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex))
- webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler(&ucEvaluate, ucGetZettel))
- webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler())
- webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate))
- webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler(
+ webSrv.AddListRoute(!isAPI, 'c', server.MethodGet, wui.MakeGetZettelFromListHandler(&ucQuery, &ucEvaluate, ucListRoles, ucListSyntax))
+ webSrv.AddListRoute(!isAPI, 'c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
+ webSrv.AddZettelRoute(!isAPI, 'c', server.MethodGet, wui.MakeGetCreateZettelHandler(
+ ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax))
+ webSrv.AddZettelRoute(!isAPI, 'c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
+ webSrv.AddZettelRoute(!isAPI, 'd', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel, ucGetAllZettel))
+ webSrv.AddZettelRoute(!isAPI, 'd', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete))
+ webSrv.AddZettelRoute(!isAPI, 'e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax))
+ webSrv.AddZettelRoute(!isAPI, 'e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate))
+ }
+ webSrv.AddListRoute(!isAPI, 'g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh))
+ webSrv.AddListRoute(!isAPI, 'h', server.MethodGet, wui.MakeListHTMLMetaHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex))
+ webSrv.AddZettelRoute(!isAPI, 'h', server.MethodGet, wui.MakeGetHTMLZettelHandler(&ucEvaluate, ucGetZettel))
+ webSrv.AddListRoute(!isAPI, 'i', server.MethodGet, wui.MakeGetLoginOutHandler())
+ webSrv.AddListRoute(!isAPI, 'i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate))
+ webSrv.AddZettelRoute(!isAPI, 'i', server.MethodGet, wui.MakeGetInfoHandler(
ucParseZettel, ucGetReferences, &ucEvaluate, ucGetZettel, ucGetAllZettel, &ucQuery))
// API
- webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate))
- webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler())
- webSrv.AddZettelRoute('r', server.MethodGet, a.MakeGetReferencesHandler(ucParseZettel, ucGetReferences))
- webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion))
- webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh))
- webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex))
- webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel, ucParseZettel, ucEvaluate))
+ webSrv.AddListRoute(isAPI, 'a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate))
+ webSrv.AddListRoute(isAPI, 'a', server.MethodPut, a.MakeRenewAuthHandler())
+ webSrv.AddZettelRoute(isAPI, 'r', server.MethodGet, a.MakeGetReferencesHandler(ucParseZettel, ucGetReferences))
+ webSrv.AddListRoute(isAPI, 'x', server.MethodGet, a.MakeGetDataHandler(ucVersion))
+ webSrv.AddListRoute(isAPI, 'x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh))
+ webSrv.AddListRoute(isAPI, 'z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex))
+ webSrv.AddZettelRoute(isAPI, 'z', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel, ucParseZettel, ucEvaluate))
if !authManager.IsReadonly() {
- webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel))
- webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate))
- webSrv.AddZettelRoute('z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete))
+ webSrv.AddListRoute(isAPI, 'z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel))
+ webSrv.AddZettelRoute(isAPI, 'z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate))
+ webSrv.AddZettelRoute(isAPI, 'z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete))
}
if authManager.WithAuth() {
webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager))
}
Index: docs/manual/00001004010000.zettel
==================================================================
--- docs/manual/00001004010000.zettel
+++ docs/manual/00001004010000.zettel
@@ -2,11 +2,11 @@
title: Zettelstore startup configuration
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20250627155145
+modified: 20250828135622
The configuration file, specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options.
These cannot be stored in a [[configuration zettel|00001004020000]] because they are needed before Zettelstore can start or because of security reasons.
For example, Zettelstore needs to know in advance on which network address it must listen or where zettel are stored.
An attacker that is able to change the owner can do anything.
@@ -49,11 +49,10 @@
If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"".
In this case, even a key ''box-uri-2'' will be ignored.
; [!debug-mode|''debug-mode'']
: If set to [[true|00001006030500]], allows to debug the Zettelstore software (mostly used by Zettelstore developers).
- Disables any timeout values of the internal web server and does not send some security-related data.
Sets [[''log-level''|#log-level]] to ""debug"".
Enables [[''runtime-profiling''|#runtime-profiling]].
Do not enable it for a production server.
Index: docs/manual/00001004051000.zettel
==================================================================
--- docs/manual/00001004051000.zettel
+++ docs/manual/00001004051000.zettel
@@ -2,11 +2,11 @@
title: The ''run'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20250627160733
+modified: 20250828135353
=== ``zettelstore run``
This starts the web service.
```
@@ -24,15 +24,13 @@
; [!d|''-d DIR'']
: Specifies ''DIR'' as the directory that contains all zettel.
Default is ''./zettel'' (''.\\zettel'' on Windows), where ''.'' denotes the ""current directory"".
; [!debug|''-debug'']
-: Allows better debugging of the internal web server by disabling any timeout values.
- You should specify this only as a developer.
- Especially do not enable it for a production server.
+: Allows debugging of the internal web server.
+ Same as setting [[''debug-mode''|00001004010000#debug-mode]] to ""true"".
- [[https://blog.cloudflare.com/exposing-go-on-the-internet/#timeouts]] contains a good explanation for the usefulness of sensitive timeout values.
; [!p|''-p PORT'']
: Specifies the integer value ''PORT'' as the TCP port, where the Zettelstore web server listens for requests.
Default: 23123.
Index: docs/manual/00001006030000.zettel
==================================================================
--- docs/manual/00001006030000.zettel
+++ docs/manual/00001006030000.zettel
@@ -2,19 +2,21 @@
title: Supported Key Types
role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20250115172354
+modified: 20250708154024
All [[supported metadata keys|00001006020000]] conform to a type.
User-defined metadata keys conform also to a type, based on the suffix of the key.
|=Suffix|Type
| ''-date'' | [[Timestamp|00001006034500]]
| ''-number'' | [[Number|00001006033000]]
+| ''-ref'' | [[Identifier|00001006032000]]
+| ''-refs'' | [[IdentifierSet|00001006032500]]
| ''-role'' | [[Word|00001006035500]]
| ''-time'' | [[Timestamp|00001006034500]]
| ''-url'' | [[URL|00001006035000]]
| ''-zettel'' | [[Identifier|00001006032000]]
| ''-zid'' | [[Identifier|00001006032000]]
Index: docs/manual/00001010000000.zettel
==================================================================
--- docs/manual/00001010000000.zettel
+++ docs/manual/00001010000000.zettel
@@ -2,11 +2,11 @@
title: Security
role: manual
tags: #configuration #manual #security #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20250102212014
+modified: 20250828121450
Your zettel may contain sensitive content.
You probably want to ensure that only authorized persons can read and/or modify them.
Zettelstore ensures this in various ways.
@@ -63,6 +63,6 @@
To enforce encryption, [[authenticated sessions|00001010040700]] are marked as secure by default.
If you still want to access the Zettelstore remotely without encryption, you must change the startup configuration.
Otherwise, authentication will not work.
-* [[Use a server for encryption|00001010090100]]
+* [[Use a server, also for encryption|00001010090100]]
Index: docs/manual/00001010090100.zettel
==================================================================
--- docs/manual/00001010090100.zettel
+++ docs/manual/00001010090100.zettel
@@ -1,12 +1,12 @@
id: 00001010090100
-title: External server to encrypt message transport
+title: External server, also to encrypt message transport
role: manual
tags: #configuration #encryption #manual #security #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20250701125905
+modified: 20250828135632
Since Zettelstore does not encrypt the messages it exchanges with its clients, you may need some additional software to enable encryption.
=== Public-key encryption
To enable encryption, you probably use some kind of encryption keys.
@@ -67,5 +67,18 @@
This will forward requests with the prefix ""/manual/"" to the running Zettelstore.
All other requests will be handled by Caddy itself.
In this case you must specify the [[startup configuration key ''url-prefix''|00001004010000#url-prefix]] with the value ""/manual/"".
This is to allow Zettelstore to ignore the prefix while reading web requests and to give the correct URLs with the given prefix when sending a web response.
+
+=== Cross-origin protection
+
+Zettelstore protects you against [[Cross-Site Request Forgery (CSRF)|https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF]].
+The protection will work only if the external server forwards certain HTTP request headers to Zettelstore:
+
+* [[''Sec-Fetch-Site''|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site]] is read first and should contain the values ""same-origin"" or ""none"".
+* [[''Origin''|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin]] is read next, if ''Sec-Fetch-Site'' is either missing or contains other values.
+ In this case ''Origin'' should contain the same host name as read via header [[''Host''|https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Host]].
+
+Otherwise, an cross-origin request will be detected.
+
+To debug the configuration of the external server, Zettelstore will log the received header values, if Zettelstore is run with the [[''-debug''|00001004051000#debug]] parameter.
Index: docs/manual/00001012053800.zettel
==================================================================
--- docs/manual/00001012053800.zettel
+++ docs/manual/00001012053800.zettel
@@ -2,11 +2,11 @@
title: API: Retrieve references of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20250415154139
-modified: 20250416181257
+modified: 20251013185705
The [[endpoint|00001012920000]] to retrieve references of a specific zettel is ''/r/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
A zettel may contain references to external material, in the form of an URI.
External material may be referenced via Zettelmarkup [[links|00001007040310]], [[inline embedding|00001007040320]], or a [[block transclusion|00001007031100]].
@@ -39,11 +39,11 @@
https://github.github.com/gfm/
https://spec.commonmark.org/0.31.2/
https://github.com/yuin/goldmark
```
-These examples should make clean, that no duplicates are removed and no sorting takes place.
+These examples illustrate that the data remains unsorted and that duplicates are preserved.
The client must handle this, e.g.:
```sh
# curl 'http://127.0.0.1:23123/r/00001008010500' | sort -u
https://commonmark.org/
https://github.com/yuin/goldmark
Index: docs/manual/00001012920525.zettel
==================================================================
--- docs/manual/00001012920525.zettel
+++ docs/manual/00001012920525.zettel
@@ -2,11 +2,11 @@
title: SHTML Encoding
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230316181044
-modified: 20250102180003
+modified: 20250806192348
A zettel representation that is a [[s-expression|00001012930000]], syntactically similar to the [[Sz encoding|00001012920516]], but denotes [[HTML|00001012920510]] semantics.
It is derived from a XML encoding in s-expressions, called [[SXML|https://en.wikipedia.org/wiki/SXML]].
It is (relatively) easy to parse and contains everything to transform it into real HTML.
@@ -30,7 +30,7 @@
This allows a more space economic storage of data.
An HTML tag like ``< a href="link">Text`` is encoded in SHTML with a list, where the first element is a symbol named a the tag.
The second element is an optional encoding of the tag's attributes.
Further elements are either other tag encodings or a string.
-The above tag is encoded as ``(a (@ (href . "link")) "Text")``.
-Also possible is to encode the attribute without pairs: ``(a (@ (href "link")) "Text")`` (note the missing full stop character).
+The above tag is encoded as ``(a ((href . "link")) "Text")``.
+Also possible is to encode the attribute without pairs: ``(a ((href "link")) "Text")`` (note the missing full stop character).
Index: docs/manual/00001012931000.zettel
==================================================================
--- docs/manual/00001012931000.zettel
+++ docs/manual/00001012931000.zettel
@@ -2,11 +2,11 @@
title: Encoding of Sz
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230403153903
-modified: 20250102213403
+modified: 20251021190931
Zettel in a [[Sz encoding|00001012920516]] are represented as a [[symbolic expression|00001012930000]].
To process these symbolic expressions, you need to know, how a specific part of a zettel is represented by a symbolic expression.
Basically, each part of a zettel is represented as a list, often a nested list.
@@ -66,15 +66,26 @@
These are specified by the empty list ''()''.
Or there are attributes.
In this case, the first element of the list must be the symbol ''quote'': ''(quote'' ''('' A,,1,, A,,2,, … A,,n,, '')'''')''.
=== Other
+==== ''UNKNOWN''
A list with ''UNKNOWN'' as its first element signals an internal error during transforming a zettel into the Sz encoding.
-It may be ignored, or it may produce an error.
+It may be ignored or may result in an error.
:::syntax
__Unknown__ **=** ''(UNKNOWN'' Object … '')''.
:::
-The list may only contain the symbol ''UNKNOWN'', or additionally an unlimited amount of other objects.
+The list may only contain the symbol ''UNKNOWN'', or, in addition, an unlimited amount of other objects.
+
+==== ''**xyz:NOT-FOUND**''
+Any symbol with the pattern ''**xyz:NOT-FOUND**'', where ""''xyz''"" is any string, signals an internal error.
+
+==== ''*SPLICE-NODES*''
+A list with ''*SPLICE-NODES*'' as its first element may occur during internal processing.
+It signals that some processing routine intends to return more than one object instead of just one [[__BlockElement__|00001012931400]] or one [[__InlineElement__|00001012931600]].
+The elements of such a list should be ""spliced"" into the parent list.
-Similar, any symbol with the pattern ''**xyz:NOT-FOUND**'', where ''xyz'' is any string, signals an internal error.
+==== NIL (or ''()'')
+Analogous to a splice node, which signal that more than one values is about to be returned, a ""NIL"" value (denoted as ""''()''"") signals that no object should be returned.
+Therefore, a [[__BlockElement__|00001012931400]] or an [[__InlineElement__|00001012931600]] may be ""NIL"".
Index: docs/manual/00001012931400.zettel
==================================================================
--- docs/manual/00001012931400.zettel
+++ docs/manual/00001012931400.zettel
@@ -2,11 +2,11 @@
title: Encoding of Sz Block Elements
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230403161803
-modified: 20250317143910
+modified: 20251007144738
=== ''PARA''
:::syntax
__Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] … '')''.
:::
@@ -29,22 +29,17 @@
=== ''ORDERED'', ''UNORDERED'', ''QUOTATION''
These three symbols are specifying different kinds of lists / enumerations: an ordered list, an unordered list, and a quotation list.
Their structure is the same.
:::syntax
-__OrderedList__ **=** ''(ORDERED'' [[__Attributes__|00001012931000#attribute]] __ListElement__ … '')''.
+__OrderedList__ **=** ''(ORDERED'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''.
-__UnorderedList__ **=** ''(UNORDERED'' [[__Attributes__|00001012931000#attribute]] __ListElement__ … '')''.
+__UnorderedList__ **=** ''(UNORDERED'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''.
-__QuotationList__ **=** ''(QUOTATION'' [[__Attributes__|00001012931000#attribute]] __ListElement__ … '')''.
+__QuotationList__ **=** ''(QUOTATION'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''.
:::
-:::syntax
-__ListElement__ **=** [[__Block__|00001012931000#block]] **|** [[__Inline__|00001012931000#inline]].
-:::
-A list element is either a block or an inline.
-If it is a block, it may contain a nested list.
=== ''DESCRIPTION''
:::syntax
__Description__ **=** ''(DESCRIPTION'' [[__Attributes__|00001012931000#attribute]] __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''.
:::
A description is a sequence of one or more terms and values.
@@ -149,17 +144,17 @@
:::
The string contains text that should be treated as (nested) zettel content.
=== ''BLOB''
:::syntax
-__BLOB__ **=** ''(BLOB'' [[__Attributes__|00001012931000#attribute]] ''('' [[__InlineElement__|00001012931600]] … '')'' String,,1,, String,,2,, '')''.
+__BLOB__ **=** ''(BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, [[__InlineElement__|00001012931600]] … '')''.
:::
A BLOB contains an image in block mode.
The inline elements states some description.
The first string contains the syntax of the image.
The second string contains the actual image.
-If the syntax is ""SVG"", then the second string contains the SVG code.
+If the syntax is ""''svg''"", then the second string contains the SVG code.
Otherwise the (binary) image data is encoded with base64.
=== ''TRANSCLUDE''
:::syntax
__Transclude__ **=** ''(TRANSCLUDE'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] [[__InlineElement__|00001012931600]] … '')''.
Index: docs/manual/00001012931600.zettel
==================================================================
--- docs/manual/00001012931600.zettel
+++ docs/manual/00001012931600.zettel
@@ -2,11 +2,11 @@
title: Encoding of Sz Inline Elements
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230403161845
-modified: 20250313130428
+modified: 20251007144653
=== ''TEXT''
:::syntax
__Text__ **=** ''(TEXT'' String '')''.
:::
@@ -47,16 +47,16 @@
The __InlineElement__ at the end of the list is interpreted as describing text.
=== ''EMBED-BLOB''
:::syntax
-__EmbedBLOB__ **=** ''(EMBED-BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, '')''.
+__EmbedBLOB__ **=** ''(EMBED-BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, [[__InlineElement__|00001012931600]] … '')''.
:::
If used if some processed image has to be embedded inside some inline material.
The first string specifies the syntax of the image content.
The second string contains the image content.
-If the syntax is ""SVG"", the image content is not encoded further.
+If the syntax is ""''svg''"", the image content is not encoded further.
Otherwise a base64 encoding is used.
=== ''CITE''
:::syntax
__CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
@@ -139,15 +139,10 @@
:::syntax
__CommentLiteral__ **=** ''(LITERAL-COMMENT'' [[__Attributes__|00001012931000#attribute]] String '')''.
:::
The string contains text that should be treated as an internal comment not to be interpreted further.
-:::syntax
-__HTMLLiteral__ **=** ''(LITERAL-HTML'' [[__Attributes__|00001012931000#attribute]] String '')''.
-:::
-The string contains text that should be treated as HTML code.
-
:::syntax
__InputLiteral__ **=** ''(LITERAL-INPUT'' [[__Attributes__|00001012931000#attribute]] String '')''.
:::
The string contains text that should be treated as input entered by a user.
@@ -158,10 +153,5 @@
:::syntax
__OutputLiteral__ **=** ''(LITERAL-OUTPUT'' [[__Attributes__|00001012931000#attribute]] String '')''.
:::
The string contains text that should be treated as computer output to be read by a user.
-
-:::syntax
-__ZettelLiteral__ **=** ''(LITERAL-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''.
-:::
-The string contains text that should be treated as (nested) zettel content.
Index: docs/manual/00001018000000.zettel
==================================================================
--- docs/manual/00001018000000.zettel
+++ docs/manual/00001018000000.zettel
@@ -2,11 +2,11 @@
title: Troubleshooting
role: manual
tags: #manual #zettelstore
syntax: zmk
created: 20211027105921
-modified: 20250627130445
+modified: 20250828140447
This page lists some problems and their solutions that may occur when using your Zettelstore.
=== Installation
* **Problem:** When you double-click on the Zettelstore executable icon, macOS complains that Zettelstore is an application from an unknown developer.
@@ -23,10 +23,14 @@
** **Explanation:** A local running Zettelstore typically means, that you are accessing the Zettelstore using a URL with schema ''http://'', and not ''https://'', for example ''http://localhost:23123/''.
The difference between these two is the missing encryption of user name / password and for the answer of the Zettelstore if you use the ''http://'' schema.
To be secure by default, the Zettelstore will not work in an insecure environment.
** **Solution 1:** If you are sure that your communication medium is safe, even if you use the ''http:/\/'' schema (for example, you are running the Zettelstore on the same computer you are working on, or if the Zettelstore is running on a computer in your protected local network), then you could add the entry ''insecure-cookie: true'' in your [[startup configuration|00001004010000#insecure-cookie]] file.
** **Solution 2:** If you are not sure about the security of your communication medium (for example, if unknown persons might use your local network), then you should run an [[external server|00001010090100]] in front of your Zettelstore to enable the use of the ''https://'' schema.
+* **Problem:** [[Authentication is enabled|00001010040100]] for a Zettelstore running behind an external server (e.g. for encryption)
+ Entering user name and password produces an error message, stating that a ""cross-origin request"" was detected.
+** **Explanation:** The external web server dos not transfer needed HTTP header data to your Zettelstore.
+** **Solution:** Try to run Zettelstore in debug mode, as described in [[Cross-origin protection|00001010090100#cross-origin-protection]].
=== Working with Zettel Files
* **Problem:** When you delete a zettel file by removing it from the ""disk"", e.g. by dropping it into the trash folder, by dragging into another folder, or by removing it from the command line, Zettelstore sometimes does not detect the change.
If you access the zettel via Zettelstore, an error is reported.
** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel.
Index: go.mod
==================================================================
--- go.mod
+++ go.mod
@@ -1,21 +1,21 @@
module zettelstore.de/z
-go 1.24
+go 1.25
require (
github.com/fsnotify/fsnotify v1.9.0
- github.com/yuin/goldmark v1.7.12
- golang.org/x/crypto v0.39.0
- golang.org/x/term v0.32.0
- t73f.de/r/sx v0.0.0-20250707071435-95b82f7d24bb
- t73f.de/r/sxwebs v0.0.0-20250707071704-c44197610ee4
- t73f.de/r/webs v0.0.0-20250707071548-227f3e99db55
- t73f.de/r/zero v0.0.0-20250703105709-bb38976d4455
- t73f.de/r/zsc v0.0.0-20250707072124-be388711ad2a
- t73f.de/r/zsx v0.0.0-20250707071920-5e29047e4db7
+ github.com/yuin/goldmark v1.7.13
+ golang.org/x/crypto v0.43.0
+ golang.org/x/term v0.36.0
+ t73f.de/r/sx v0.0.0-20251106134512-57bd2ba0e8e3
+ t73f.de/r/sxwebs v0.0.0-20251106134640-a792e6dfefd8
+ t73f.de/r/webs v0.0.0-20251106132628-d89a2b2c2373
+ t73f.de/r/zero v0.0.0-20251106132433-8bb93fc3269f
+ t73f.de/r/zsc v0.0.0-20251106134919-31f6048e2c2e
+ t73f.de/r/zsx v0.0.0-20251106134735-30b13dc3ce8a
)
require (
- golang.org/x/sys v0.33.0 // indirect
- golang.org/x/text v0.26.0 // indirect
+ golang.org/x/sys v0.37.0 // indirect
+ golang.org/x/text v0.30.0 // indirect
)
Index: go.sum
==================================================================
--- go.sum
+++ go.sum
@@ -1,24 +1,24 @@
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
-github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
-golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
-golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
-golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
-golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
-golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
-golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
-golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
-t73f.de/r/sx v0.0.0-20250707071435-95b82f7d24bb h1:cYvTOpaJinh/EPB7i8nx7PtT7hniuSP+NZr74P9U+fE=
-t73f.de/r/sx v0.0.0-20250707071435-95b82f7d24bb/go.mod h1:uglbFdRHlcpQVVyCNh4Fd7jbKo8alGBCjRp0aZv8IIg=
-t73f.de/r/sxwebs v0.0.0-20250707071704-c44197610ee4 h1:WbT8qQAQjqx1S0syci6yWi+YiNQFFYVBkOSfSYIRid4=
-t73f.de/r/sxwebs v0.0.0-20250707071704-c44197610ee4/go.mod h1:zSel+qtBHV9NglxDHlFFPwvaetlZh4H0pLyd7OkpkpQ=
-t73f.de/r/webs v0.0.0-20250707071548-227f3e99db55 h1:gl4XpbrzrtAFDY+4V9bixLTUruhzEcwvfKiZi1NqJY4=
-t73f.de/r/webs v0.0.0-20250707071548-227f3e99db55/go.mod h1:b8/5E5Pe6WSWqh+T+sxLO5ZLiGVkuL5tgh86kx2OAIg=
-t73f.de/r/zero v0.0.0-20250703105709-bb38976d4455 h1:TFRPPexX2WrwuF03hC+Be2ONx2bPzMMBlNDn0rk88eI=
-t73f.de/r/zero v0.0.0-20250703105709-bb38976d4455/go.mod h1:Ovx7CYsjz45BNuIEMGZfqA7NdQxERydJqUGnOBoQaXQ=
-t73f.de/r/zsc v0.0.0-20250707072124-be388711ad2a h1:p12BTQ8TdKZy6GcCRgVINKSeoMz0wBPcNG7ssYvLNrc=
-t73f.de/r/zsc v0.0.0-20250707072124-be388711ad2a/go.mod h1:JnkeoahGBxNK0gDcTnAIfDP2rAP33BtnAU1/rCEewC8=
-t73f.de/r/zsx v0.0.0-20250707071920-5e29047e4db7 h1:ERxpb1Hqln+NXoZDK6sqjmX3BzeoLO+O64f4bK0B6dk=
-t73f.de/r/zsx v0.0.0-20250707071920-5e29047e4db7/go.mod h1:64/AjQ1GnEBoBhXI1D0bDMGDj7JCbtZUTT3WoA7kS0s=
+github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
+github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
+golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+t73f.de/r/sx v0.0.0-20251106134512-57bd2ba0e8e3 h1:8EQK+viyafJrYKprKFjxv8KO80XLjH2Av5gaI24298g=
+t73f.de/r/sx v0.0.0-20251106134512-57bd2ba0e8e3/go.mod h1:4hTEMWfmRB4ocR4ir0WuVoV+q0ed58pwGZwsznSXt1Q=
+t73f.de/r/sxwebs v0.0.0-20251106134640-a792e6dfefd8 h1:4I4hAwUuMb4bDNqttF1ZXGbZRnTTBvgHeWZOdELUZw8=
+t73f.de/r/sxwebs v0.0.0-20251106134640-a792e6dfefd8/go.mod h1:0rjepPI8mKx3vIe3+IVN3qJVr25uma2A6ozUDpnqbwY=
+t73f.de/r/webs v0.0.0-20251106132628-d89a2b2c2373 h1:puhtswOd5A9+RzeTBRfuSsvhs0Nsn9e3PHM7zM4Yges=
+t73f.de/r/webs v0.0.0-20251106132628-d89a2b2c2373/go.mod h1:n6yEgRO4XVZYi8F5ilHWgv7oFzVsRDZrXorCdXxdVqk=
+t73f.de/r/zero v0.0.0-20251106132433-8bb93fc3269f h1:v334GYGrruo8wr9Wh9R/KahJr8gMX/Nbysg/wpgtAC8=
+t73f.de/r/zero v0.0.0-20251106132433-8bb93fc3269f/go.mod h1:6TIoFD0Qn7oEE4GYUzA1cQzwrvhGAADYsm930FK6Yz0=
+t73f.de/r/zsc v0.0.0-20251106134919-31f6048e2c2e h1:JEDbKtuem7NdzueTqPiJ7zhJBT1sue9c7+9Rsf4Oy50=
+t73f.de/r/zsc v0.0.0-20251106134919-31f6048e2c2e/go.mod h1:WST5do8U/LEf6bprTRnvpLqNnW9qS7Am7qCKnFe4Br4=
+t73f.de/r/zsx v0.0.0-20251106134735-30b13dc3ce8a h1:hPo18TPk3Wlmh34Vuj0DVGJN13TiZ8Mule18EvwYs1Y=
+t73f.de/r/zsx v0.0.0-20251106134735-30b13dc3ce8a/go.mod h1:sQaYZqc37hSYmHENHrBqIiazhuoZc4Q4dpP9Wc6AM/I=
Index: internal/ast/ast.go
==================================================================
--- internal/ast/ast.go
+++ internal/ast/ast.go
@@ -13,91 +13,66 @@
// Package ast provides the abstract syntax tree for parsed zettel content.
package ast
import (
- "net/url"
- "strings"
+ "fmt"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
+
"zettelstore.de/z/internal/zettel"
)
-// ZettelNode is the root node of the abstract syntax tree.
-// It is *not* part of the visitor pattern.
-type ZettelNode struct {
- Meta *meta.Meta // Original metadata
- Content zettel.Content // Original content
- Zid id.Zid // Zettel identification.
- InhMeta *meta.Meta // Metadata of the zettel, with inherited values.
- BlocksAST BlockSlice // Zettel abstract syntax tree is a sequence of block nodes.
- Syntax string // Syntax / parser that produced the Ast
-}
-
-// Node is the interface, all nodes must implement.
-type Node interface {
- WalkChildren(v Visitor)
-}
-
-// BlockNode is the interface that all block nodes must implement.
-type BlockNode interface {
- Node
- blockNode()
-}
-
-// ItemNode is a node that can occur as a list item.
-type ItemNode interface {
- BlockNode
- itemNode()
-}
-
-// ItemSlice is a slice of ItemNodes.
-type ItemSlice []ItemNode
-
-// DescriptionNode is a node that contains just textual description.
-type DescriptionNode interface {
- ItemNode
- descriptionNode()
-}
-
-// DescriptionSlice is a slice of DescriptionNodes.
-type DescriptionSlice []DescriptionNode
-
-// InlineNode is the interface that all inline nodes must implement.
-type InlineNode interface {
- Node
- inlineNode()
-}
-
-// Reference is a reference to external or internal material.
-type Reference struct {
- URL *url.URL
- Value string
- State RefState
-}
-
-// RefState indicates the state of the reference.
-type RefState int
-
-// Constants for RefState
-const (
- RefStateInvalid RefState = iota // Invalid Reference
- RefStateZettel // Reference to an internal zettel
- RefStateSelf // Reference to same zettel with a fragment
- RefStateFound // Reference to an existing internal zettel, URL is ajusted
- RefStateBroken // Reference to a non-existing internal zettel
- RefStateHosted // Reference to local hosted non-Zettel, without URL change
- RefStateBased // Reference to local non-Zettel, to be prefixed
- RefStateQuery // Reference to a zettel query
- RefStateExternal // Reference to external material
-)
-
-// ParseSpacedText returns an inline slice that consists just of test and space node.
-// No Zettelmarkup parsing is done. It is typically used to transform the zettel
-// description into an inline slice.
-func ParseSpacedText(s string) InlineSlice {
- return InlineSlice{&TextNode{Text: NormalizedSpacedText(s)}}
-}
-
-// NormalizedSpacedText returns the given string, but normalize multiple spaces to one space.
-func NormalizedSpacedText(s string) string { return strings.Join(strings.Fields(s), " ") }
+// Zettel is the root node of the abstract syntax tree.
+type Zettel struct {
+ Meta *meta.Meta // Original metadata
+ Content zettel.Content // Original content
+ Zid id.Zid // Zettel identification.
+ InhMeta *meta.Meta // Metadata of the zettel, with inherited values.
+ Blocks *sx.Pair // Syntax tree, encodes as an sx.Object.
+ Syntax string // Syntax / parser that produced the Ast
+}
+
+var mapMetaTypeS = map[*meta.DescriptionType]*sx.Symbol{
+ meta.TypeCredential: sz.SymTypeCredential,
+ meta.TypeEmpty: sz.SymTypeEmpty,
+ meta.TypeID: sz.SymTypeID,
+ meta.TypeIDSet: sz.SymTypeIDSet,
+ meta.TypeNumber: sz.SymTypeNumber,
+ meta.TypeString: sz.SymTypeString,
+ meta.TypeTagSet: sz.SymTypeTagSet,
+ meta.TypeTimestamp: sz.SymTypeTimestamp,
+ meta.TypeURL: sz.SymTypeURL,
+ meta.TypeWord: sz.SymTypeWord,
+}
+
+// GetMetaSz transforms the given metadata into a sz list.
+func GetMetaSz(m *meta.Meta) *sx.Pair {
+ var lb sx.ListBuilder
+ lb.Add(sz.SymMeta)
+ for key, val := range m.Computed() {
+ ty := m.Type(key)
+ symType := mapGetS(mapMetaTypeS, ty)
+ var obj sx.Object
+ if ty.IsSet {
+ var setObjs sx.ListBuilder
+ for _, val := range val.AsSlice() {
+ setObjs.Add(sx.MakeString(val))
+ }
+ obj = setObjs.List()
+ } else {
+ obj = sx.MakeString(string(val))
+ }
+ lb.Add(sx.Nil().Cons(obj).Cons(sx.MakeSymbol(key)).Cons(symType))
+ }
+ return lb.List()
+}
+
+func mapGetS[T comparable](m map[T]*sx.Symbol, k T) *sx.Symbol {
+ if result, found := m[k]; found {
+ return result
+ }
+ return sx.MakeSymbol(fmt.Sprintf("**%v:NOT-FOUND**", k))
+}
DELETED internal/ast/block.go
Index: internal/ast/block.go
==================================================================
--- internal/ast/block.go
+++ /dev/null
@@ -1,303 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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 ast
-
-import "t73f.de/r/zsx"
-
-// Definition of Block nodes.
-
-// BlockSlice is a slice of BlockNodes.
-type BlockSlice []BlockNode
-
-func (*BlockSlice) blockNode() { /* Just a marker */ }
-
-// WalkChildren walks down to the descriptions.
-func (bs *BlockSlice) WalkChildren(v Visitor) {
- if bs != nil {
- for _, bn := range *bs {
- Walk(v, bn)
- }
- }
-}
-
-// FirstParagraphInlines returns the inline list of the first paragraph that
-// contains a inline list.
-func (bs BlockSlice) FirstParagraphInlines() InlineSlice {
- for _, bn := range bs {
- pn, ok := bn.(*ParaNode)
- if !ok {
- continue
- }
- if inl := pn.Inlines; len(inl) > 0 {
- return inl
- }
- }
- return nil
-}
-
-//--------------------------------------------------------------------------
-
-// ParaNode contains just a sequence of inline elements.
-// Another name is "paragraph".
-type ParaNode struct {
- Inlines InlineSlice
-}
-
-func (*ParaNode) blockNode() { /* Just a marker */ }
-func (*ParaNode) itemNode() { /* Just a marker */ }
-func (*ParaNode) descriptionNode() { /* Just a marker */ }
-
-// CreateParaNode creates a parameter block from inline nodes.
-func CreateParaNode(nodes ...InlineNode) *ParaNode { return &ParaNode{Inlines: nodes} }
-
-// WalkChildren walks down the inline elements.
-func (pn *ParaNode) WalkChildren(v Visitor) { Walk(v, &pn.Inlines) }
-
-//--------------------------------------------------------------------------
-
-// VerbatimNode contains uninterpreted text
-type VerbatimNode struct {
- Kind VerbatimKind
- Attrs zsx.Attributes
- Content []byte
-}
-
-// VerbatimKind specifies the format that is applied to code inline nodes.
-type VerbatimKind int
-
-// Constants for VerbatimCode
-const (
- _ VerbatimKind = iota
- VerbatimZettel // Zettel content
- VerbatimCode // Program code
- VerbatimEval // Code to be externally interpreted. Syntax is stored in default attribute.
- VerbatimComment // Block comment
- VerbatimHTML // Block HTML, e.g. for Markdown
- VerbatimMath // Block math mode
-)
-
-func (*VerbatimNode) blockNode() { /* Just a marker */ }
-func (*VerbatimNode) itemNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*VerbatimNode) WalkChildren(Visitor) { /* No children*/ }
-
-//--------------------------------------------------------------------------
-
-// RegionNode encapsulates a region of block nodes.
-type RegionNode struct {
- Kind RegionKind
- Attrs zsx.Attributes
- Blocks BlockSlice
- Inlines InlineSlice // Optional text at the end of the region
-}
-
-// RegionKind specifies the actual region type.
-type RegionKind int
-
-// Values for RegionCode
-const (
- _ RegionKind = iota
- RegionSpan // Just a span of blocks
- RegionQuote // A longer quotation
- RegionVerse // Line breaks matter
-)
-
-func (*RegionNode) blockNode() { /* Just a marker */ }
-func (*RegionNode) itemNode() { /* Just a marker */ }
-
-// WalkChildren walks down the blocks and the text.
-func (rn *RegionNode) WalkChildren(v Visitor) {
- Walk(v, &rn.Blocks)
- Walk(v, &rn.Inlines)
-}
-
-//--------------------------------------------------------------------------
-
-// HeadingNode stores the heading text and level.
-type HeadingNode struct {
- Level int
- Attrs zsx.Attributes
- Slug string // Heading text, normalized
- Fragment string // Heading text, suitable to be used as an unique URL fragment
- Inlines InlineSlice // Heading text, possibly formatted
-}
-
-func (*HeadingNode) blockNode() { /* Just a marker */ }
-func (*HeadingNode) itemNode() { /* Just a marker */ }
-
-// WalkChildren walks the heading text.
-func (hn *HeadingNode) WalkChildren(v Visitor) { Walk(v, &hn.Inlines) }
-
-//--------------------------------------------------------------------------
-
-// HRuleNode specifies a horizontal rule.
-type HRuleNode struct {
- Attrs zsx.Attributes
-}
-
-func (*HRuleNode) blockNode() { /* Just a marker */ }
-func (*HRuleNode) itemNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*HRuleNode) WalkChildren(Visitor) { /* No children*/ }
-
-//--------------------------------------------------------------------------
-
-// NestedListNode specifies a nestable list, either ordered or unordered.
-type NestedListNode struct {
- Kind NestedListKind
- Attrs zsx.Attributes
- Items []ItemSlice
-}
-
-// NestedListKind specifies the actual list type.
-type NestedListKind uint8
-
-// Values for ListCode
-const (
- _ NestedListKind = iota
- NestedListOrdered // Ordered list.
- NestedListUnordered // Unordered list.
- NestedListQuote // Quote list.
-)
-
-func (*NestedListNode) blockNode() { /* Just a marker */ }
-func (*NestedListNode) itemNode() { /* Just a marker */ }
-
-// WalkChildren walks down the items.
-func (ln *NestedListNode) WalkChildren(v Visitor) {
- if items := ln.Items; items != nil {
- for _, item := range items {
- WalkItemSlice(v, item)
- }
- }
-}
-
-//--------------------------------------------------------------------------
-
-// DescriptionListNode specifies a description list.
-type DescriptionListNode struct {
- Attrs zsx.Attributes
- Descriptions []Description
-}
-
-// Description is one element of a description list.
-type Description struct {
- Term InlineSlice
- Descriptions []DescriptionSlice
-}
-
-func (*DescriptionListNode) blockNode() { /* Just a marker */ }
-
-// WalkChildren walks down to the descriptions.
-func (dn *DescriptionListNode) WalkChildren(v Visitor) {
- if descrs := dn.Descriptions; descrs != nil {
- for i, desc := range descrs {
- if len(desc.Term) > 0 {
- Walk(v, &descrs[i].Term) // Otherwise, changes in desc.Term will not go back into AST
- }
- if dss := desc.Descriptions; dss != nil {
- for _, dns := range dss {
- WalkDescriptionSlice(v, dns)
- }
- }
- }
- }
-}
-
-//--------------------------------------------------------------------------
-
-// TableNode specifies a full table
-type TableNode struct {
- Header TableRow // The header row
- Align []Alignment // Default column alignment
- Rows []TableRow // The slice of cell rows
-}
-
-// TableCell contains the data for one table cell
-type TableCell struct {
- Align Alignment // Cell alignment
- Inlines InlineSlice // Cell content
-}
-
-// TableRow is a slice of cells.
-type TableRow []*TableCell
-
-// Alignment specifies text alignment.
-// Currently only for tables.
-type Alignment int
-
-// Constants for Alignment.
-const (
- _ Alignment = iota
- AlignDefault // Default alignment, inherited
- AlignLeft // Left alignment
- AlignCenter // Center the content
- AlignRight // Right alignment
-)
-
-func (*TableNode) blockNode() { /* Just a marker */ }
-
-// WalkChildren walks down to the cells.
-func (tn *TableNode) WalkChildren(v Visitor) {
- if header := tn.Header; header != nil {
- for i := range header {
- Walk(v, header[i]) // Otherwise changes will not go back
- }
- }
- if rows := tn.Rows; rows != nil {
- for _, row := range rows {
- for i := range row {
- Walk(v, &row[i].Inlines) // Otherwise changes will not go back
- }
- }
- }
-}
-
-// WalkChildren walks the list of inline elements.
-func (cell *TableCell) WalkChildren(v Visitor) {
- Walk(v, &cell.Inlines) // Otherwise changes will not go back
-}
-
-//--------------------------------------------------------------------------
-
-// TranscludeNode specifies block content from other zettel to embedded in
-// current zettel
-type TranscludeNode struct {
- Attrs zsx.Attributes
- Ref *Reference
- Inlines InlineSlice // Optional text.
-}
-
-func (*TranscludeNode) blockNode() { /* Just a marker */ }
-
-// WalkChildren walks the associated text.
-func (tn *TranscludeNode) WalkChildren(v Visitor) { Walk(v, &tn.Inlines) }
-
-//--------------------------------------------------------------------------
-
-// BLOBNode contains just binary data that must be interpreted according to
-// a syntax.
-type BLOBNode struct {
- Attrs zsx.Attributes
- Description InlineSlice
- Syntax string
- Blob []byte
-}
-
-func (*BLOBNode) blockNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*BLOBNode) WalkChildren(Visitor) { /* No children*/ }
DELETED internal/ast/inline.go
Index: internal/ast/inline.go
==================================================================
--- internal/ast/inline.go
+++ /dev/null
@@ -1,210 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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 ast
-
-import "t73f.de/r/zsx"
-
-// Definitions of inline nodes.
-
-// InlineSlice is a list of BlockNodes.
-type InlineSlice []InlineNode
-
-func (*InlineSlice) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks down to the list.
-func (is *InlineSlice) WalkChildren(v Visitor) {
- if is != nil {
- for _, in := range *is {
- Walk(v, in)
- }
- }
-}
-
-// --------------------------------------------------------------------------
-
-// TextNode just contains some text.
-type TextNode struct {
- Text string // The text itself.
-}
-
-func (*TextNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*TextNode) WalkChildren(Visitor) { /* No children*/ }
-
-// --------------------------------------------------------------------------
-
-// BreakNode signals a new line that must / should be interpreted as a new line break.
-type BreakNode struct {
- Hard bool // Hard line break?
-}
-
-func (*BreakNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*BreakNode) WalkChildren(Visitor) { /* No children*/ }
-
-// --------------------------------------------------------------------------
-
-// LinkNode contains the specified link.
-type LinkNode struct {
- Attrs zsx.Attributes // Optional attributes
- Ref *Reference
- Inlines InlineSlice // The text associated with the link.
-}
-
-func (*LinkNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks to the link text.
-func (ln *LinkNode) WalkChildren(v Visitor) {
- if len(ln.Inlines) > 0 {
- Walk(v, &ln.Inlines)
- }
-}
-
-// --------------------------------------------------------------------------
-
-// EmbedRefNode contains the specified embedded reference material.
-type EmbedRefNode struct {
- Attrs zsx.Attributes // Optional attributes
- Ref *Reference // The reference to be embedded.
- Syntax string // Syntax of referenced material, if known
- Inlines InlineSlice // Optional text associated with the image.
-}
-
-func (*EmbedRefNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks to the text that describes the embedded material.
-func (en *EmbedRefNode) WalkChildren(v Visitor) { Walk(v, &en.Inlines) }
-
-// --------------------------------------------------------------------------
-
-// EmbedBLOBNode contains the specified embedded BLOB material.
-type EmbedBLOBNode struct {
- Attrs zsx.Attributes // Optional attributes
- Syntax string // Syntax of Blob
- Blob []byte // BLOB data itself.
- Inlines InlineSlice // Optional text associated with the image.
-}
-
-func (*EmbedBLOBNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks to the text that describes the embedded material.
-func (en *EmbedBLOBNode) WalkChildren(v Visitor) { Walk(v, &en.Inlines) }
-
-// --------------------------------------------------------------------------
-
-// CiteNode contains the specified citation.
-type CiteNode struct {
- Attrs zsx.Attributes // Optional attributes
- Key string // The citation key
- Inlines InlineSlice // Optional text associated with the citation.
-}
-
-func (*CiteNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks to the cite text.
-func (cn *CiteNode) WalkChildren(v Visitor) { Walk(v, &cn.Inlines) }
-
-// --------------------------------------------------------------------------
-
-// MarkNode contains the specified merked position.
-// It is a BlockNode too, because although it is typically parsed during inline
-// mode, it is moved into block mode afterwards.
-type MarkNode struct {
- Mark string // The mark text itself
- Slug string // Slugified form of Mark
- Fragment string // Unique form of Slug
- Inlines InlineSlice // Marked inline content
-}
-
-func (*MarkNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (mn *MarkNode) WalkChildren(v Visitor) {
- if len(mn.Inlines) > 0 {
- Walk(v, &mn.Inlines)
- }
-}
-
-// --------------------------------------------------------------------------
-
-// FootnoteNode contains the specified footnote.
-type FootnoteNode struct {
- Attrs zsx.Attributes // Optional attributes
- Inlines InlineSlice // The footnote text.
-}
-
-func (*FootnoteNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks to the footnote text.
-func (fn *FootnoteNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) }
-
-// --------------------------------------------------------------------------
-
-// FormatNode specifies some inline formatting.
-type FormatNode struct {
- Kind FormatKind
- Attrs zsx.Attributes // Optional attributes.
- Inlines InlineSlice
-}
-
-// FormatKind specifies the format that is applied to the inline nodes.
-type FormatKind int
-
-// Constants for FormatCode
-const (
- _ FormatKind = iota
- FormatEmph // Emphasized text
- FormatStrong // Strongly emphasized text
- FormatInsert // Inserted text
- FormatDelete // Deleted text
- FormatSuper // Superscripted text
- FormatSub // SubscriptedText
- FormatQuote // Quoted text
- FormatMark // Marked text
- FormatSpan // Generic inline container
-)
-
-func (*FormatNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren walks to the formatted text.
-func (fn *FormatNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) }
-
-// --------------------------------------------------------------------------
-
-// LiteralNode specifies some uninterpreted text.
-type LiteralNode struct {
- Kind LiteralKind
- Attrs zsx.Attributes // Optional attributes.
- Content []byte
-}
-
-// LiteralKind specifies the format that is applied to code inline nodes.
-type LiteralKind int
-
-// Constants for LiteralCode
-const (
- _ LiteralKind = iota
- LiteralCode // Inline program code
- LiteralInput // Computer input, e.g. Keyboard strokes
- LiteralOutput // Computer output
- LiteralComment // Inline comment
- LiteralMath // Inline math mode
-)
-
-func (*LiteralNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*LiteralNode) WalkChildren(Visitor) { /* No children*/ }
DELETED internal/ast/ref.go
Index: internal/ast/ref.go
==================================================================
--- internal/ast/ref.go
+++ /dev/null
@@ -1,109 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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 ast
-
-import (
- "net/url"
- "strings"
-
- "t73f.de/r/zsc/api"
- "t73f.de/r/zsc/domain/id"
-)
-
-// QueryPrefix is the prefix that denotes a query expression.
-const QueryPrefix = api.QueryPrefix
-
-// ParseReference parses a string and returns a reference.
-func ParseReference(s string) *Reference {
- if invalidReference(s) {
- return &Reference{URL: nil, Value: s, State: RefStateInvalid}
- }
- if strings.HasPrefix(s, QueryPrefix) {
- return &Reference{URL: nil, Value: s[len(QueryPrefix):], State: RefStateQuery}
- }
- if state, ok := localState(s); ok {
- if state == RefStateBased {
- s = s[1:]
- }
- u, err := url.Parse(s)
- if err == nil {
- return &Reference{URL: u, Value: s, State: state}
- }
- }
- u, err := url.Parse(s)
- if err != nil {
- return &Reference{URL: nil, Value: s, State: RefStateInvalid}
- }
- if !externalURL(u) {
- if _, err = id.Parse(u.Path); err == nil {
- return &Reference{URL: u, Value: s, State: RefStateZettel}
- }
- if u.Path == "" && u.Fragment != "" {
- return &Reference{URL: u, Value: s, State: RefStateSelf}
- }
- }
- return &Reference{URL: u, Value: s, State: RefStateExternal}
-}
-
-func invalidReference(s string) bool { return s == "" || s == "00000000000000" }
-func externalURL(u *url.URL) bool {
- return u.Scheme != "" || u.Opaque != "" || u.Host != "" || u.User != nil
-}
-
-func localState(path string) (RefState, bool) {
- if len(path) > 0 && path[0] == '/' {
- if len(path) > 1 && path[1] == '/' {
- return RefStateBased, true
- }
- return RefStateHosted, true
- }
- if len(path) > 1 && path[0] == '.' {
- if len(path) > 2 && path[1] == '.' && path[2] == '/' {
- return RefStateHosted, true
- }
- return RefStateHosted, path[1] == '/'
- }
- return RefStateInvalid, false
-}
-
-// String returns the string representation of a reference.
-func (r Reference) String() string {
- if r.State == RefStateQuery {
- return QueryPrefix + r.Value
- }
- if r.URL != nil {
- return r.URL.String()
- }
- return r.Value
-}
-
-// IsValid returns true if reference is valid
-func (r *Reference) IsValid() bool { return r.State != RefStateInvalid }
-
-// IsZettel returns true if it is a referencen to a local zettel.
-func (r *Reference) IsZettel() bool {
- switch r.State {
- case RefStateZettel, RefStateSelf, RefStateFound, RefStateBroken:
- return true
- }
- return false
-}
-
-// IsLocal returns true if reference is local
-func (r *Reference) IsLocal() bool {
- return r.State == RefStateHosted || r.State == RefStateBased
-}
-
-// IsExternal returns true if it is a referencen to external material.
-func (r *Reference) IsExternal() bool { return r.State == RefStateExternal }
DELETED internal/ast/ref_test.go
Index: internal/ast/ref_test.go
==================================================================
--- internal/ast/ref_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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 ast_test
-
-import (
- "testing"
-
- "zettelstore.de/z/internal/ast"
-)
-
-func TestParseReference(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- link string
- err bool
- exp string
- }{
- {"", true, ""},
- {"123", false, "123"},
- {",://", true, ""},
- }
-
- for i, tc := range testcases {
- got := ast.ParseReference(tc.link)
- if got.IsValid() == tc.err {
- t.Errorf(
- "TC=%d, expected parse error of %q: %v, but got %q", i, tc.link, tc.err, got)
- }
- if got.IsValid() && got.String() != tc.exp {
- t.Errorf("TC=%d, Reference of %q is %q, but got %q", i, tc.link, tc.exp, got)
- }
- }
-}
-
-func TestReferenceIsZettelMaterial(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- link string
- isZettel bool
- isExternal bool
- isLocal bool
- }{
- {"", false, false, false},
- {"00000000000000", false, false, false},
- {"http://zettelstore.de/z/ast", false, true, false},
- {"12345678901234", true, false, false},
- {"12345678901234#local", true, false, false},
- {"http://12345678901234", false, true, false},
- {"http://zettelstore.de/z/12345678901234", false, true, false},
- {"http://zettelstore.de/12345678901234", false, true, false},
- {"/12345678901234", false, false, true},
- {"//12345678901234", false, false, true},
- {"./12345678901234", false, false, true},
- {"../12345678901234", false, false, true},
- {".../12345678901234", false, true, false},
- }
-
- for i, tc := range testcases {
- ref := ast.ParseReference(tc.link)
- isZettel := ref.IsZettel()
- if isZettel != tc.isZettel {
- t.Errorf(
- "TC=%d, Reference %q isZettel=%v expected, but got %v",
- i,
- tc.link,
- tc.isZettel,
- isZettel)
- }
- isLocal := ref.IsLocal()
- if isLocal != tc.isLocal {
- t.Errorf(
- "TC=%d, Reference %q isLocal=%v expected, but got %v",
- i,
- tc.link,
- tc.isLocal, isLocal)
- }
- isExternal := ref.IsExternal()
- if isExternal != tc.isExternal {
- t.Errorf(
- "TC=%d, Reference %q isExternal=%v expected, but got %v",
- i,
- tc.link,
- tc.isExternal,
- isExternal)
- }
- }
-}
DELETED internal/ast/sztrans/sztrans.go
Index: internal/ast/sztrans/sztrans.go
==================================================================
--- internal/ast/sztrans/sztrans.go
+++ /dev/null
@@ -1,651 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2025-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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: 2025-present Detlef Stern
-//-----------------------------------------------------------------------------
-
-// Package sztrans allows to transform a sz representation of text into an
-// abstract syntax tree.
-package sztrans
-
-import (
- "fmt"
- "log"
-
- "t73f.de/r/sx"
- "t73f.de/r/zsc/sz"
- "t73f.de/r/zsx"
-
- "zettelstore.de/z/internal/ast"
-)
-
-type transformer struct{}
-
-// GetBlockSlice returns the sz representations as a AST BlockSlice
-func GetBlockSlice(pair *sx.Pair) (ast.BlockSlice, error) {
- if pair == nil {
- return nil, nil
- }
- var t transformer
- if obj := zsx.Walk(&t, pair, nil); !obj.IsNil() {
- if sxn, isNode := obj.(sxNode); isNode {
- if bs, ok := sxn.node.(*ast.BlockSlice); ok {
- return *bs, nil
- }
- return nil, fmt.Errorf("no BlockSlice AST: %T/%v for %v", sxn.node, sxn.node, pair)
- }
- return nil, fmt.Errorf("no AST for %v: %v", pair, obj)
- }
- return nil, fmt.Errorf("error walking %v", pair)
-}
-
-func (t *transformer) VisitBefore(pair *sx.Pair, _ *sx.Pair) (sx.Object, bool) {
- if sym, isSymbol := sx.GetSymbol(pair.Car()); isSymbol {
- switch sym {
- case zsx.SymText:
- if p := pair.Tail(); p != nil {
- if s, isString := sx.GetString(p.Car()); isString {
- return sxNode{&ast.TextNode{Text: s.GetValue()}}, true
- }
- }
- case zsx.SymSoft:
- return sxNode{&ast.BreakNode{Hard: false}}, true
- case zsx.SymHard:
- return sxNode{&ast.BreakNode{Hard: true}}, true
- case zsx.SymLiteralCode:
- return handleLiteral(ast.LiteralCode, pair.Tail())
- case zsx.SymLiteralComment:
- return handleLiteral(ast.LiteralComment, pair.Tail())
- case zsx.SymLiteralInput:
- return handleLiteral(ast.LiteralInput, pair.Tail())
- case zsx.SymLiteralMath:
- return handleLiteral(ast.LiteralMath, pair.Tail())
- case zsx.SymLiteralOutput:
- return handleLiteral(ast.LiteralOutput, pair.Tail())
- case zsx.SymThematic:
- return sxNode{&ast.HRuleNode{Attrs: zsx.GetAttributes(pair.Tail().Head())}}, true
- case zsx.SymVerbatimComment:
- return handleVerbatim(ast.VerbatimComment, pair.Tail())
- case zsx.SymVerbatimEval:
- return handleVerbatim(ast.VerbatimEval, pair.Tail())
- case zsx.SymVerbatimHTML:
- return handleVerbatim(ast.VerbatimHTML, pair.Tail())
- case zsx.SymVerbatimMath:
- return handleVerbatim(ast.VerbatimMath, pair.Tail())
- case zsx.SymVerbatimCode:
- return handleVerbatim(ast.VerbatimCode, pair.Tail())
- case zsx.SymVerbatimZettel:
- return handleVerbatim(ast.VerbatimZettel, pair.Tail())
- }
- }
- return sx.Nil(), false
-}
-
-func handleLiteral(kind ast.LiteralKind, rest *sx.Pair) (sx.Object, bool) {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if s, isString := sx.GetString(curr.Car()); isString {
- return sxNode{&ast.LiteralNode{
- Kind: kind,
- Attrs: attrs,
- Content: []byte(s.GetValue())}}, true
- }
- }
- }
- return nil, false
-}
-
-func handleVerbatim(kind ast.VerbatimKind, rest *sx.Pair) (sx.Object, bool) {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if s, isString := sx.GetString(curr.Car()); isString {
- return sxNode{&ast.VerbatimNode{
- Kind: kind,
- Attrs: attrs,
- Content: []byte(s.GetValue()),
- }}, true
- }
- }
- }
- return nil, false
-}
-
-func (t *transformer) VisitAfter(pair *sx.Pair, _ *sx.Pair) sx.Object {
- if sym, isSymbol := sx.GetSymbol(pair.Car()); isSymbol {
- switch sym {
- case zsx.SymBlock:
- bns := collectBlocks(pair.Tail())
- return sxNode{&bns}
- case zsx.SymPara:
- return sxNode{&ast.ParaNode{Inlines: collectInlines(pair.Tail())}}
- case zsx.SymHeading:
- return handleHeading(pair.Tail())
- case zsx.SymListOrdered:
- return handleList(ast.NestedListOrdered, pair.Tail())
- case zsx.SymListUnordered:
- return handleList(ast.NestedListUnordered, pair.Tail())
- case zsx.SymListQuote:
- return handleList(ast.NestedListQuote, pair.Tail())
- case zsx.SymDescription:
- return handleDescription(pair.Tail())
- case zsx.SymTable:
- return handleTable(pair.Tail())
- case zsx.SymCell:
- return handleCell(pair.Tail())
- case zsx.SymRegionBlock:
- return handleRegion(ast.RegionSpan, pair.Tail())
- case zsx.SymRegionQuote:
- return handleRegion(ast.RegionQuote, pair.Tail())
- case zsx.SymRegionVerse:
- return handleRegion(ast.RegionVerse, pair.Tail())
- case zsx.SymTransclude:
- return handleTransclude(pair.Tail())
- case zsx.SymBLOB:
- return handleBLOB(pair.Tail())
-
- case zsx.SymLink:
- return handleLink(pair.Tail())
- case zsx.SymEmbed:
- return handleEmbed(pair.Tail())
- case zsx.SymEmbedBLOB:
- return handleEmbedBLOB(pair.Tail())
- case zsx.SymCite:
- return handleCite(pair.Tail())
- case zsx.SymMark:
- return handleMark(pair.Tail())
- case zsx.SymEndnote:
- return handleEndnote(pair.Tail())
- case zsx.SymFormatDelete:
- return handleFormat(ast.FormatDelete, pair.Tail())
- case zsx.SymFormatEmph:
- return handleFormat(ast.FormatEmph, pair.Tail())
- case zsx.SymFormatInsert:
- return handleFormat(ast.FormatInsert, pair.Tail())
- case zsx.SymFormatMark:
- return handleFormat(ast.FormatMark, pair.Tail())
- case zsx.SymFormatQuote:
- return handleFormat(ast.FormatQuote, pair.Tail())
- case zsx.SymFormatSpan:
- return handleFormat(ast.FormatSpan, pair.Tail())
- case zsx.SymFormatSub:
- return handleFormat(ast.FormatSub, pair.Tail())
- case zsx.SymFormatSuper:
- return handleFormat(ast.FormatSuper, pair.Tail())
- case zsx.SymFormatStrong:
- return handleFormat(ast.FormatStrong, pair.Tail())
- }
- log.Println("MISS", pair)
- }
- return pair
-}
-
-func collectBlocks(lst *sx.Pair) (result ast.BlockSlice) {
- for val := range lst.Values() {
- if sxn, isNode := val.(sxNode); isNode {
- if bn, isInline := sxn.node.(ast.BlockNode); isInline {
- result = append(result, bn)
- }
- }
- }
- return result
-}
-
-func collectInlines(lst *sx.Pair) (result ast.InlineSlice) {
- for val := range lst.Values() {
- if sxn, isNode := val.(sxNode); isNode {
- if in, isInline := sxn.node.(ast.InlineNode); isInline {
- result = append(result, in)
- }
- }
- }
- return result
-}
-
-func handleHeading(rest *sx.Pair) sx.Object {
- if rest != nil {
- if num, isNumber := rest.Car().(sx.Int64); isNumber && num > 0 && num < 6 {
- if curr := rest.Tail(); curr != nil {
- attrs := zsx.GetAttributes(curr.Head())
- if curr = curr.Tail(); curr != nil {
- if sSlug, isSlug := sx.GetString(curr.Car()); isSlug {
- if curr = curr.Tail(); curr != nil {
- if sUniq, isUniq := sx.GetString(curr.Car()); isUniq {
- return sxNode{&ast.HeadingNode{
- Level: int(num),
- Attrs: attrs,
- Slug: sSlug.GetValue(),
- Fragment: sUniq.GetValue(),
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- }
- }
- }
- }
- }
- log.Println("HEAD", rest)
- return rest
-}
-
-func handleList(kind ast.NestedListKind, rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- return sxNode{&ast.NestedListNode{
- Kind: kind,
- Items: collectItemSlices(rest.Tail()),
- Attrs: attrs}}
- }
- log.Println("LIST", kind, rest)
- return rest
-}
-
-func collectItemSlices(lst *sx.Pair) (result []ast.ItemSlice) {
- for val := range lst.Values() {
- if sxn, isNode := val.(sxNode); isNode {
- if bns, isBlockSlice := sxn.node.(*ast.BlockSlice); isBlockSlice {
- items := make(ast.ItemSlice, len(*bns))
- for i, bn := range *bns {
- if it, ok := bn.(ast.ItemNode); ok {
- items[i] = it
- }
- }
- result = append(result, items)
- }
- if ins, isInline := sxn.node.(*ast.InlineSlice); isInline {
- items := make(ast.ItemSlice, len(*ins))
- for i, bn := range *ins {
- if it, ok := bn.(ast.ItemNode); ok {
- items[i] = it
- }
- }
- result = append(result, items)
- }
- }
- }
- return result
-}
-
-func handleDescription(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- var descs []ast.Description
- for curr := rest.Tail(); curr != nil; {
- term := collectInlines(curr.Head())
- curr = curr.Tail()
- if curr == nil {
- descr := ast.Description{Term: term, Descriptions: nil}
- descs = append(descs, descr)
- break
- }
-
- car := curr.Car()
- if sx.IsNil(car) {
- descs = append(descs, ast.Description{Term: term, Descriptions: nil})
- curr = curr.Tail()
- continue
- }
-
- sxn, isNode := car.(sxNode)
- if !isNode {
- descs = nil
- break
- }
- blocks, isBlocks := sxn.node.(*ast.BlockSlice)
- if !isBlocks {
- descs = nil
- break
- }
-
- descSlice := make([]ast.DescriptionSlice, 0, len(*blocks))
- for _, bn := range *blocks {
- bns, isBns := bn.(*ast.BlockSlice)
- if !isBns {
- continue
- }
- ds := make(ast.DescriptionSlice, 0, len(*bns))
- for _, b := range *bns {
- if defNode, isDef := b.(ast.DescriptionNode); isDef {
- ds = append(ds, defNode)
- }
- }
- descSlice = append(descSlice, ds)
- }
-
- descr := ast.Description{Term: term, Descriptions: descSlice}
- descs = append(descs, descr)
-
- curr = curr.Tail()
- }
- if len(descs) > 0 {
- return sxNode{&ast.DescriptionListNode{
- Attrs: attrs,
- Descriptions: descs,
- }}
- }
- }
- log.Println("DESC", rest)
- return rest
-}
-
-func handleTable(rest *sx.Pair) sx.Object {
- if rest != nil {
- header := collectRow(rest.Head())
- cols := len(header)
-
- var rows []ast.TableRow
- for curr := range rest.Tail().Pairs() {
- row := collectRow(curr.Head())
- rows = append(rows, row)
- cols = max(cols, len(row))
- }
- align := make([]ast.Alignment, cols)
- for i := range cols {
- align[i] = ast.AlignDefault
- }
-
- return sxNode{&ast.TableNode{
- Header: header,
- Align: align,
- Rows: rows,
- }}
- }
- log.Println("TABL", rest)
- return rest
-}
-
-func collectRow(lst *sx.Pair) (row ast.TableRow) {
- for curr := range lst.Values() {
- if sxn, isNode := curr.(sxNode); isNode {
- if cell, isCell := sxn.node.(*ast.TableCell); isCell {
- row = append(row, cell)
- }
- }
- }
- return row
-}
-
-func handleCell(rest *sx.Pair) sx.Object {
- if rest != nil {
- align := ast.AlignDefault
- if alignPair := rest.Head().Assoc(zsx.SymAttrAlign); alignPair != nil {
- if alignValue := alignPair.Cdr(); zsx.AttrAlignCenter.IsEqual(alignValue) {
- align = ast.AlignCenter
- } else if zsx.AttrAlignLeft.IsEqual(alignValue) {
- align = ast.AlignLeft
- } else if zsx.AttrAlignRight.IsEqual(alignValue) {
- align = ast.AlignRight
- }
- }
- return sxNode{&ast.TableCell{
- Align: align,
- Inlines: collectInlines(rest.Tail()),
- }}
- }
- log.Println("CELL", rest)
- return rest
-}
-
-func handleRegion(kind ast.RegionKind, rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if blockList := curr.Head(); blockList != nil {
- return sxNode{&ast.RegionNode{
- Kind: kind,
- Attrs: attrs,
- Blocks: collectBlocks(blockList),
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- }
- log.Println("REGI", rest)
- return rest
-}
-
-func handleTransclude(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- ref := collectReference(curr.Head())
- return sxNode{&ast.TranscludeNode{
- Attrs: attrs,
- Ref: ref,
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- log.Println("TRAN", rest)
- return rest
-}
-
-func handleBLOB(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- ins := collectInlines(curr.Head())
- if curr = curr.Tail(); curr != nil {
- if syntax, isString := sx.GetString(curr.Car()); isString {
- if curr = curr.Tail(); curr != nil {
- if blob, isBlob := sx.GetString(curr.Car()); isBlob {
- return sxNode{&ast.BLOBNode{
- Attrs: attrs,
- Description: ins,
- Syntax: syntax.GetValue(),
- Blob: []byte(blob.GetValue()),
- }}
-
- }
- }
- }
- }
- }
- }
- log.Println("BLOB", rest)
- return rest
-}
-
-var mapRefState = map[*sx.Symbol]ast.RefState{
- zsx.SymRefStateInvalid: ast.RefStateInvalid,
- sz.SymRefStateZettel: ast.RefStateZettel,
- zsx.SymRefStateSelf: ast.RefStateSelf,
- sz.SymRefStateFound: ast.RefStateFound,
- sz.SymRefStateBroken: ast.RefStateBroken,
- zsx.SymRefStateHosted: ast.RefStateHosted,
- sz.SymRefStateBased: ast.RefStateBased,
- sz.SymRefStateQuery: ast.RefStateQuery,
- zsx.SymRefStateExternal: ast.RefStateExternal,
-}
-
-func handleLink(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if szref := curr.Head(); szref != nil {
- if stateSym, isSym := sx.GetSymbol(szref.Car()); isSym {
- refval, isString := sx.GetString(szref.Cdr())
- if !isString {
- refval, isString = sx.GetString(szref.Tail().Car())
- }
- if isString {
- ref := ast.ParseReference(refval.GetValue())
- ref.State = mapRefState[stateSym]
- ins := collectInlines(curr.Tail())
- return sxNode{&ast.LinkNode{
- Attrs: attrs,
- Ref: ref,
- Inlines: ins,
- }}
- }
- }
- }
- }
- }
- log.Println("LINK", rest)
- return rest
-}
-
-func handleEmbed(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if ref := collectReference(curr.Head()); ref != nil {
- if curr = curr.Tail(); curr != nil {
- if syntax, isString := sx.GetString(curr.Car()); isString {
- return sxNode{&ast.EmbedRefNode{
- Attrs: attrs,
- Ref: ref,
- Syntax: syntax.GetValue(),
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- }
- }
- }
- log.Println("EMBE", rest)
- return rest
-}
-
-func handleEmbedBLOB(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if syntax, isSyntax := sx.GetString(curr.Car()); isSyntax {
- if curr = curr.Tail(); curr != nil {
- if content, isContent := sx.GetString(curr.Car()); isContent {
- return sxNode{&ast.EmbedBLOBNode{
- Attrs: attrs,
- Syntax: syntax.GetValue(),
- Blob: []byte(content.GetValue()),
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- }
- }
- }
- log.Println("EMBL", rest)
- return rest
-}
-
-func collectReference(pair *sx.Pair) *ast.Reference {
- if pair != nil {
- if sym, isSymbol := sx.GetSymbol(pair.Car()); isSymbol {
- if next := pair.Tail(); next != nil {
- if sRef, isString := sx.GetString(next.Car()); isString {
- ref := ast.ParseReference(sRef.GetValue())
- switch sym {
- case zsx.SymRefStateInvalid:
- ref.State = ast.RefStateInvalid
- case sz.SymRefStateZettel:
- ref.State = ast.RefStateZettel
- case zsx.SymRefStateSelf:
- ref.State = ast.RefStateSelf
- case sz.SymRefStateFound:
- ref.State = ast.RefStateFound
- case sz.SymRefStateBroken:
- ref.State = ast.RefStateBroken
- case zsx.SymRefStateHosted:
- ref.State = ast.RefStateHosted
- case sz.SymRefStateBased:
- ref.State = ast.RefStateBased
- case sz.SymRefStateQuery:
- ref.State = ast.RefStateQuery
- case zsx.SymRefStateExternal:
- ref.State = ast.RefStateExternal
- }
- return ref
- }
- }
- }
- }
- return nil
-}
-
-func handleCite(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- if curr := rest.Tail(); curr != nil {
- if sKey, isString := sx.GetString(curr.Car()); isString {
- return sxNode{&ast.CiteNode{
- Attrs: attrs,
- Key: sKey.GetValue(),
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- }
- log.Println("CITE", rest)
- return rest
-}
-
-func handleMark(rest *sx.Pair) sx.Object {
- if rest != nil {
- if sMark, isMarkS := sx.GetString(rest.Car()); isMarkS {
- if curr := rest.Tail(); curr != nil {
- if sSlug, isSlug := sx.GetString(curr.Car()); isSlug {
- if curr = curr.Tail(); curr != nil {
- if sUniq, isUniq := sx.GetString(curr.Car()); isUniq {
- return sxNode{&ast.MarkNode{
- Mark: sMark.GetValue(),
- Slug: sSlug.GetValue(),
- Fragment: sUniq.GetValue(),
- Inlines: collectInlines(curr.Tail()),
- }}
- }
- }
- }
- }
- }
- }
- log.Println("MARK", rest)
- return rest
-}
-
-func handleEndnote(rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- return sxNode{&ast.FootnoteNode{
- Attrs: attrs,
- Inlines: collectInlines(rest.Tail()),
- }}
- }
- log.Println("ENDN", rest)
- return rest
-}
-
-func handleFormat(kind ast.FormatKind, rest *sx.Pair) sx.Object {
- if rest != nil {
- attrs := zsx.GetAttributes(rest.Head())
- return sxNode{&ast.FormatNode{
- Kind: kind,
- Attrs: attrs,
- Inlines: collectInlines(rest.Tail()),
- }}
- }
- log.Println("FORM", kind, rest)
- return rest
-}
-
-type sxNode struct {
- node ast.Node
-}
-
-func (sxNode) IsNil() bool { return false }
-func (sxNode) IsAtom() bool { return true }
-func (n sxNode) String() string { return fmt.Sprintf("%T/%v", n.node, n.node) }
-func (n sxNode) GoString() string { return n.String() }
-func (n sxNode) IsEqual(other sx.Object) bool {
- return n.String() == other.String()
-}
DELETED internal/ast/walk.go
Index: internal/ast/walk.go
==================================================================
--- internal/ast/walk.go
+++ /dev/null
@@ -1,48 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2021-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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: 2021-present Detlef Stern
-//-----------------------------------------------------------------------------
-
-package ast
-
-// Visitor is a visitor for walking the AST.
-type Visitor interface {
- Visit(node Node) Visitor
-}
-
-// Walk traverses the AST.
-func Walk(v Visitor, node Node) {
- if v = v.Visit(node); v == nil {
- return
- }
-
- // Implementation note:
- // It is much faster to use interface dispatching than to use a switch statement.
- // On my "cpu: Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz", a switch statement
- // implementation tooks approx 940-980 ns/op. Interface dispatching is in the
- // range of 900-930 ns/op.
- node.WalkChildren(v)
- v.Visit(nil)
-}
-
-// WalkItemSlice traverses an item slice.
-func WalkItemSlice(v Visitor, ins ItemSlice) {
- for _, in := range ins {
- Walk(v, in)
- }
-}
-
-// WalkDescriptionSlice traverses an item slice.
-func WalkDescriptionSlice(v Visitor, dns DescriptionSlice) {
- for _, dn := range dns {
- Walk(v, dn)
- }
-}
DELETED internal/ast/walk_test.go
Index: internal/ast/walk_test.go
==================================================================
--- internal/ast/walk_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2021-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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: 2021-present Detlef Stern
-//-----------------------------------------------------------------------------
-
-package ast_test
-
-import (
- "testing"
-
- "t73f.de/r/zsx"
-
- "zettelstore.de/z/internal/ast"
-)
-
-func BenchmarkWalk(b *testing.B) {
- root := ast.BlockSlice{
- &ast.HeadingNode{
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "A Simple Heading"}},
- },
- &ast.ParaNode{
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "This is the introduction."}},
- },
- &ast.NestedListNode{
- Kind: ast.NestedListUnordered,
- Items: []ast.ItemSlice{
- []ast.ItemNode{
- &ast.ParaNode{
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "Item 1"}},
- },
- },
- []ast.ItemNode{
- &ast.ParaNode{
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "Item 2"}},
- },
- },
- },
- },
- &ast.ParaNode{
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "This is some intermediate text."}},
- },
- ast.CreateParaNode(
- &ast.FormatNode{
- Kind: ast.FormatEmph,
- Attrs: zsx.Attributes(map[string]string{
- "": "class",
- "color": "green",
- }),
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "This is some emphasized text."}},
- },
- &ast.TextNode{Text: " "},
- &ast.LinkNode{
- Ref: &ast.Reference{Value: "http://zettelstore.de"},
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "URL text."}},
- },
- ),
- }
- v := benchVisitor{}
-
- for b.Loop() {
- ast.Walk(&v, &root)
- }
-}
-
-type benchVisitor struct{}
-
-func (bv *benchVisitor) Visit(ast.Node) ast.Visitor { return bv }
Index: internal/box/constbox/base.sxn
==================================================================
--- internal/box/constbox/base.sxn
+++ internal/box/constbox/base.sxn
@@ -10,53 +10,53 @@
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(@@@@
-(html ,@(if lang `((@ (lang ,lang))))
+(html ,@(if lang `(((lang ,lang))))
(head
- (meta (@ (charset "utf-8")))
- (meta (@ (name "viewport") (content "width=device-width, initial-scale=1.0")))
- (meta (@ (name "generator") (content "Zettelstore")))
- (meta (@ (name "format-detection") (content "telephone=no")))
+ (meta ((charset "utf-8")))
+ (meta ((name "viewport") (content "width=device-width, initial-scale=1.0")))
+ (meta ((name "generator") (content "Zettelstore")))
+ (meta ((name "format-detection") (content "telephone=no")))
,@META-HEADER
- (link (@ (rel "stylesheet") (href ,css-base-url)))
- (link (@ (rel "stylesheet") (href ,css-user-url)))
+ (link ((rel "stylesheet") (href ,css-base-url)))
+ (link ((rel "stylesheet") (href ,css-user-url)))
,@(ROLE-DEFAULT-meta (current-frame))
,@(let* ((frame (current-frame))(rem (resolve-symbol 'ROLE-EXTRA-meta frame))) (if (defined? rem) (rem frame)))
(title ,title))
(body
- (nav (@ (class "zs-menu"))
- (a (@ (href ,home-url)) "Home")
- ,@(if with-auth
- `((div (@ (class "zs-dropdown"))
- (button "User")
- (nav (@ (class "zs-dropdown-content"))
- ,@(if user-is-valid
- `((a (@ (href ,user-zettel-url)) ,user-ident)
- (a (@ (href ,logout-url)) "Logout"))
- `((a (@ (href ,login-url)) "Login"))
- )
- )))
- )
- (div (@ (class "zs-dropdown"))
- (button "Lists")
- (nav (@ (class "zs-dropdown-content"))
- ,@list-urls
- ,@(if (symbol-bound? 'refresh-url) `((a (@ (href ,refresh-url)) "Refresh")))
- ))
- ,@(if new-zettel-links
- `((div (@ (class "zs-dropdown"))
- (button "New")
- (nav (@ (class "zs-dropdown-content"))
- ,@(map wui-link new-zettel-links)
- )))
- )
- (search (form (@ (action ,search-url))
- (input (@ (type "search") (inputmode "search") (name ,query-key-query)
- (title "General search field, with same behaviour as search field in search result list")
- (placeholder "Search..") (dir "auto")))))
- )
- (main (@ (class "content")) ,DETAIL)
+ (nav ((class "zs-menu"))
+ ,(wui-href home-url "Home")
+ ,@(if with-auth
+ `((div ((class "zs-dropdown"))
+ (button "User")
+ (nav ((class "zs-dropdown-content"))
+ ,@(if user-is-valid
+ `(,(wui-href user-zettel-url user-ident)
+ ,(wui-href logout-url "Logout"))
+ `(,(wui-href login-url "Login"))
+ )
+ )))
+ )
+ (div ((class "zs-dropdown"))
+ (button "Lists")
+ (nav ((class "zs-dropdown-content"))
+ ,@list-urls
+ ,(if (symbol-bound? 'refresh-url) (wui-href refresh-url "Refresh"))
+ ))
+ ,@(if new-zettel-links
+ `((div ((class "zs-dropdown"))
+ (button "New")
+ (nav ((class "zs-dropdown-content"))
+ ,@(map wui-link new-zettel-links)
+ )))
+ )
+ (search (form ((action ,search-url))
+ (input ((type "search") (inputmode "search") (name ,query-key-query)
+ (title "General search field, with same behaviour as search field in search result list")
+ (placeholder "Search..") (dir "auto")))))
+ )
+ (main ((class "content")) ,DETAIL)
,@(if FOOTER `((footer (hr) ,@FOOTER)))
,@(if debug-mode '((div (b "WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!"))))
)))
Index: internal/box/constbox/constbox.go
==================================================================
--- internal/box/constbox/constbox.go
+++ internal/box/constbox/constbox.go
@@ -164,71 +164,71 @@
constHeader{
meta.KeyTitle: "Zettelstore Base HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230510155100",
- meta.KeyModified: "20250623131400",
+ meta.KeyModified: "20250806182400",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentBaseSxn)},
id.ZidLoginTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Login Form HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
- meta.KeyModified: "20240219145200",
+ meta.KeyModified: "20250806182600",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentLoginSxn)},
id.ZidZettelTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Zettel HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230510155300",
- meta.KeyModified: "20250626113800",
+ meta.KeyModified: "20250806182700",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentZettelSxn)},
id.ZidInfoTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Info HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
- meta.KeyModified: "20250624160000",
+ meta.KeyModified: "20250806182500",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentInfoSxn)},
id.ZidFormTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Form HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
- meta.KeyModified: "20250612180300",
+ meta.KeyModified: "20250806182500",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentFormSxn)},
id.ZidDeleteTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Delete HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
- meta.KeyModified: "20250612180200",
+ meta.KeyModified: "20250806182500",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentDeleteSxn)},
id.ZidListTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore List Zettel HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230704122100",
- meta.KeyModified: "20250612180200",
+ meta.KeyModified: "20250806182600",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentListZettelSxn)},
id.ZidErrorTemplate: {
constHeader{
@@ -255,11 +255,11 @@
constHeader{
meta.KeyTitle: "Zettelstore Sxn Base Code",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230619132800",
- meta.KeyModified: "20250624200200",
+ meta.KeyModified: "20250806182700",
meta.KeyReadOnly: meta.ValueTrue,
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentBaseCodeSxn)},
id.ZidBaseCSS: {
Index: internal/box/constbox/delete.sxn
==================================================================
--- internal/box/constbox/delete.sxn
+++ internal/box/constbox/delete.sxn
@@ -13,27 +13,27 @@
`(article
(header (h1 "Delete Zettel " ,zid))
(p "Do you really want to delete this zettel?")
,@(if shadowed-box
- `((div (@ (class "zs-info"))
+ `((div ((class "zs-info"))
(h2 "Information")
(p "If you delete this zettel, the previously shadowed zettel from overlayed box " ,shadowed-box " becomes available.")
))
)
,@(if incoming
- `((div (@ (class "zs-warning"))
+ `((div ((class "zs-warning"))
(h2 "Warning!")
(p "If you delete this zettel, incoming references from the following zettel will become invalid.")
(ul ,@(map wui-item-link incoming))
))
)
,@(if (and (symbol-bound? 'useless) useless)
- `((div (@ (class "zs-warning"))
+ `((div ((class "zs-warning"))
(h2 "Warning!")
(p "Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.")
(ul ,@(map wui-item useless))
))
)
,(wui-meta-desc metapairs)
- (form (@ (method "POST")) (input (@ (class "zs-primary") (type "submit") (value "Delete"))))
+ (form ((method "POST")) (input ((class "zs-primary") (type "submit") (value "Delete"))))
)
Index: internal/box/constbox/form.sxn
==================================================================
--- internal/box/constbox/form.sxn
+++ internal/box/constbox/form.sxn
@@ -11,53 +11,53 @@
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 ,heading))
- (form (@ (action ,form-action-url) (method "POST") (enctype "multipart/form-data"))
+ (form ((action ,form-action-url) (method "POST") (enctype "multipart/form-data"))
(div
- (label (@ (for "zs-title")) "Title " (a (@ (title "Main heading of this zettel.")) (@H "ⓘ")))
- (input (@ (class "zs-input") (type "text") (id "zs-title") (name "title")
+ (label ((for "zs-title")) "Title " (a ((title "Main heading of this zettel.")) (@H "ⓘ")))
+ (input ((class "zs-input") (type "text") (id "zs-title") (name "title")
(title "Title of this zettel")
(placeholder "Title..") (value ,meta-title) (dir "auto") (autofocus))))
(div
- (label (@ (for "zs-role")) "Role " (a (@ (title "One word, without spaces, to set the main role of this zettel.")) (@H "ⓘ")))
- (input (@ (class "zs-input") (type "text") (pattern "\\w*") (id "zs-role") (name "role")
+ (label ((for "zs-role")) "Role " (a ((title "One word, without spaces, to set the main role of this zettel.")) (@H "ⓘ")))
+ (input ((class "zs-input") (type "text") (pattern "\\w*") (id "zs-role") (name "role")
(title "One word, letters and digits, but no spaces, to set the main role of the zettel.")
(placeholder "role..") (value ,meta-role) (dir "auto")
,@(if role-data '((list "zs-role-data")))
))
,@(wui-datalist "zs-role-data" role-data)
)
(div
- (label (@ (for "zs-tags")) "Tags " (a (@ (title "Tags must begin with an '#' sign. They are separated by spaces.")) (@H "ⓘ")))
- (input (@ (class "zs-input") (type "text") (id "zs-tags") (name "tags")
+ (label ((for "zs-tags")) "Tags " (a ((title "Tags must begin with an '#' sign. They are separated by spaces.")) (@H "ⓘ")))
+ (input ((class "zs-input") (type "text") (id "zs-tags") (name "tags")
(title "Tags/keywords to categorize the zettel. Each tags is a word that begins with a '#' character; they are separated by spaces")
(placeholder "#tag") (value ,meta-tags) (dir "auto"))))
(div
- (label (@ (for "zs-meta")) "Metadata " (a (@ (title "Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.")) (@H "ⓘ")))
- (textarea (@ (class "zs-input") (id "zs-meta") (name "meta") (rows "4")
+ (label ((for "zs-meta")) "Metadata " (a ((title "Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.")) (@H "ⓘ")))
+ (textarea ((class "zs-input") (id "zs-meta") (name "meta") (rows "4")
(title "Additional metadata about the zettel")
(placeholder "metakey: metavalue") (dir "auto")) ,meta))
(div
- (label (@ (for "zs-syntax")) "Syntax " (a (@ (title "Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).")) (@H "ⓘ")))
- (input (@ (class "zs-input") (type "text") (pattern "\\w*") (id "zs-syntax") (name "syntax")
+ (label ((for "zs-syntax")) "Syntax " (a ((title "Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).")) (@H "ⓘ")))
+ (input ((class "zs-input") (type "text") (pattern "\\w*") (id "zs-syntax") (name "syntax")
(title "Syntax/format of zettel content below, one word, letters and digits, no spaces.")
(placeholder "syntax..") (value ,meta-syntax) (dir "auto")
,@(if syntax-data '((list "zs-syntax-data")))
))
,@(wui-datalist "zs-syntax-data" syntax-data)
)
,@(if (symbol-bound? 'content)
`((div
- (label (@ (for "zs-content")) "Content " (a (@ (title "Content for this zettel, according to above syntax.")) (@H "ⓘ")))
- (textarea (@ (class "zs-input zs-content") (id "zs-content") (name "content") (rows "20")
+ (label ((for "zs-content")) "Content " (a ((title "Content for this zettel, according to above syntax.")) (@H "ⓘ")))
+ (textarea ((class "zs-input zs-content") (id "zs-content") (name "content") (rows "20")
(title "Zettel content, according to the given syntax")
(placeholder "Zettel content..") (dir "auto")) ,content)
))
)
(div
- (input (@ (class "zs-primary") (type "submit") (value "Submit")))
- (input (@ (class "zs-secondary") (type "submit") (value "Save") (formaction "?save")))
- (input (@ (class "zs-upload") (type "file") (id "zs-file") (name "file")))
+ (input ((class "zs-primary") (type "submit") (value "Submit")))
+ (input ((class "zs-secondary") (type "submit") (value "Save") (formaction "?save")))
+ (input ((class "zs-upload") (type "file") (id "zs-file") (name "file")))
))
)
Index: internal/box/constbox/info.sxn
==================================================================
--- internal/box/constbox/info.sxn
+++ internal/box/constbox/info.sxn
@@ -12,22 +12,22 @@
;;;----------------------------------------------------------------------------
`(article
(header (h1 "Information for Zettel " ,zid)
(p
- (a (@ (href ,web-url)) "Web")
- ,@(if (symbol-bound? 'edit-url) `((@H " · ") (a (@ (href ,edit-url)) "Edit")))
- (@H " · ") (a (@ (href ,context-full-url)) "Full Context")
+ ,(wui-href web-url "Web")
+ ,@(if (symbol-bound? 'edit-url) `((@H " · ") ,(wui-href edit-url "Edit")))
+ (@H " · ") ,(wui-href context-full-url "Full Context")
,@(if (symbol-bound? 'thread-query-url)
- `((@H " · [") (a (@ (href ,thread-query-url)) "Thread")
- ,@(if (symbol-bound? 'folge-query-url) `((@H ", ") (a (@ (href ,folge-query-url)) "Folge")))
- ,@(if (symbol-bound? 'sequel-query-url) `((@H ", ") (a (@ (href ,sequel-query-url)) "Sequel")))
+ `((@H " · [") ,(wui-href thread-query-url "Thread")
+ ,@(if (symbol-bound? 'folge-query-url) `((@H ", ") ,(wui-href folge-query-url "Folge")))
+ ,@(if (symbol-bound? 'sequel-query-url) `((@H ", ") ,(wui-href sequel-query-url "Sequel")))
(@H "]")))
,@(ROLE-DEFAULT-actions (current-frame))
,@(let* ((frame (current-frame))(rea (resolve-symbol 'ROLE-EXTRA-actions frame))) (if (defined? rea) (rea frame)))
- ,@(if (symbol-bound? 'reindex-url) `((@H " · ") (a (@ (href ,reindex-url)) "Reindex")))
- ,@(if (symbol-bound? 'delete-url) `((@H " · ") (a (@ (href ,delete-url)) "Delete")))
+ ,@(if (symbol-bound? 'reindex-url) `((@H " · ") ,(wui-href reindex-url "Reindex")))
+ ,@(if (symbol-bound? 'delete-url) `((@H " · ") ,(wui-href delete-url "Delete")))
)
)
(h2 "Interpreted Metadata")
(table ,@(map wui-info-meta-table-row metadata))
(h2 "References")
@@ -35,12 +35,12 @@
,@(if query-links `((h3 "Queries") (ul ,@(map wui-item-link query-links))))
,@(if ext-links `((h3 "External") (ul ,@(map wui-item-popup-link ext-links))))
(h3 "Unlinked")
,@unlinked-content
(form
- (label (@ (for "phrase")) "Search Phrase")
- (input (@ (class "zs-input") (type "text") (id "phrase") (name ,query-key-phrase) (placeholder "Phrase..") (value ,phrase)))
+ (label ((for "phrase")) "Search Phrase")
+ (input ((class "zs-input") (type "text") (id "phrase") (name ,query-key-phrase) (placeholder "Phrase..") (value ,phrase)))
)
(h2 "Parts and encodings")
,(wui-enc-matrix enc-eval)
(h3 "Parsed (not evaluated)")
,(wui-enc-matrix enc-parsed)
Index: internal/box/constbox/listzettel.sxn
==================================================================
--- internal/box/constbox/listzettel.sxn
+++ internal/box/constbox/listzettel.sxn
@@ -11,40 +11,40 @@
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 ,heading))
- (search (form (@ (action ,search-url))
- (input (@ (class "zs-input") (type "search") (inputmode "search") (name ,query-key-query)
+ (search (form ((action ,search-url))
+ (input ((class "zs-input") (type "search") (inputmode "search") (name ,query-key-query)
(title "Contains the search that leads to the list below. You're allowed to modify it")
(placeholder "Search..") (value ,query-value) (dir "auto")))))
,@(if (symbol-bound? 'tag-zettel)
- `((p (@ (class "zs-meta-zettel")) "Tag zettel: " ,@tag-zettel))
+ `((p ((class "zs-meta-zettel")) "Tag zettel: " ,@tag-zettel))
)
,@(if (symbol-bound? 'create-tag-zettel)
- `((p (@ (class "zs-meta-zettel")) "Create tag zettel: " ,@create-tag-zettel))
+ `((p ((class "zs-meta-zettel")) "Create tag zettel: " ,@create-tag-zettel))
)
,@(if (symbol-bound? 'role-zettel)
- `((p (@ (class "zs-meta-zettel")) "Role zettel: " ,@role-zettel))
+ `((p ((class "zs-meta-zettel")) "Role zettel: " ,@role-zettel))
)
,@(if (symbol-bound? 'create-role-zettel)
- `((p (@ (class "zs-meta-zettel")) "Create role zettel: " ,@create-role-zettel))
+ `((p ((class "zs-meta-zettel")) "Create role zettel: " ,@create-role-zettel))
)
,@content
,@endnotes
- (form (@ (action ,(if (symbol-bound? 'create-url) create-url)))
+ (form ((action ,(if (symbol-bound? 'create-url) create-url)))
,(if (symbol-bound? 'data-url)
`(@L "Other encodings"
,(if (> num-entries 3) `(@L " of these " ,num-entries " entries: ") ": ")
- (a (@ (href ,data-url)) "data")
+ ,(wui-href data-url "data")
", "
- (a (@ (href ,plain-url)) "plain")
+ ,(wui-href plain-url "plain")
)
)
,@(if (symbol-bound? 'create-url)
- `((input (@ (type "hidden") (name ,query-key-query) (value ,query-value)))
- (input (@ (type "hidden") (name ,query-key-seed) (value ,seed)))
- (input (@ (class "zs-primary") (type "submit") (value "Save As Zettel")))
+ `((input ((type "hidden") (name ,query-key-query) (value ,query-value)))
+ (input ((type "hidden") (name ,query-key-seed) (value ,seed)))
+ (input ((class "zs-primary") (type "submit") (value "Save As Zettel")))
)
)
)
)
Index: internal/box/constbox/login.sxn
==================================================================
--- internal/box/constbox/login.sxn
+++ internal/box/constbox/login.sxn
@@ -11,17 +11,17 @@
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 "Login"))
- ,@(if retry '((div (@ (class "zs-indication zs-error")) "Wrong user name / password. Try again.")))
- (form (@ (method "POST") (action ""))
- (div
- (label (@ (for "username")) "User name:")
- (input (@ (class "zs-input") (type "text") (id "username") (name "username") (placeholder "Your user name..") (autofocus))))
- (div
- (label (@ (for "password")) "Password:")
- (input (@ (class "zs-input") (type "password") (id "password") (name "password") (placeholder "Your password.."))))
- (div
- (input (@ (class "zs-primary") (type "submit") (value "Login"))))
+ ,@(if retry '((div ((class "zs-indication zs-error")) "Wrong user name / password. Try again.")))
+ (form ((method "POST") (action ""))
+ (div
+ (label ((for "username")) "User name:")
+ (input ((class "zs-input") (type "text") (id "username") (name "username") (placeholder "Your user name..") (autofocus))))
+ (div
+ (label ((for "password")) "Password:")
+ (input ((class "zs-input") (type "password") (id "password") (name "password") (placeholder "Your password.."))))
+ (div
+ (input ((class "zs-primary") (type "submit") (value "Login"))))
)
)
Index: internal/box/constbox/wuicode.sxn
==================================================================
--- internal/box/constbox/wuicode.sxn
+++ internal/box/constbox/wuicode.sxn
@@ -17,17 +17,20 @@
(defun wui-item (s) `(li ,s))
;; wui-info-meta-table-row takes a pair and translates it into a HTML table row
;; with two columns.
(defun wui-info-meta-table-row (p)
- `(tr (td (@ (class zs-info-meta-key)) ,(car p)) (td (@ (class zs-info-meta-value)) ,(cdr p))))
+ `(tr (td ((class zs-info-meta-key)) ,(car p)) (td ((class zs-info-meta-value)) ,(cdr p))))
+
+;; wui-href builds an HTML link
+(defun wui-href (url text . attrs) `(a ((href ,url) ,@attrs) ,text))
;; wui-local-link translates a local link into HTML.
-(defun wui-local-link (l) `(li (a (@ (href ,l )) ,l)))
+(defun wui-local-link (l) `(li ,(wui-href l l)))
;; wui-link takes a link (title . url) and returns a HTML reference.
-(defun wui-link (q) `(a (@ (href ,(cdr q))) ,(car q)))
+(defun wui-link (q) (wui-href (cdr q) (car q)))
;; wui-item-link taks a pair (text . url) and returns a HTML link inside
;; a list item.
(defun wui-item-link (q) `(li ,(wui-link q)))
@@ -36,20 +39,20 @@
(defun wui-tdata-link (q) `(td ,(wui-link q)))
;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open
;; a new tab / window.
(defun wui-item-popup-link (e)
- `(li (a (@ (href ,e) (target "_blank") (rel "external noreferrer")) ,e)))
+ `(li ,(wui-href e e '(target "_blank") '(rel "external noreferrer"))))
;; wui-option-value returns a value for an HTML option element.
-(defun wui-option-value (v) `(option (@ (value ,v))))
+(defun wui-option-value (v) `(option ((value ,v))))
;; wui-datalist returns a HTML datalist with the given HTML identifier and a
;; list of values.
(defun wui-datalist (id lst)
(if lst
- `((datalist (@ (id ,id)) ,@(map wui-option-value lst)))))
+ `((datalist ((id ,id)) ,@(map wui-option-value lst)))))
;; wui-pair-desc-item takes a pair '(term . text) and returns a list with
;; a HTML description term and a HTML description data.
(defun wui-pair-desc-item (p) `((dt ,(car p)) (dd ,(cdr p))))
@@ -67,11 +70,11 @@
;; wui-optional-link puts the text into a link, if symbol is defined. Otherwise just return the text
(defun wui-optional-link (text url-sym)
(let ((url (resolve-symbol url-sym)))
(if (defined? url)
- `(a (@ (href ,(resolve-symbol url-sym))) ,text)
+ (wui-href url text)
text)))
;; CSS-ROLE-map is a mapping (pair list, assoc list) of role names to zettel
;; identifier. It is used in the base template to update the metadata of the
;; HTML page to include some role specific CSS code.
@@ -82,11 +85,11 @@
;; specific code should include the returned list of this function.
(defun ROLE-DEFAULT-meta (frame)
`(,@(let* ((meta-role (resolve-symbol 'meta-role frame))
(entry (assoc CSS-ROLE-map meta-role)))
(if (pair? entry)
- `((link (@ (rel "stylesheet") (href ,(zid-content-path (cdr entry))))))
+ `((link ((rel "stylesheet") (href ,(zid-content-path (cdr entry))))))
)
)
)
)
@@ -94,33 +97,33 @@
(defvar ACTION-SEPARATOR '(@H " · "))
;; ROLE-DEFAULT-actions returns the default text for actions.
(defun ROLE-DEFAULT-actions (frame)
`(,@(let ((copy-url (resolve-symbol 'copy-url frame)))
- (if (defined? copy-url) `((@H " · ") (a (@ (href ,copy-url)) "Copy"))))
+ (if (defined? copy-url) `((@H " · ") ,(wui-href copy-url "Copy"))))
,@(let ((sequel-url (resolve-symbol 'sequel-url frame)))
- (if (defined? sequel-url) `((@H " · ") (a (@ (href ,sequel-url)) "Sequel"))))
+ (if (defined? sequel-url) `((@H " · ") ,(wui-href sequel-url "Sequel"))))
,@(let ((folge-url (resolve-symbol 'folge-url frame)))
- (if (defined? folge-url) `((@H " · ") (a (@ (href ,folge-url)) "Folge"))))
+ (if (defined? folge-url) `((@H " · ") ,(wui-href folge-url "Folge"))))
)
)
;; ROLE-tag-actions returns an additional action "Zettel" for zettel with role "tag".
(defun ROLE-tag-actions (frame)
`(,@(let ((title (resolve-symbol 'title frame)))
(if (and (defined? title) title)
- `(,ACTION-SEPARATOR (a (@ (href ,(query->url (concat "tags:" title)))) "Zettel"))
+ `(,ACTION-SEPARATOR ,(wui-href (query->url (concat "tags:" title)) "Zettel"))
)
)
)
)
;; ROLE-role-actions returns an additional action "Zettel" for zettel with role "role".
(defun ROLE-role-actions (frame)
`(,@(let ((title (resolve-symbol 'title frame)))
(if (and (defined? title) title)
- `(,ACTION-SEPARATOR (a (@ (href ,(query->url (concat "role:" title)))) "Zettel"))
+ `(,ACTION-SEPARATOR ,(wui-href (query->url (concat "role:" title)) "Zettel"))
)
)
)
)
Index: internal/box/constbox/zettel.sxn
==================================================================
--- internal/box/constbox/zettel.sxn
+++ internal/box/constbox/zettel.sxn
@@ -12,21 +12,21 @@
;;;----------------------------------------------------------------------------
`(article
(header
(h1 ,heading)
- (div (@ (class "zs-meta"))
- ,@(if (symbol-bound? 'edit-url) `((a (@ (href ,edit-url)) "Edit") (@H " · ")))
+ (div ((class "zs-meta"))
+ ,@(if (symbol-bound? 'edit-url) `(,(wui-href edit-url "Edit") (@H " · ")))
,zid (@H " · ")
- (a (@ (href ,info-url)) "Info") (@H " · ")
- "(" ,@(if (symbol-bound? 'role-url) `((a (@ (href ,role-url)) ,meta-role)))
+ ,(wui-href info-url "Info") (@H " · ")
+ "(" ,@(if (symbol-bound? 'role-url) `(,(wui-href role-url meta-role)))
,@(if (and (symbol-bound? 'folge-role-url) (symbol-bound? 'meta-folge-role))
- `((@H " → ") (a (@ (href ,folge-role-url)) ,meta-folge-role)))
+ `((@H " → ") ,(wui-href folge-role-url meta-folge-role)))
")"
,@(if tag-refs `((@H " · ") ,@tag-refs))
- (@H " · ") (a (@ (href ,context-url)) "Context")
- ,@(if (symbol-bound? 'thread-query-url) `((@H " · ") (a (@ (href ,thread-query-url)) "Thread")))
+ (@H " · ") ,(wui-href context-url "Context")
+ ,@(if (symbol-bound? 'thread-query-url) `((@H " · ") ,(wui-href thread-query-url "Thread")))
,@(ROLE-DEFAULT-actions (current-frame))
,@(let* ((frame (current-frame))(rea (resolve-symbol 'ROLE-EXTRA-actions frame))) (if (defined? rea) (rea frame)))
,@(if superior-refs `((br) "Superior: " ,superior-refs))
,@(if prequel-refs `((br) ,(wui-optional-link "Prequel" 'sequel-query-url) ": " ,prequel-refs))
,@(if precursor-refs `((br) ,(wui-optional-link "Precursor" 'folge-query-url) ": " ,precursor-refs))
@@ -37,17 +37,17 @@
,@content
,endnotes
,@(if (or folge-links sequel-links back-links subordinate-links)
`((nav
,@(if folge-links
- `((details (@ (,folge-open))
+ `((details ((,folge-open))
(summary ,(wui-optional-link "Folgezettel" 'folge-query-url))
(ul ,@(map wui-item-link folge-links)))))
,@(if sequel-links
- `((details (@ (,sequel-open))
+ `((details ((,sequel-open))
(summary ,(wui-optional-link "Sequel" 'sequel-query-url))
(ul ,@(map wui-item-link sequel-links)))))
- ,@(if subordinate-links `((details (@ (,subordinate-open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links)))))
- ,@(if back-links `((details (@ (,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links)))))
+ ,@(if subordinate-links `((details ((,subordinate-open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links)))))
+ ,@(if back-links `((details ((,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links)))))
))
)
)
Index: internal/box/manager/collect.go
==================================================================
--- internal/box/manager/collect.go
+++ internal/box/manager/collect.go
@@ -14,15 +14,17 @@
package manager
import (
"strings"
+ "t73f.de/r/sx"
zerostrings "t73f.de/r/zero/strings"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/id/idset"
+ "t73f.de/r/zsc/sz"
+ "t73f.de/r/zsx"
- "zettelstore.de/z/internal/ast"
"zettelstore.de/z/internal/box/manager/store"
)
type collectData struct {
refs *idset.Set
@@ -34,49 +36,56 @@
data.refs = idset.New()
data.words = store.NewWordSet()
data.urls = store.NewWordSet()
}
-func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) {
- ast.Walk(data, &zn.BlocksAST)
-}
-
-func (data *collectData) Visit(node ast.Node) ast.Visitor {
- switch n := node.(type) {
- case *ast.VerbatimNode:
- data.addText(string(n.Content))
- case *ast.TranscludeNode:
- data.addRef(n.Ref)
- case *ast.TextNode:
- data.addText(n.Text)
- case *ast.LinkNode:
- data.addRef(n.Ref)
- case *ast.EmbedRefNode:
- data.addRef(n.Ref)
- case *ast.CiteNode:
- data.addText(n.Key)
- case *ast.LiteralNode:
- data.addText(string(n.Content))
- }
- return data
+func collectZettelIndexData(blocks *sx.Pair, data *collectData) {
+ zsx.WalkIt(data, blocks, nil)
+}
+
+func (data *collectData) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymText:
+ data.addText(zsx.GetText(node))
+ case zsx.SymVerbatimCode, zsx.SymVerbatimComment, zsx.SymVerbatimEval,
+ zsx.SymVerbatimHTML, zsx.SymVerbatimMath, zsx.SymVerbatimZettel:
+ _, _, s := zsx.GetVerbatim(node)
+ data.addText(s)
+ case zsx.SymLiteralCode, zsx.SymLiteralComment, zsx.SymLiteralInput,
+ zsx.SymLiteralMath, zsx.SymLiteralOutput:
+ _, _, s := zsx.GetLiteral(node)
+ data.addText(s)
+ case zsx.SymLink:
+ _, ref, _ := zsx.GetLink(node)
+ data.addRef(ref)
+ case zsx.SymEmbed:
+ _, ref, _, _ := zsx.GetEmbed(node)
+ data.addRef(ref)
+ case zsx.SymTransclude:
+ _, ref, _ := zsx.GetTransclusion(node)
+ data.addRef(ref)
+ case zsx.SymCite:
+ _, key, _ := zsx.GetCite(node)
+ data.addText(key)
+ }
+ }
+ return false
+}
+func (data *collectData) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+func (data *collectData) addRef(ref *sx.Pair) {
+ sym, refValue := zsx.GetReference(ref)
+ if zsx.SymRefStateExternal.IsEqual(sym) {
+ data.urls.Add(strings.ToLower(refValue))
+ } else if sz.SymRefStateZettel.IsEqual(sym) {
+ if zid, err := id.Parse(refValue); err == nil {
+ data.refs.Add(zid)
+ }
+ }
}
func (data *collectData) addText(s string) {
for _, word := range zerostrings.NormalizeWords(s) {
data.words.Add(word)
}
}
-
-func (data *collectData) addRef(ref *ast.Reference) {
- if ref == nil {
- return
- }
- if ref.IsExternal() {
- data.urls.Add(strings.ToLower(ref.Value))
- }
- if !ref.IsZettel() {
- return
- }
- if zid, err := id.Parse(ref.URL.Path); err == nil {
- data.refs.Add(zid)
- }
-}
Index: internal/box/manager/indexer.go
==================================================================
--- internal/box/manager/indexer.go
+++ internal/box/manager/indexer.go
@@ -150,11 +150,12 @@
func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) {
var cData collectData
cData.initialize()
if mustIndexZettel(zettel.Meta) {
- collectZettelIndexData(parser.ParseZettel(ctx, zettel, "", mgr.rtConfig), &cData)
+ zn := parser.ParseZettel(ctx, zettel, "", mgr.rtConfig)
+ collectZettelIndexData(zn.Blocks, &cData)
}
m := zettel.Meta
zi := store.NewZettelIndex(m)
mgr.idxCollectFromMeta(ctx, m, zi, &cData)
Index: internal/collect/collect.go
==================================================================
--- internal/collect/collect.go
+++ internal/collect/collect.go
@@ -15,42 +15,49 @@
package collect
import (
"iter"
- "zettelstore.de/z/internal/ast"
+ "t73f.de/r/sx"
+ "t73f.de/r/zsx"
)
type refYielder struct {
- yield func(*ast.Reference) bool
+ yield func(*sx.Pair) bool
stop bool
}
// ReferenceSeq returns an iterator of all references mentioned in the given
-// zettel. This also includes references to images.
-func ReferenceSeq(zn *ast.ZettelNode) iter.Seq[*ast.Reference] {
- return func(yield func(*ast.Reference) bool) {
+// block slice. This also includes references to images.
+func ReferenceSeq(block *sx.Pair) iter.Seq[*sx.Pair] {
+ return func(yield func(*sx.Pair) bool) {
yielder := refYielder{yield, false}
- ast.Walk(&yielder, &zn.BlocksAST)
+ zsx.WalkIt(&yielder, block, nil)
}
}
// Visit all node to collect data for the summary.
-func (y *refYielder) Visit(node ast.Node) ast.Visitor {
+func (y *refYielder) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if y.stop {
- return nil
- }
- var stop bool
- switch n := node.(type) {
- case *ast.TranscludeNode:
- stop = !y.yield(n.Ref)
- case *ast.LinkNode:
- stop = !y.yield(n.Ref)
- case *ast.EmbedRefNode:
- stop = !y.yield(n.Ref)
- }
- if stop {
- y.stop = true
- return nil
- }
- return y
-}
+ return true
+ }
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymLink:
+ _, ref, _ := zsx.GetLink(node)
+ y.stop = !y.yield(ref)
+
+ case zsx.SymEmbed:
+ _, ref, _, _ := zsx.GetEmbed(node)
+ y.stop = !y.yield(ref)
+
+ case zsx.SymTransclude:
+ _, ref, _ := zsx.GetTransclusion(node)
+ y.stop = !y.yield(ref)
+ }
+ if y.stop {
+ return true
+ }
+ }
+ return false
+}
+func (*refYielder) VisitItAfter(*sx.Pair, *sx.Pair) {}
Index: internal/collect/collect_test.go
==================================================================
--- internal/collect/collect_test.go
+++ internal/collect/collect_test.go
@@ -16,47 +16,48 @@
import (
"slices"
"testing"
- "zettelstore.de/z/internal/ast"
+ "t73f.de/r/sx"
+ "t73f.de/r/zsc/sz"
+ "t73f.de/r/zsx"
+
"zettelstore.de/z/internal/collect"
)
-func parseRef(s string) *ast.Reference {
- r := ast.ParseReference(s)
- if !r.IsValid() {
+func parseRef(s string) *sx.Pair {
+ r := sz.ScanReference(s)
+ sym, _ := zsx.GetReference(r)
+ if zsx.SymRefStateInvalid.IsEqualSymbol(sym) {
panic(s)
}
return r
}
func TestReferenceSeq(t *testing.T) {
t.Parallel()
- zn := &ast.ZettelNode{}
- summary := slices.Collect(collect.ReferenceSeq(zn))
+ summary := slices.Collect(collect.ReferenceSeq(nil))
if len(summary) != 0 {
t.Error("No references expected, but got:", summary)
}
- intNode := &ast.LinkNode{Ref: parseRef("01234567890123")}
- para := ast.CreateParaNode(intNode, &ast.LinkNode{Ref: parseRef("https://zettelstore.de/z")})
- zn.BlocksAST = ast.BlockSlice{para}
- summary = slices.Collect(collect.ReferenceSeq(zn))
+ intNode := zsx.MakeLink(nil, parseRef("01234567890123"), nil)
+ para := zsx.MakePara(intNode, zsx.MakeLink(nil, parseRef("https://zettelstore.de/z"), nil))
+ blocks := zsx.MakeBlock(para)
+ summary = slices.Collect(collect.ReferenceSeq(blocks))
if len(summary) != 2 {
t.Error("2 refs expected, but got:", summary)
}
- para.Inlines = append(para.Inlines, intNode)
- summary = slices.Collect(collect.ReferenceSeq(zn))
+ para.LastPair().AppendBang(intNode)
+ summary = slices.Collect(collect.ReferenceSeq(blocks))
if cnt := len(summary); cnt != 3 {
t.Error("Ref count does not work. Expected: 3, got", summary)
}
- zn = &ast.ZettelNode{
- BlocksAST: ast.BlockSlice{ast.CreateParaNode(&ast.EmbedRefNode{Ref: parseRef("12345678901234")})},
- }
- summary = slices.Collect(collect.ReferenceSeq(zn))
+ blocks = zsx.MakeBlock(zsx.MakePara(zsx.MakeEmbed(nil, parseRef("12345678901234"), "", nil)))
+ summary = slices.Collect(collect.ReferenceSeq(blocks))
if len(summary) != 1 {
t.Error("Only one image ref expected, but got: ", summary)
}
}
Index: internal/collect/order.go
==================================================================
--- internal/collect/order.go
+++ internal/collect/order.go
@@ -12,62 +12,73 @@
//-----------------------------------------------------------------------------
// Package collect provides functions to collect items from a syntax tree.
package collect
-import "zettelstore.de/z/internal/ast"
-
-// Order of internal links within the given zettel.
-func Order(zn *ast.ZettelNode) (result []*ast.LinkNode) {
- for _, bn := range zn.BlocksAST {
- ln, ok := bn.(*ast.NestedListNode)
- if !ok {
- continue
- }
- switch ln.Kind {
- case ast.NestedListOrdered, ast.NestedListUnordered:
- for _, is := range ln.Items {
- if ln := firstItemZettelLink(is); ln != nil {
- result = append(result, ln)
+import (
+ "t73f.de/r/sx"
+ "t73f.de/r/zsx"
+)
+
+// Order returns links in the items of the first list found in the given block node.
+func Order(block *sx.Pair) *sx.Pair {
+ var lb sx.ListBuilder
+ blocks := zsx.GetBlock(block)
+ for bn := range blocks.Pairs() {
+ blk := bn.Head()
+ if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol {
+ if zsx.SymListUnordered.IsEqualSymbol(sym) || zsx.SymListOrdered.IsEqualSymbol(sym) {
+ _, _, items := zsx.GetList(blk)
+ for item := range items.Pairs() {
+ if ln := firstItemZettelLink(item.Head()); ln != nil {
+ lb.Add(ln)
+ }
}
}
}
}
- return result
+ return lb.List()
}
-func firstItemZettelLink(is ast.ItemSlice) *ast.LinkNode {
- for _, in := range is {
- if pn, ok := in.(*ast.ParaNode); ok {
- if ln := firstInlineZettelLink(pn.Inlines); ln != nil {
+func firstItemZettelLink(item *sx.Pair) *sx.Pair {
+ blocks := zsx.GetBlock(item)
+ for bn := range blocks.Pairs() {
+ blk := bn.Head()
+ if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol && zsx.SymPara.IsEqualSymbol(sym) {
+ inlines := zsx.GetPara(blk)
+ if ln := firstInlineZettelLink(inlines); ln != nil {
return ln
}
}
}
return nil
}
-func firstInlineZettelLink(is ast.InlineSlice) (result *ast.LinkNode) {
- for _, inl := range is {
- switch in := inl.(type) {
- case *ast.LinkNode:
- return in
- case *ast.EmbedRefNode:
- result = firstInlineZettelLink(in.Inlines)
- case *ast.EmbedBLOBNode:
- result = firstInlineZettelLink(in.Inlines)
- case *ast.CiteNode:
- result = firstInlineZettelLink(in.Inlines)
- case *ast.FootnoteNode:
- // Ignore references in footnotes
- continue
- case *ast.FormatNode:
- result = firstInlineZettelLink(in.Inlines)
- default:
- continue
- }
- if result != nil {
- return result
+func firstInlineZettelLink(inlines *sx.Pair) (result *sx.Pair) {
+ for inode := range inlines.Pairs() {
+ inl := inode.Head()
+ if sym, isSymbol := sx.GetSymbol(inl.Car()); isSymbol {
+ switch sym {
+ case zsx.SymLink:
+ return inl
+ case zsx.SymFormatDelete, zsx.SymFormatEmph, zsx.SymFormatInsert, zsx.SymFormatMark,
+ zsx.SymFormatQuote, zsx.SymFormatSpan, zsx.SymFormatStrong, zsx.SymFormatSub,
+ zsx.SymFormatSuper:
+ _, _, finlines := zsx.GetFormat(inl)
+ result = firstInlineZettelLink(finlines)
+ case zsx.SymCite:
+ _, _, cinlines := zsx.GetCite(inl)
+ result = firstInlineZettelLink(cinlines)
+ case zsx.SymEmbed:
+ _, _, _, einlines := zsx.GetEmbed(inl)
+ result = firstInlineZettelLink(einlines)
+ case zsx.SymEmbedBLOB:
+ _, _, _, binlines := zsx.GetEmbedBLOBuncode(inl)
+ result = firstInlineZettelLink(binlines)
+ }
+ if result != nil {
+ return result
+ }
}
}
return nil
}
Index: internal/encoder/encoder.go
==================================================================
--- internal/encoder/encoder.go
+++ internal/encoder/encoder.go
@@ -16,10 +16,11 @@
package encoder
import (
"io"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"zettelstore.de/z/internal/ast"
@@ -26,44 +27,42 @@
)
// Encoder is an interface that allows to encode different parts of a zettel.
type Encoder interface {
// WriteZettel encodes a whole zettel and writes it to the Writer.
- WriteZettel(io.Writer, *ast.ZettelNode) (int, error)
+ WriteZettel(io.Writer, *ast.Zettel) error
// WriteMeta encodes just the metadata.
- WriteMeta(io.Writer, *meta.Meta) (int, error)
+ WriteMeta(io.Writer, *meta.Meta) error
- // WiteBlocks encodes a block slice, i.e. the zettel content.
- WriteBlocks(io.Writer, *ast.BlockSlice) (int, error)
+ // WriteSz encodes SZ represented zettel content.
+ WriteSz(io.Writer, *sx.Pair) error
}
// Create builds a new encoder with the given options.
func Create(enc api.EncodingEnum, params *CreateParameter) Encoder {
switch enc {
case api.EncoderHTML:
// We need a new transformer every time, because tx.inVerse must be unique.
// If we can refactor it out, the transformer can be created only once.
return &htmlEncoder{
- tx: NewSzTransformer(),
th: shtml.NewEvaluator(1),
lang: params.Lang,
}
case api.EncoderMD:
return &mdEncoder{lang: params.Lang}
case api.EncoderSHTML:
// We need a new transformer every time, because tx.inVerse must be unique.
// If we can refactor it out, the transformer can be created only once.
return &shtmlEncoder{
- tx: NewSzTransformer(),
th: shtml.NewEvaluator(1),
lang: params.Lang,
}
case api.EncoderSz:
// We need a new transformer every time, because trans.inVerse must be unique.
// If we can refactor it out, the transformer can be created only once.
- return &szEncoder{trans: NewSzTransformer()}
+ return &szEncoder{}
case api.EncoderText:
return (*TextEncoder)(nil)
case api.EncoderZmk:
return (*zmkEncoder)(nil)
}
Index: internal/encoder/encoder_blob_test.go
==================================================================
--- internal/encoder/encoder_blob_test.go
+++ internal/encoder/encoder_blob_test.go
@@ -18,11 +18,10 @@
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
- "zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/parser"
)
type blobTestCase struct {
descr string
@@ -42,12 +41,12 @@
0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82,
},
expect: expectMap{
encoderHTML: `

`,
- encoderSz: `(BLOCK (BLOB () ((TEXT "Minimal PNG")) "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="))`,
- encoderSHTML: `((p (img (@ (alt . "Minimal PNG") (src . "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==")))))`,
+ encoderSz: `(BLOCK (BLOB () "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==" (TEXT "Minimal PNG")))`,
+ encoderSHTML: `((p (img ((alt . "Minimal PNG") (src . "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==")))))`,
encoderText: "",
encoderZmk: `%% Unable to display BLOB with description 'Minimal PNG' and syntax 'png'.`,
},
},
}
@@ -55,9 +54,9 @@
func TestBlob(t *testing.T) {
m := meta.New(id.Invalid)
for testNum, tc := range pngTestCases {
m.Set(meta.KeyTitle, meta.Value(tc.descr))
inp := input.NewInput(tc.blob)
- bs := parser.Parse(inp, m, tc.syntax, config.NoHTML)
- checkEncodings(t, testNum, bs, false, tc.descr, tc.expect, "???")
+ node := parser.Parse(inp, m, tc.syntax, nil)
+ checkEncodings(t, testNum, node, false, tc.descr, tc.expect, "???")
}
}
Index: internal/encoder/encoder_block_test.go
==================================================================
--- internal/encoder/encoder_block_test.go
+++ internal/encoder/encoder_block_test.go
@@ -10,10 +10,16 @@
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
package encoder_test
+
+import "t73f.de/r/zsc/domain/meta"
+
+// func TestEncoderBlock(t *testing.T) {
+// executeTestCases(t, tcsBlock)
+// }
var tcsBlock = []zmkTestCase{
{
descr: "Empty Zettelmarkup should produce near nothing",
zmk: "",
@@ -67,11 +73,11 @@
zmk: `=== Top Job`,
expect: expectMap{
encoderHTML: "Top Job
",
encoderMD: "# Top Job",
encoderSz: `(BLOCK (HEADING 1 () "top-job" "top-job" (TEXT "Top Job")))`,
- encoderSHTML: `((h2 (@ (id . "top-job")) "Top Job"))`,
+ encoderSHTML: `((h2 ((id . "top-job")) "Top Job"))`,
encoderText: `Top Job`,
encoderZmk: useZmk,
},
},
{
@@ -78,11 +84,11 @@
descr: "Simple List",
zmk: "* A\n* B\n* C",
expect: expectMap{
encoderHTML: "",
encoderMD: "* A\n* B\n* C",
- encoderSz: `(BLOCK (UNORDERED () (INLINE (TEXT "A")) (INLINE (TEXT "B")) (INLINE (TEXT "C"))))`,
+ encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "A"))) (BLOCK (PARA (TEXT "B"))) (BLOCK (PARA (TEXT "C")))))`,
encoderSHTML: `((ul (li "A") (li "B") (li "C")))`,
encoderText: "A\nB\nC",
encoderZmk: useZmk,
},
},
@@ -90,11 +96,11 @@
descr: "Nested List",
zmk: "* T1\n*# T2\n* T3\n** T4\n** T5\n* T6",
expect: expectMap{
encoderHTML: ``,
encoderMD: "* T1\n 1. T2\n* T3\n * T4\n * T5\n* T6",
- encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "T1")) (ORDERED () (INLINE (TEXT "T2")))) (BLOCK (PARA (TEXT "T3")) (UNORDERED () (INLINE (TEXT "T4")) (INLINE (TEXT "T5")))) (BLOCK (PARA (TEXT "T6")))))`,
+ encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "T1")) (ORDERED () (BLOCK (PARA (TEXT "T2"))))) (BLOCK (PARA (TEXT "T3")) (UNORDERED () (BLOCK (PARA (TEXT "T4"))) (BLOCK (PARA (TEXT "T5"))))) (BLOCK (PARA (TEXT "T6")))))`,
encoderSHTML: `((ul (li (p "T1") (ol (li "T2"))) (li (p "T3") (ul (li "T4") (li "T5"))) (li (p "T6"))))`,
encoderText: "T1\nT2\nT3\nT4\nT5\nT6",
encoderZmk: useZmk,
},
},
@@ -102,11 +108,11 @@
descr: "Sequence of two lists",
zmk: "* Item1.1\n* Item1.2\n* Item1.3\n\n* Item2.1\n* Item2.2",
expect: expectMap{
encoderHTML: "- Item1.1
- Item1.2
- Item1.3
- Item2.1
- Item2.2
",
encoderMD: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2",
- encoderSz: `(BLOCK (UNORDERED () (INLINE (TEXT "Item1.1")) (INLINE (TEXT "Item1.2")) (INLINE (TEXT "Item1.3")) (INLINE (TEXT "Item2.1")) (INLINE (TEXT "Item2.2"))))`,
+ encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "Item1.1"))) (BLOCK (PARA (TEXT "Item1.2"))) (BLOCK (PARA (TEXT "Item1.3"))) (BLOCK (PARA (TEXT "Item2.1"))) (BLOCK (PARA (TEXT "Item2.2")))))`,
encoderSHTML: `((ul (li "Item1.1") (li "Item1.2") (li "Item1.3") (li "Item2.1") (li "Item2.2")))`,
encoderText: "Item1.1\nItem1.2\nItem1.3\nItem2.1\nItem2.2",
encoderZmk: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2",
},
},
@@ -127,11 +133,11 @@
zmk: `---{lang="zmk"}`,
expect: expectMap{
encoderHTML: `
`,
encoderMD: "---",
encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`,
- encoderSHTML: `((hr (@ (lang . "zmk"))))`,
+ encoderSHTML: `((hr ((lang . "zmk"))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -150,11 +156,11 @@
descr: "A list after paragraph",
zmk: "Text\n# abc",
expect: expectMap{
encoderHTML: "Text
- abc
",
encoderMD: "Text\n\n1. abc",
- encoderSz: `(BLOCK (PARA (TEXT "Text")) (ORDERED () (INLINE (TEXT "abc"))))`,
+ encoderSz: `(BLOCK (PARA (TEXT "Text")) (ORDERED () (BLOCK (PARA (TEXT "abc")))))`,
encoderSHTML: `((p "Text") (ol (li "abc")))`,
encoderText: "Text\nabc",
encoderZmk: useZmk,
},
},
@@ -162,11 +168,11 @@
descr: "Simple List Quote",
zmk: "> ToBeOrNotToBe",
expect: expectMap{
encoderHTML: "ToBeOrNotToBe
",
encoderMD: "> ToBeOrNotToBe",
- encoderSz: `(BLOCK (QUOTATION () (INLINE (TEXT "ToBeOrNotToBe"))))`,
+ encoderSz: `(BLOCK (QUOTATION () (BLOCK (PARA (TEXT "ToBeOrNotToBe")))))`,
encoderSHTML: `((blockquote (@L "ToBeOrNotToBe")))`,
encoderText: "ToBeOrNotToBe",
encoderZmk: useZmk,
},
},
@@ -259,11 +265,11 @@
zmk: "~~~\nHello\nWorld\n~~~",
expect: expectMap{
encoderHTML: "Hello\nWorld
",
encoderMD: "",
encoderSz: `(BLOCK (VERBATIM-EVAL () "Hello\nWorld"))`,
- encoderSHTML: "((pre (code (@ (class . \"zs-eval\")) \"Hello\\nWorld\")))",
+ encoderSHTML: "((pre (code ((class . \"zs-eval\")) \"Hello\\nWorld\")))",
encoderText: "Hello\nWorld",
encoderZmk: useZmk,
},
},
{
@@ -271,11 +277,11 @@
zmk: "$$$\nHello\n\\LaTeX\n$$$",
expect: expectMap{
encoderHTML: "Hello\n\\LaTeX
",
encoderMD: "",
encoderSz: `(BLOCK (VERBATIM-MATH () "Hello\n\\LaTeX"))`,
- encoderSHTML: "((pre (code (@ (class . \"zs-math\")) \"Hello\\n\\\\LaTeX\")))",
+ encoderSHTML: "((pre (code ((class . \"zs-math\")) \"Hello\\n\\\\LaTeX\")))",
encoderText: "Hello\n\\LaTeX",
encoderZmk: useZmk,
},
},
{
@@ -318,11 +324,11 @@
descr: "Simple Table",
zmk: "|c1|c2|c3\n|d1||d3",
expect: expectMap{
encoderHTML: ``,
encoderMD: "",
- encoderSz: `(BLOCK (TABLE () ((CELL () (TEXT "c1")) (CELL () (TEXT "c2")) (CELL () (TEXT "c3"))) ((CELL () (TEXT "d1")) (CELL ()) (CELL () (TEXT "d3")))))`,
+ encoderSz: `(BLOCK (TABLE () () ((CELL () (TEXT "c1")) (CELL () (TEXT "c2")) (CELL () (TEXT "c3"))) ((CELL () (TEXT "d1")) (CELL ()) (CELL () (TEXT "d3")))))`,
encoderSHTML: `((table (tbody (tr (td "c1") (td "c2") (td "c3")) (tr (td "d1") (td) (td "d3")))))`,
encoderText: "c1 c2 c3\nd1 d3",
encoderZmk: useZmk,
},
},
@@ -333,12 +339,12 @@
|| h1 | h2 | h3 |
|---|
| c1 | c2 | c3 |
| f1 | f2 | =f3 |
`,
encoderMD: "",
- encoderSz: `(BLOCK (TABLE ((CELL ((align . "right")) (TEXT "h1")) (CELL () (TEXT "h2")) (CELL ((align . "center")) (TEXT "h3"))) ((CELL ((align . "left")) (TEXT "c1")) (CELL () (TEXT "c2")) (CELL ((align . "center")) (TEXT "c3"))) ((CELL ((align . "right")) (TEXT "f1")) (CELL () (TEXT "f2")) (CELL ((align . "center")) (TEXT "=f3")))))`,
- encoderSHTML: `((table (thead (tr (th (@ (class . "right")) "h1") (th "h2") (th (@ (class . "center")) "h3"))) (tbody (tr (td (@ (class . "left")) "c1") (td "c2") (td (@ (class . "center")) "c3")) (tr (td (@ (class . "right")) "f1") (td "f2") (td (@ (class . "center")) "=f3")))))`,
+ encoderSz: `(BLOCK (TABLE () ((CELL ((align . "right")) (TEXT "h1")) (CELL () (TEXT "h2")) (CELL ((align . "center")) (TEXT "h3"))) ((CELL ((align . "left")) (TEXT "c1")) (CELL () (TEXT "c2")) (CELL ((align . "center")) (TEXT "c3"))) ((CELL ((align . "right")) (TEXT "f1")) (CELL () (TEXT "f2")) (CELL ((align . "center")) (TEXT "=f3")))))`,
+ encoderSHTML: `((table (thead (tr (th ((class . "right")) "h1") (th "h2") (th ((class . "center")) "h3"))) (tbody (tr (td ((class . "left")) "c1") (td "c2") (td ((class . "center")) "c3")) (tr (td ((class . "right")) "f1") (td "f2") (td ((class . "center")) "=f3")))))`,
encoderText: "h1 h2 h3\nc1 c2 c3\nf1 f2 =f3",
encoderZmk: /*`|=h1>|=h2|=h3:
|h1|=h2|=:h3
@@ -351,11 +357,11 @@
zmk: `Text[^Endnote]`,
expect: expectMap{
encoderHTML: "Text1
- Endnote \u21a9\ufe0e
",
encoderMD: "Text",
encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (TEXT "Endnote"))))`,
- encoderSHTML: "((p \"Text\" (sup (@ (id . \"fnref:1\")) (a (@ (class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))",
+ encoderSHTML: "((p \"Text\" (sup ((id . \"fnref:1\")) (a ((class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))",
encoderText: "Text Endnote",
encoderZmk: useZmk,
},
},
{
@@ -363,11 +369,11 @@
zmk: `Text[^Endnote[^Nested]]`,
expect: expectMap{
encoderHTML: "Text1
- Endnote2 \u21a9\ufe0e
- Nested \u21a9\ufe0e
",
encoderMD: "Text",
encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (TEXT "Endnote") (ENDNOTE () (TEXT "Nested")))))`,
- encoderSHTML: "((p \"Text\" (sup (@ (id . \"fnref:1\")) (a (@ (class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))",
+ encoderSHTML: "((p \"Text\" (sup ((id . \"fnref:1\")) (a ((class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))",
encoderText: "Text Endnote Nested",
encoderZmk: useZmk,
},
},
{
@@ -375,11 +381,11 @@
zmk: `{{{http://example.com/image}}}{width="100px"}`,
expect: expectMap{
encoderHTML: `
`,
encoderMD: "",
encoderSz: `(BLOCK (TRANSCLUDE (("width" . "100px")) (EXTERNAL "http://example.com/image")))`,
- encoderSHTML: `((p (img (@ (class . "external") (src . "http://example.com/image") (width . "100px")))))`,
+ encoderSHTML: `((p (img ((class . "external") (src . "http://example.com/image") (width . "100px")))))`,
encoderText: "",
encoderZmk: useZmk,
},
},
{
@@ -391,10 +397,35 @@
// encoderSHTML: ``,
encoderText: "",
encoderZmk: useZmk,
},
},
+ {
+ descr: "Zettel with disallowed syntax HTML",
+ zmk: "Hello
\nWorld\n",
+ syntax: meta.ValueSyntaxHTML,
+ expect: expectMap{
+ encoderHTML: "<h1>Hello</h1>\nWorld
",
+ encoderSz: `(BLOCK (VERBATIM-CODE (("" . "html")) "Hello
\nWorld"))`,
+ encoderSHTML: `((pre (code ((class . "language-html")) "Hello
\nWorld")))`,
+ encoderText: "Hello
\nWorld",
+ encoderZmk: "```{=\"html\"}\nHello
\nWorld\n```",
+ },
+ },
+ {
+ descr: "Zettel with allowed syntax HTML",
+ zmk: "Hello
\nWorld\n",
+ syntax: meta.ValueSyntaxHTML,
+ allowHTML: true,
+ expect: expectMap{
+ encoderHTML: "Hello
\nWorld",
+ encoderSz: `(BLOCK (VERBATIM-HTML (("" . "html")) "Hello
\nWorld"))`,
+ encoderSHTML: `((@H "Hello
\nWorld"))`,
+ encoderText: "Hello
\nWorld",
+ encoderZmk: "@@@{=\"html\"}\nHello
\nWorld\n@@@",
+ },
+ },
{
descr: "",
zmk: ``,
expect: expectMap{
encoderHTML: ``,
@@ -403,9 +434,5 @@
encoderText: "",
encoderZmk: useZmk,
},
},
}
-
-// func TestEncoderBlock(t *testing.T) {
-// executeTestCases(t, tcsBlock)
-// }
Index: internal/encoder/encoder_inline_test.go
==================================================================
--- internal/encoder/encoder_inline_test.go
+++ internal/encoder/encoder_inline_test.go
@@ -163,11 +163,11 @@
zmk: `""quotes""{lang=de}`,
expect: expectMap{
encoderHTML: `„quotes“
`,
encoderMD: "„quotes“",
encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes"))))`,
- encoderSHTML: `((p (span (@ (lang . "de")) (@H "„") "quotes" (@H "“"))))`,
+ encoderSHTML: `((p (span ((lang . "de")) (@H "„") "quotes" (@H "“"))))`,
encoderText: `quotes`,
encoderZmk: `""quotes""{lang="de"}`,
},
},
{
@@ -187,11 +187,11 @@
zmk: `""""{lang=unknown}`,
expect: expectMap{
encoderHTML: `""
`,
encoderMD: """",
encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "unknown")))))`,
- encoderSHTML: `((p (span (@ (lang . "unknown")) (@H """ """))))`,
+ encoderSHTML: `((p (span ((lang . "unknown")) (@H """ """))))`,
encoderText: ``,
encoderZmk: `""""{lang="unknown"}`,
},
},
{
@@ -307,11 +307,11 @@
zmk: `$$\TeX$$`,
expect: expectMap{
encoderHTML: `\TeX
`,
encoderMD: "\\TeX",
encoderSz: `(BLOCK (PARA (LITERAL-MATH () "\\TeX")))`,
- encoderSHTML: `((p (code (@ (class . "zs-math")) "\\TeX")))`,
+ encoderSHTML: `((p (code ((class . "zs-math")) "\\TeX")))`,
encoderText: `\TeX`,
encoderZmk: useZmk,
},
},
{
@@ -319,15 +319,27 @@
zmk: `::""abc""::{lang=fr}`,
expect: expectMap{
encoderHTML: `« abc »
`,
encoderMD: "« abc »",
encoderSz: `(BLOCK (PARA (FORMAT-SPAN (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc")))))`,
- encoderSHTML: `((p (span (@ (lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»")))))`,
+ encoderSHTML: `((p (span ((lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»")))))`,
encoderText: `abc`,
encoderZmk: `::""abc""::{lang="fr"}`,
},
},
+ {
+ descr: "Nested Insert Quote formatting",
+ zmk: `>>""abc"">>{lang=fr}`,
+ expect: expectMap{
+ encoderHTML: `« abc »
`,
+ encoderMD: "« abc »",
+ encoderSz: `(BLOCK (PARA (FORMAT-INSERT (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc")))))`,
+ encoderSHTML: `((p (ins ((lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»")))))`,
+ encoderText: `abc`,
+ encoderZmk: `>>""abc"">>{lang="fr"}`,
+ },
+ },
{
descr: "Simple Citation",
zmk: `[@Stern18]`,
expect: expectMap{
encoderHTML: `Stern18
`, // TODO
@@ -399,11 +411,11 @@
},
{
descr: "Comment after text and with -->",
zmk: `Text%%{-} comment --> end`,
expect: expectMap{
- encoderHTML: `Text
`,
+ encoderHTML: `Text
`,
encoderMD: "Text",
encoderSz: `(BLOCK (PARA (TEXT "Text") (LITERAL-COMMENT (("-" . "")) "comment --> end")))`,
encoderSHTML: `((p "Text" (@@ "comment --> end")))`,
encoderText: `Text`,
encoderZmk: useZmk,
@@ -414,11 +426,11 @@
zmk: `[^endnote]`,
expect: expectMap{
encoderHTML: `1
- endnote ↩︎
`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (ENDNOTE () (TEXT "endnote"))))`,
- encoderSHTML: `((p (sup (@ (id . "fnref:1")) (a (@ (class . "zs-noteref") (href . "#fn:1") (role . "doc-noteref")) "1"))))`,
+ encoderSHTML: `((p (sup ((id . "fnref:1")) (a ((class . "zs-noteref") (href . "#fn:1") (role . "doc-noteref")) "1"))))`,
encoderText: `endnote`,
encoderZmk: useZmk,
},
},
{
@@ -426,11 +438,11 @@
zmk: `[!mark]`,
expect: expectMap{
encoderHTML: `
`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (MARK "mark" "mark" "mark")))`,
- encoderSHTML: `((p (a (@ (id . "mark")))))`,
+ encoderSHTML: `((p (a ((id . "mark")))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -438,11 +450,11 @@
zmk: `[!mark|with text]`,
expect: expectMap{
encoderHTML: `with text
`,
encoderMD: "with text",
encoderSz: `(BLOCK (PARA (MARK "mark" "mark" "mark" (TEXT "with text"))))`,
- encoderSHTML: `((p (a (@ (id . "mark")) "with text")))`,
+ encoderSHTML: `((p (a ((id . "mark")) "with text")))`,
encoderText: `with text`,
encoderZmk: useZmk,
},
},
{
@@ -474,11 +486,23 @@
zmk: `[[abc]]`,
expect: expectMap{
encoderHTML: `abc
`,
encoderMD: "[abc](abc)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "abc"))))`,
- encoderSHTML: `((p (a (@ (href . "abc")) "abc")))`,
+ encoderSHTML: `((p (a ((href . "abc")) "abc")))`,
+ encoderText: ``,
+ encoderZmk: useZmk,
+ },
+ },
+ {
+ descr: "Dummy Link with attribute",
+ zmk: `[[abc]]{a="b"}`,
+ expect: expectMap{
+ encoderHTML: `abc
`,
+ encoderMD: "[abc](abc)",
+ encoderSz: `(BLOCK (PARA (LINK (("a" . "b")) (HOSTED "abc"))))`,
+ encoderSHTML: `((p (a ((a . "b") (href . "abc")) "abc")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -486,11 +510,11 @@
zmk: `[[https://zettelstore.de]]`,
expect: expectMap{
encoderHTML: `https://zettelstore.de
`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (LINK () (EXTERNAL "https://zettelstore.de"))))`,
- encoderSHTML: `((p (a (@ (href . "https://zettelstore.de") (rel . "external")) "https://zettelstore.de")))`,
+ encoderSHTML: `((p (a ((href . "https://zettelstore.de") (rel . "external")) "https://zettelstore.de")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -498,11 +522,11 @@
zmk: `[[Home|https://zettelstore.de]]`,
expect: expectMap{
encoderHTML: `Home
`,
encoderMD: "[Home](https://zettelstore.de)",
encoderSz: `(BLOCK (PARA (LINK () (EXTERNAL "https://zettelstore.de") (TEXT "Home"))))`,
- encoderSHTML: `((p (a (@ (href . "https://zettelstore.de") (rel . "external")) "Home")))`,
+ encoderSHTML: `((p (a ((href . "https://zettelstore.de") (rel . "external")) "Home")))`,
encoderText: `Home`,
encoderZmk: useZmk,
},
},
{
@@ -510,11 +534,11 @@
zmk: `[[00000000000100]]`,
expect: expectMap{
encoderHTML: `00000000000100
`,
encoderMD: "[00000000000100](00000000000100)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100"))))`,
- encoderSHTML: `((p (a (@ (href . "00000000000100")) "00000000000100")))`,
+ encoderSHTML: `((p (a ((href . "00000000000100")) "00000000000100")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -522,11 +546,11 @@
zmk: `[[Config|00000000000100]]`,
expect: expectMap{
encoderHTML: `Config
`,
encoderMD: "[Config](00000000000100)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100") (TEXT "Config"))))`,
- encoderSHTML: `((p (a (@ (href . "00000000000100")) "Config")))`,
+ encoderSHTML: `((p (a ((href . "00000000000100")) "Config")))`,
encoderText: `Config`,
encoderZmk: useZmk,
},
},
{
@@ -534,11 +558,11 @@
zmk: `[[00000000000100#frag]]`,
expect: expectMap{
encoderHTML: `00000000000100#frag
`,
encoderMD: "[00000000000100#frag](00000000000100#frag)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100#frag"))))`,
- encoderSHTML: `((p (a (@ (href . "00000000000100#frag")) "00000000000100#frag")))`,
+ encoderSHTML: `((p (a ((href . "00000000000100#frag")) "00000000000100#frag")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -546,11 +570,11 @@
zmk: `[[Config|00000000000100#frag]]`,
expect: expectMap{
encoderHTML: `Config
`,
encoderMD: "[Config](00000000000100#frag)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100#frag") (TEXT "Config"))))`,
- encoderSHTML: `((p (a (@ (href . "00000000000100#frag")) "Config")))`,
+ encoderSHTML: `((p (a ((href . "00000000000100#frag")) "Config")))`,
encoderText: `Config`,
encoderZmk: useZmk,
},
},
{
@@ -558,11 +582,11 @@
zmk: `[[#frag]]`,
expect: expectMap{
encoderHTML: `#frag
`,
encoderMD: "[#frag](#frag)",
encoderSz: `(BLOCK (PARA (LINK () (SELF "#frag"))))`,
- encoderSHTML: `((p (a (@ (href . "#frag")) "#frag")))`,
+ encoderSHTML: `((p (a ((href . "#frag")) "#frag")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -570,11 +594,11 @@
zmk: `[[H|/hosted]]`,
expect: expectMap{
encoderHTML: `H
`,
encoderMD: "[H](/hosted)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "/hosted") (TEXT "H"))))`,
- encoderSHTML: `((p (a (@ (href . "/hosted")) "H")))`,
+ encoderSHTML: `((p (a ((href . "/hosted")) "H")))`,
encoderText: `H`,
encoderZmk: useZmk,
},
},
{
@@ -583,11 +607,11 @@
expect: expectMap{
encoderHTML: `B
`,
encoderMD: "[B](/based)",
encoderSz: `(BLOCK (PARA (LINK () (BASED "/based") (TEXT "B"))))`,
encoderText: `B`,
- encoderSHTML: `((p (a (@ (href . "/based")) "B")))`,
+ encoderSHTML: `((p (a ((href . "/based")) "B")))`,
encoderZmk: useZmk,
},
},
{
descr: "Relative link",
@@ -594,11 +618,11 @@
zmk: `[[R|../relative]]`,
expect: expectMap{
encoderHTML: `R
`,
encoderMD: "[R](../relative)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "../relative") (TEXT "R"))))`,
- encoderSHTML: `((p (a (@ (href . "../relative")) "R")))`,
+ encoderSHTML: `((p (a ((href . "../relative")) "R")))`,
encoderText: `R`,
encoderZmk: useZmk,
},
},
{
@@ -606,11 +630,11 @@
zmk: `[[query:title:syntax]]`,
expect: expectMap{
encoderHTML: `title:syntax
`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (LINK () (QUERY "title:syntax"))))`,
- encoderSHTML: `((p (a (@ (href . "?q=title%3Asyntax")) "title:syntax")))`,
+ encoderSHTML: `((p (a ((href . "?q=title%3Asyntax")) "title:syntax")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
@@ -618,11 +642,11 @@
zmk: `[[Q|query:title:syntax]]`,
expect: expectMap{
encoderHTML: `Q
`,
encoderMD: "Q",
encoderSz: `(BLOCK (PARA (LINK () (QUERY "title:syntax") (TEXT "Q"))))`,
- encoderSHTML: `((p (a (@ (href . "?q=title%3Asyntax")) "Q")))`,
+ encoderSHTML: `((p (a ((href . "?q=title%3Asyntax")) "Q")))`,
encoderText: `Q`,
encoderZmk: useZmk,
},
},
{
@@ -630,15 +654,39 @@
zmk: `{{abc}}`,
expect: expectMap{
encoderHTML: `
`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (EMBED () (HOSTED "abc") "")))`,
- encoderSHTML: `((p (img (@ (src . "abc")))))`,
+ encoderSHTML: `((p (img ((src . "abc")))))`,
+ encoderText: ``,
+ encoderZmk: useZmk,
+ },
+ },
+ {
+ descr: "Dummy Embed with attributes",
+ zmk: `{{abc}}{a="b"}`,
+ expect: expectMap{
+ encoderHTML: `
`,
+ encoderMD: "",
+ encoderSz: `(BLOCK (PARA (EMBED (("a" . "b")) (HOSTED "abc") "")))`,
+ encoderSHTML: `((p (img ((a . "b") (src . "abc")))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
+ {
+ descr: "Link and attributes with quotes",
+ zmk: `[[text|/url]]{title="TITL \"\""}`,
+ expect: expectMap{
+ encoderHTML: `text
`,
+ encoderMD: "[text](/url)", // better: [text](/url "TITL \"\"")
+ encoderSz: `(BLOCK (PARA (LINK (("title" . "TITL \"\"")) (HOSTED "/url") (TEXT "text"))))`,
+ encoderSHTML: `((p (a ((href . "/url") (title . "TITL \"\"")) "text")))`,
+ encoderText: `text`,
+ encoderZmk: useZmk,
+ },
+ },
{
descr: "",
zmk: ``,
expect: expectMap{
encoderHTML: ``,
Index: internal/encoder/encoder_test.go
==================================================================
--- internal/encoder/encoder_test.go
+++ internal/encoder/encoder_test.go
@@ -16,26 +16,26 @@
import (
"fmt"
"strings"
"testing"
- "t73f.de/r/sx/sxreader"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
- "zettelstore.de/z/internal/ast"
- "zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/encoder"
"zettelstore.de/z/internal/parser"
)
type zmkTestCase struct {
- descr string
- zmk string
- inline bool
- expect expectMap
+ descr string
+ zmk string
+ syntax string
+ allowHTML bool
+ inline bool
+ expect expectMap
}
type expectMap map[api.EncodingEnum]string
const useZmk = "\000"
@@ -50,26 +50,34 @@
func TestEncoder(t *testing.T) {
for i := range tcsInline {
tcsInline[i].inline = true
}
- executeTestCases(t, append(tcsBlock, tcsInline...))
+ executeTestCases(t, append(append([]zmkTestCase{}, tcsBlock...), tcsInline...))
}
func executeTestCases(t *testing.T, testCases []zmkTestCase) {
for testNum, tc := range testCases {
inp := input.NewInput([]byte(tc.zmk))
- bs := parser.Parse(inp, nil, meta.ValueSyntaxZmk, config.NoHTML)
- checkEncodings(t, testNum, bs, tc.inline, tc.descr, tc.expect, tc.zmk)
- checkSz(t, testNum, bs, tc.inline, tc.descr)
+ syntax := tc.syntax
+ if syntax == "" {
+ syntax = meta.ValueSyntaxZmk
+ }
+ alst := sx.Nil()
+ if tc.allowHTML {
+ alst = alst.Cons(sx.Cons(parser.SymAllowHTML, nil))
+ }
+ node := parser.Parse(inp, nil, syntax, alst)
+ parser.Clean(node)
+ checkEncodings(t, testNum, node, tc.inline, tc.descr, tc.expect, tc.zmk)
}
}
-func checkEncodings(t *testing.T, testNum int, bs ast.BlockSlice, isInline bool, descr string, expected expectMap, zmkDefault string) {
+func checkEncodings(t *testing.T, testNum int, node *sx.Pair, isInline bool, descr string, expected expectMap, zmkDefault string) {
for enc, exp := range expected {
encdr := encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN})
- got, err := encode(encdr, bs)
+ got, err := encode(encdr, node)
if err != nil {
prefix := fmt.Sprintf("Test #%d", testNum)
if d := descr; d != "" {
prefix += "\nReason: " + d
}
@@ -89,41 +97,17 @@
t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got)
}
}
}
-func checkSz(t *testing.T, testNum int, bs ast.BlockSlice, isInline bool, descr string) {
- t.Helper()
- encdr := encoder.Create(encoderSz, nil)
- exp, err := encode(encdr, bs)
- if err != nil {
- t.Error(err)
- return
- }
- val, err := sxreader.MakeReader(strings.NewReader(exp)).Read()
- if err != nil {
- t.Error(err)
- return
- }
- got := val.String()
- if exp != got {
- prefix := fmt.Sprintf("Test #%d", testNum)
- if d := descr; d != "" {
- prefix += "\nReason: " + d
- }
- prefix += "\nMode: " + mode(isInline)
- t.Errorf("%s\n\nExpected: %q\nGot: %q", prefix, exp, got)
- }
-}
-
-func encode(e encoder.Encoder, bs ast.BlockSlice) (string, error) {
+func encode(e encoder.Encoder, node *sx.Pair) (string, error) {
var sb strings.Builder
- _, err := e.WriteBlocks(&sb, &bs)
+ err := e.WriteSz(&sb, node)
return sb.String(), err
}
func mode(isInline bool) string {
if isInline {
return "inline"
}
return "block"
}
Index: internal/encoder/htmlenc.go
==================================================================
--- internal/encoder/htmlenc.go
+++ internal/encoder/htmlenc.go
@@ -21,58 +21,57 @@
"t73f.de/r/sx"
"t73f.de/r/sxwebs/sxhtml"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
+ "t73f.de/r/zsc/sz"
+ "t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// htmlEncoder contains all data needed for encoding.
type htmlEncoder struct {
- tx SzTransformer
th *shtml.Evaluator
lang string
textEnc TextEncoder
}
// WriteZettel encodes a full zettel as HTML5.
-func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode) (int, error) {
+func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
env := shtml.MakeEnvironment(he.lang)
- hm, err := he.th.Evaluate(he.tx.GetMeta(zn.InhMeta), &env)
+ hm, err := he.th.Evaluate(ast.GetMetaSz(zn.InhMeta), &env)
if err != nil {
- return 0, err
+ return err
}
- var isTitle ast.InlineSlice
+ var szTitle *sx.Pair
var htitle *sx.Pair
plainTitle, hasTitle := zn.InhMeta.Get(meta.KeyTitle)
if hasTitle {
- isTitle = ast.ParseSpacedText(string(plainTitle))
- xtitle := he.tx.GetSz(&isTitle)
- htitle, err = he.th.Evaluate(xtitle, &env)
+ szTitle = zsx.MakeInline((zsx.MakeText(sz.NormalizedSpacedText(string(plainTitle)))))
+ htitle, err = he.th.Evaluate(szTitle, &env)
if err != nil {
- return 0, err
+ return err
}
}
- xast := he.tx.GetSz(&zn.BlocksAST)
- hast, err := he.th.Evaluate(xast, &env)
+ hast, err := he.th.Evaluate(zn.Blocks, &env)
if err != nil {
- return 0, err
+ return err
}
hen := shtml.Endnotes(&env)
var head sx.ListBuilder
head.AddN(
shtml.SymHead,
- sx.Nil().Cons(sx.Nil().Cons(sx.Cons(sx.MakeSymbol("charset"), sx.MakeString("utf-8"))).Cons(sxhtml.SymAttr)).Cons(shtml.SymMeta),
+ sx.Nil().Cons(sx.Nil().Cons(sx.Cons(sxhtml.MakeSymbol("charset"), sx.MakeString("utf-8")))).Cons(shtml.SymMeta),
)
head.ExtendBang(hm)
var sb strings.Builder
if hasTitle {
- _, _ = he.textEnc.WriteInlines(&sb, &isTitle)
+ _ = he.textEnc.WriteSz(&sb, szTitle)
} else {
sb.Write(zn.Meta.Zid.Bytes())
}
head.Add(sx.MakeList(shtml.SymAttrTitle, sx.MakeString(sb.String())))
@@ -94,32 +93,29 @@
gen := sxhtml.NewGenerator().SetNewline()
return gen.WriteHTML(w, doc)
}
// WriteMeta encodes meta data as HTML5.
-func (he *htmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
+func (he *htmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
env := shtml.MakeEnvironment(he.lang)
- hm, err := he.th.Evaluate(he.tx.GetMeta(m), &env)
+ hm, err := he.th.Evaluate(ast.GetMetaSz(m), &env)
if err != nil {
- return 0, err
+ return err
}
gen := sxhtml.NewGenerator().SetNewline()
return gen.WriteListHTML(w, hm)
}
-// WriteBlocks encodes a block slice.
-func (he *htmlEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) {
+// WriteSz encodes SZ represented zettel content.
+func (he *htmlEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
env := shtml.MakeEnvironment(he.lang)
- hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env)
+ hobj, err := he.th.Evaluate(node, &env)
if err == nil {
gen := sxhtml.NewGenerator()
- length, err2 := gen.WriteListHTML(w, hobj)
- if err2 != nil {
- return length, err2
+ if err = gen.WriteListHTML(w, hobj); err != nil {
+ return err
}
- l, err2 := gen.WriteHTML(w, shtml.Endnotes(&env))
- length += l
- return length, err2
+ return gen.WriteHTML(w, shtml.Endnotes(&env))
}
- return 0, err
+ return err
}
Index: internal/encoder/mdenc.go
==================================================================
--- internal/encoder/mdenc.go
+++ internal/encoder/mdenc.go
@@ -15,13 +15,16 @@
// Encodes the abstract syntax tree back into Markdown.
import (
"io"
+ "net/url"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
@@ -29,250 +32,171 @@
type mdEncoder struct {
lang string
}
// WriteZettel writes the encoded zettel to the writer.
-func (me *mdEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode) (int, error) {
+func (me *mdEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
v := newMDVisitor(w, me.lang)
- v.acceptMeta(zn.InhMeta)
+ v.b.WriteMeta(zn.InhMeta)
if zn.InhMeta.YamlSep {
v.b.WriteString("---\n")
} else {
v.b.WriteLn()
}
- ast.Walk(&v, &zn.BlocksAST)
- length, err := v.b.Flush()
- return length, err
+ v.walk(zn.Blocks, nil)
+ return v.b.Flush()
}
// WriteMeta encodes meta data as markdown.
-func (me *mdEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
- v := newMDVisitor(w, me.lang)
- v.acceptMeta(m)
- length, err := v.b.Flush()
- return length, err
-}
-
-func (v *mdVisitor) acceptMeta(m *meta.Meta) {
- for key, val := range m.Computed() {
- v.b.WriteStrings(key, ": ", string(val), "\n")
- }
-}
-
-// WriteBlocks writes the content of a block slice to the writer.
-func (me *mdEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) {
- v := newMDVisitor(w, me.lang)
- ast.Walk(&v, bs)
- length, err := v.b.Flush()
- return length, err
-}
-
-// mdVisitor writes the abstract syntax tree to an EncWriter.
+func (*mdEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
+ ew := newEncWriter(w)
+ ew.WriteMeta(m)
+ return ew.Flush()
+}
+
+// WriteSz encodes SZ represented zettel content.
+func (me *mdEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
+ v := newMDVisitor(w, me.lang)
+ zsx.WalkIt(&v, node, nil)
+ return v.b.Flush()
+}
+
type mdVisitor struct {
b encWriter
listInfo []int
listPrefix string
- langStack shtml.LangStack
+ defLang string
quoteNesting uint
}
func newMDVisitor(w io.Writer, lang string) mdVisitor {
- return mdVisitor{b: newEncWriter(w), langStack: shtml.NewLangStack(lang)}
-}
-
-// pushAttribute adds the current attributes to the visitor.
-func (v *mdVisitor) pushAttributes(a zsx.Attributes) {
- if value, ok := a.Get("lang"); ok {
- v.langStack.Push(value)
- } else {
- v.langStack.Dup()
- }
-}
-
-// popAttributes removes the current attributes from the visitor.
-func (v *mdVisitor) popAttributes() { v.langStack.Pop() }
-
-// getLanguage returns the current language,
-func (v *mdVisitor) getLanguage() string { return v.langStack.Top() }
-
-func (v *mdVisitor) getQuotes() (string, string, bool) {
- qi := shtml.GetQuoteInfo(v.getLanguage())
+ return mdVisitor{b: newEncWriter(w), defLang: lang}
+}
+
+var symLang = sx.MakeSymbol("lang")
+
+func (v *mdVisitor) getLanguage(alst *sx.Pair) string {
+ if a := alst.Assoc(symLang); a != nil {
+ if s, isString := sx.GetString(a.Cdr()); isString {
+ return s.GetValue()
+ }
+ }
+ return v.defLang
+}
+func (*mdVisitor) setLanguage(alst, attrs *sx.Pair) *sx.Pair {
+ if a := attrs.Assoc(sx.MakeString("lang")); a != nil {
+ val := a.Cdr()
+ if p, isPair := sx.GetPair(val); isPair {
+ val = p.Car()
+ }
+ return alst.Cons(sx.Cons(symLang, val))
+ }
+ return alst
+}
+
+func (v *mdVisitor) getQuotes(alst *sx.Pair) (string, string, bool) {
+ qi := shtml.GetQuoteInfo(v.getLanguage(alst))
leftQ, rightQ := qi.GetQuotes(v.quoteNesting)
return leftQ, rightQ, qi.GetNBSp()
}
-
-func (v *mdVisitor) Visit(node ast.Node) ast.Visitor {
- switch n := node.(type) {
- case *ast.BlockSlice:
- v.visitBlockSlice(n)
- case *ast.VerbatimNode:
- v.visitVerbatim(n)
- case *ast.RegionNode:
- v.visitRegion(n)
- case *ast.HeadingNode:
- v.visitHeading(n)
- case *ast.HRuleNode:
- v.b.WriteString("---")
- case *ast.NestedListNode:
- v.visitNestedList(n)
- case *ast.DescriptionListNode:
- return nil // Should write no content
- case *ast.TableNode:
- return nil // Should write no content
- case *ast.TextNode:
- v.b.WriteString(n.Text)
- case *ast.BreakNode:
- v.visitBreak(n)
- case *ast.LinkNode:
- v.visitLink(n)
- case *ast.EmbedRefNode:
- v.visitEmbedRef(n)
- case *ast.FootnoteNode:
- return nil // Should write no content
- case *ast.FormatNode:
- v.visitFormat(n)
- case *ast.LiteralNode:
- v.visitLiteral(n)
- default:
- return v
- }
- return nil
-}
-
-func (v *mdVisitor) visitBlockSlice(bs *ast.BlockSlice) {
- for i, bn := range *bs {
- if i > 0 {
+func (v *mdVisitor) walk(node, alst *sx.Pair) { zsx.WalkIt(v, node, alst) }
+func (v *mdVisitor) walkList(lst, alst *sx.Pair) { zsx.WalkItList(v, lst, 0, alst) }
+func (v *mdVisitor) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymBlock:
+ v.visitBlock(node, alst)
+
+ case zsx.SymText:
+ v.b.WriteString(zsx.GetText(node))
+ case zsx.SymSoft:
+ v.visitBreak(false)
+ case zsx.SymHard:
+ v.visitBreak(true)
+
+ case zsx.SymLink:
+ attrs, ref, inlines := zsx.GetLink(node)
+ alst = v.setLanguage(alst, attrs)
+ v.visitReference(ref, inlines, alst)
+ case zsx.SymEmbed:
+ attrs, ref, _, inlines := zsx.GetEmbed(node)
+ alst = v.setLanguage(alst, attrs)
+ _ = v.b.WriteByte('!')
+ v.visitReference(ref, inlines, alst)
+
+ case zsx.SymFormatEmph:
+ v.visitFormat(node, alst, "*", "*")
+ case zsx.SymFormatStrong:
+ v.visitFormat(node, alst, "__", "__")
+ case zsx.SymFormatQuote:
+ v.visitQuote(node, alst)
+ case zsx.SymFormatMark:
+ v.visitFormat(node, alst, "", "")
+ case zsx.SymFormatSpan, zsx.SymFormatDelete, zsx.SymFormatInsert, zsx.SymFormatSub, zsx.SymFormatSuper:
+ v.visitFormat(node, alst, "", "")
+
+ case zsx.SymLiteralCode, zsx.SymLiteralInput, zsx.SymLiteralOutput:
+ _, _, content := zsx.GetLiteral(node)
+ v.b.WriteStrings("`", content, "`")
+ case zsx.SymLiteralMath:
+ _, _, content := zsx.GetLiteral(node)
+ v.b.WriteString(content)
+
+ case zsx.SymHeading:
+ level, attrs, text, _, _ := zsx.GetHeading(node)
+ const headingSigns = "###### "
+ v.b.WriteString(headingSigns[len(headingSigns)-level-1:])
+ v.walkList(text, v.setLanguage(alst, attrs))
+
+ case zsx.SymThematic:
+ v.b.WriteString("---")
+
+ case zsx.SymListOrdered:
+ v.visitNestedList(node, alst, enumOrdered)
+ case zsx.SymListUnordered:
+ v.visitNestedList(node, alst, enumUnordered)
+ case zsx.SymListQuote:
+ if len(v.listInfo) == 0 {
+ v.visitListQuote(node, alst)
+ }
+
+ case zsx.SymVerbatimCode:
+ v.visitVerbatim(node)
+
+ case zsx.SymRegionQuote:
+ v.visitRegion(node, alst)
+
+ case zsx.SymRegionBlock, zsx.SymRegionVerse,
+ zsx.SymVerbatimComment, zsx.SymVerbatimEval, zsx.SymVerbatimHTML, zsx.SymVerbatimMath, zsx.SymVerbatimZettel,
+ zsx.SymDescription, zsx.SymTable, zsx.SymEndnote,
+ zsx.SymLiteralComment:
+ // Do nothing, ignore it.
+
+ default:
+ return false
+ }
+ return true
+ }
+ return false
+}
+
+func (v *mdVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+func (v *mdVisitor) visitBlock(node *sx.Pair, alst *sx.Pair) {
+ first := true
+ for bn := range node.Tail().Pairs() {
+ if first {
+ first = false
+ } else {
v.b.WriteString("\n\n")
}
- ast.Walk(v, bn)
- }
-}
-
-func (v *mdVisitor) visitVerbatim(vn *ast.VerbatimNode) {
- lc := len(vn.Content)
- if vn.Kind != ast.VerbatimCode || lc == 0 {
- return
- }
- v.writeSpaces(4)
- lcm1 := lc - 1
- for i := 0; i < lc; i++ {
- b := vn.Content[i]
- if b != '\n' && b != '\r' {
- _ = v.b.WriteByte(b)
- continue
- }
- j := i + 1
- for ; j < lc; j++ {
- c := vn.Content[j]
- if c != '\n' && c != '\r' {
- break
- }
- }
- if j >= lcm1 {
- break
- }
- v.b.WriteLn()
- v.writeSpaces(4)
- i = j - 1
- }
-}
-
-func (v *mdVisitor) visitRegion(rn *ast.RegionNode) {
- if rn.Kind != ast.RegionQuote {
- return
- }
- v.pushAttributes(rn.Attrs)
- defer v.popAttributes()
-
- first := true
- for _, bn := range rn.Blocks {
- pn, ok := bn.(*ast.ParaNode)
- if !ok {
- continue
- }
- if !first {
- v.b.WriteString("\n>\n")
- }
- first = false
- v.b.WriteString("> ")
- ast.Walk(v, &pn.Inlines)
- }
-}
-
-func (v *mdVisitor) visitHeading(hn *ast.HeadingNode) {
- v.pushAttributes(hn.Attrs)
- defer v.popAttributes()
-
- const headingSigns = "###### "
- v.b.WriteString(headingSigns[len(headingSigns)-hn.Level-1:])
- ast.Walk(v, &hn.Inlines)
-}
-
-func (v *mdVisitor) visitNestedList(ln *ast.NestedListNode) {
- switch ln.Kind {
- case ast.NestedListOrdered:
- v.writeNestedList(ln, "1. ")
- case ast.NestedListUnordered:
- v.writeNestedList(ln, "* ")
- case ast.NestedListQuote:
- v.writeListQuote(ln)
- }
- v.listInfo = v.listInfo[:len(v.listInfo)-1]
-}
-
-func (v *mdVisitor) writeNestedList(ln *ast.NestedListNode, enum string) {
- v.listInfo = append(v.listInfo, len(enum))
- regIndent := 4*len(v.listInfo) - 4
- paraIndent := regIndent + len(enum)
- for i, item := range ln.Items {
- if i > 0 {
- v.b.WriteLn()
- }
- v.writeSpaces(regIndent)
- v.b.WriteString(enum)
- for j, in := range item {
- if j > 0 {
- v.b.WriteLn()
- if _, ok := in.(*ast.ParaNode); ok {
- v.writeSpaces(paraIndent)
- }
- }
- ast.Walk(v, in)
- }
- }
-}
-
-func (v *mdVisitor) writeListQuote(ln *ast.NestedListNode) {
- v.listInfo = append(v.listInfo, 0)
- if len(v.listInfo) > 1 {
- return
- }
-
- prefix := v.listPrefix
- v.listPrefix = "> "
-
- for i, item := range ln.Items {
- if i > 0 {
- v.b.WriteLn()
- }
- v.b.WriteString(v.listPrefix)
- for j, in := range item {
- if j > 0 {
- v.b.WriteLn()
- if _, ok := in.(*ast.ParaNode); ok {
- v.b.WriteString(v.listPrefix)
- }
- }
- ast.Walk(v, in)
- }
- }
-
- v.listPrefix = prefix
-}
-
-func (v *mdVisitor) visitBreak(bn *ast.BreakNode) {
- if bn.Hard {
+ v.walk(bn.Head(), alst)
+ }
+}
+
+func (v *mdVisitor) visitBreak(isHard bool) {
+ if isHard {
v.b.WriteString("\\\n")
} else {
v.b.WriteLn()
}
if l := len(v.listInfo); l > 0 {
@@ -283,101 +207,174 @@
v.b.WriteString(v.listPrefix)
}
}
}
-func (v *mdVisitor) visitLink(ln *ast.LinkNode) {
- v.pushAttributes(ln.Attrs)
- defer v.popAttributes()
-
- v.writeReference(ln.Ref, ln.Inlines)
-}
-
-func (v *mdVisitor) visitEmbedRef(en *ast.EmbedRefNode) {
- v.pushAttributes(en.Attrs)
- defer v.popAttributes()
-
- _ = v.b.WriteByte('!')
- v.writeReference(en.Ref, en.Inlines)
-}
-
-func (v *mdVisitor) writeReference(ref *ast.Reference, is ast.InlineSlice) {
- if ref.State == ast.RefStateQuery {
- ast.Walk(v, &is)
- } else if len(is) > 0 {
+func (v *mdVisitor) visitReference(ref, inlines, alst *sx.Pair) {
+ refState, val := zsx.GetReference(ref)
+ if sz.SymRefStateQuery.IsEqualSymbol(refState) {
+ v.walkList(inlines, alst)
+ } else if inlines != nil {
_ = v.b.WriteByte('[')
- ast.Walk(v, &is)
- v.b.WriteStrings("](", ref.String())
+ v.walkList(inlines, alst)
+ v.b.WriteStrings("](", val)
_ = v.b.WriteByte(')')
- } else if isAutoLinkable(ref) {
+ } else if isAutoLinkable(refState, val) {
_ = v.b.WriteByte('<')
- v.b.WriteString(ref.String())
+ v.b.WriteString(val)
_ = v.b.WriteByte('>')
} else {
- s := ref.String()
- v.b.WriteStrings("[", s, "](", s, ")")
- }
-}
-
-func isAutoLinkable(ref *ast.Reference) bool {
- if ref.State != ast.RefStateExternal || ref.URL == nil {
- return false
- }
- return ref.URL.Scheme != ""
-}
-
-func (v *mdVisitor) visitFormat(fn *ast.FormatNode) {
- v.pushAttributes(fn.Attrs)
- defer v.popAttributes()
-
- switch fn.Kind {
- case ast.FormatEmph:
- _ = v.b.WriteByte('*')
- ast.Walk(v, &fn.Inlines)
- _ = v.b.WriteByte('*')
- case ast.FormatStrong:
- v.b.WriteString("__")
- ast.Walk(v, &fn.Inlines)
- v.b.WriteString("__")
- case ast.FormatQuote:
- v.writeQuote(fn)
- case ast.FormatMark:
- v.b.WriteString("")
- ast.Walk(v, &fn.Inlines)
- v.b.WriteString("")
- default:
- ast.Walk(v, &fn.Inlines)
- }
-}
-
-func (v *mdVisitor) writeQuote(fn *ast.FormatNode) {
- leftQ, rightQ, withNbsp := v.getQuotes()
+ v.b.WriteStrings("[", val, "](", val, ")")
+ }
+}
+func isAutoLinkable(refState *sx.Symbol, val string) bool {
+ if zsx.SymRefStateExternal.IsEqualSymbol(refState) {
+ if u, err := url.Parse(val); err == nil && u.Scheme != "" {
+ return true
+ }
+ }
+ return false
+}
+
+func (v *mdVisitor) visitFormat(node, alst *sx.Pair, delim1, delim2 string) {
+ _, attrs, inlines := zsx.GetFormat(node)
+ alst = v.setLanguage(alst, attrs)
+ v.b.WriteString(delim1)
+ v.walkList(inlines, alst)
+ v.b.WriteString(delim2)
+}
+func (v *mdVisitor) visitQuote(node, alst *sx.Pair) {
+ _, attrs, inlines := zsx.GetFormat(node)
+ alst = v.setLanguage(alst, attrs)
+ leftQ, rightQ, withNbsp := v.getQuotes(alst)
v.b.WriteString(leftQ)
if withNbsp {
v.b.WriteString(" ")
}
v.quoteNesting++
- ast.Walk(v, &fn.Inlines)
+ v.walkList(inlines, alst)
v.quoteNesting--
if withNbsp {
v.b.WriteString(" ")
}
v.b.WriteString(rightQ)
}
-func (v *mdVisitor) visitLiteral(ln *ast.LiteralNode) {
- switch ln.Kind {
- case ast.LiteralCode, ast.LiteralInput, ast.LiteralOutput:
- _ = v.b.WriteByte('`')
- _, _ = v.b.Write(ln.Content)
- _ = v.b.WriteByte('`')
- case ast.LiteralComment: // ignore everything
- default:
- _, _ = v.b.Write(ln.Content)
+const enumOrdered = "1. "
+const enumUnordered = "* "
+
+func (v *mdVisitor) visitNestedList(node *sx.Pair, alst *sx.Pair, enum string) {
+ v.listInfo = append(v.listInfo, len(enum))
+ regIndent := 4*len(v.listInfo) - 4
+ paraIndent := regIndent + len(enum)
+ _, attrs, blocks := zsx.GetList(node)
+ alst = v.setLanguage(alst, attrs)
+ firstBlk := true
+ for blk := range blocks.Pairs() {
+ if firstBlk {
+ firstBlk = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.writeSpaces(regIndent)
+ v.b.WriteString(enum)
+ first := true
+ for item := range blk.Head().Tail().Pairs() {
+ in := item.Head()
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ if zsx.SymPara.IsEqual(in.Car()) {
+ v.writeSpaces(paraIndent)
+ }
+ }
+ v.walk(in, alst)
+ }
+ }
+ v.listInfo = v.listInfo[:len(v.listInfo)-1]
+}
+func (v *mdVisitor) visitListQuote(node *sx.Pair, alst *sx.Pair) {
+ v.listInfo = []int{0}
+ oldPrefix := v.listPrefix
+ v.listPrefix = "> "
+
+ _, attrs, blocks := zsx.GetList(node)
+ alst = v.setLanguage(alst, attrs)
+ firstBlk := true
+ for blk := range blocks.Pairs() {
+ if firstBlk {
+ firstBlk = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.b.WriteString(v.listPrefix)
+ first := true
+ for item := range blk.Head().Tail().Pairs() {
+ in := item.Head()
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ if zsx.SymPara.IsEqual(in.Car()) {
+ v.b.WriteString(v.listPrefix)
+ }
+ }
+ v.walk(in, alst)
+ }
+ }
+ v.listPrefix = oldPrefix
+ v.listInfo = nil
+}
+
+func (v *mdVisitor) visitVerbatim(node *sx.Pair) {
+ if _, _, content := zsx.GetVerbatim(node); content != "" {
+ lc := len(content)
+ v.writeSpaces(4)
+ lcm1 := lc - 1
+ for i := 0; i < lc; i++ {
+ b := content[i]
+ if b != '\n' && b != '\r' {
+ _ = v.b.WriteByte(b)
+ continue
+ }
+ j := i + 1
+ for ; j < lc; j++ {
+ c := content[j]
+ if c != '\n' && c != '\r' {
+ break
+ }
+ }
+ if j >= lcm1 {
+ break
+ }
+ v.b.WriteLn()
+ v.writeSpaces(4)
+ i = j - 1
+ }
+ }
+}
+
+func (v *mdVisitor) visitRegion(node *sx.Pair, alst *sx.Pair) {
+ _, attrs, blocks, _ := zsx.GetRegion(node)
+ alst = v.setLanguage(alst, attrs)
+
+ first := true
+ for n := range blocks.Pairs() {
+ blk := n.Head()
+ if zsx.SymPara.IsEqual(blk.Car()) {
+ if first {
+ first = false
+ } else {
+ v.b.WriteString("\n>\n")
+ }
+ v.b.WriteString("> ")
+ v.walk(blk, alst)
+ }
}
}
func (v *mdVisitor) writeSpaces(n int) {
for range n {
v.b.WriteSpace()
}
}
Index: internal/encoder/shtmlenc.go
==================================================================
--- internal/encoder/shtmlenc.go
+++ internal/encoder/shtmlenc.go
@@ -25,44 +25,46 @@
"zettelstore.de/z/internal/ast"
)
// shtmlEncoder contains all data needed for encoding.
type shtmlEncoder struct {
- tx SzTransformer
th *shtml.Evaluator
lang string
}
// WriteZettel writes the encoded zettel to the writer.
-func (enc *shtmlEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode) (int, error) {
- env := shtml.MakeEnvironment(enc.lang)
- metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(zn.InhMeta), &env)
- if err != nil {
- return 0, err
- }
- contentSHTML, err := enc.th.Evaluate(enc.tx.GetSz(&zn.BlocksAST), &env)
- if err != nil {
- return 0, err
+func (enc *shtmlEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
+ env := shtml.MakeEnvironment(enc.lang)
+ metaSHTML, err := enc.th.Evaluate(ast.GetMetaSz(zn.InhMeta), &env)
+ if err != nil {
+ return err
+ }
+ contentSHTML, err := enc.th.Evaluate(zn.Blocks, &env)
+ if err != nil {
+ return err
}
result := sx.Cons(metaSHTML, contentSHTML)
- return result.Print(w)
+ _, err = result.Print(w)
+ return err
}
// WriteMeta encodes meta data as s-expression.
-func (enc *shtmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
- env := shtml.MakeEnvironment(enc.lang)
- metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(m), &env)
- if err != nil {
- return 0, err
- }
- return sx.Print(w, metaSHTML)
-}
-
-// WriteBlocks writes a block slice to the writer
-func (enc *shtmlEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) {
- env := shtml.MakeEnvironment(enc.lang)
- hval, err := enc.th.Evaluate(enc.tx.GetSz(bs), &env)
- if err != nil {
- return 0, err
- }
- return sx.Print(w, hval)
+func (enc *shtmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
+ env := shtml.MakeEnvironment(enc.lang)
+ metaSHTML, err := enc.th.Evaluate(ast.GetMetaSz(m), &env)
+ if err != nil {
+ return err
+ }
+ _, err = sx.Print(w, metaSHTML)
+ return err
+}
+
+// WriteSz encodes SZ represented zettel content.
+func (enc *shtmlEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
+ env := shtml.MakeEnvironment(enc.lang)
+ hval, err := enc.th.Evaluate(node, &env)
+ if err != nil {
+ return err
+ }
+ _, err = hval.Print(w)
+ return err
}
Index: internal/encoder/szenc.go
==================================================================
--- internal/encoder/szenc.go
+++ internal/encoder/szenc.go
@@ -23,25 +23,25 @@
"zettelstore.de/z/internal/ast"
)
// szEncoder contains all data needed for encoding.
-type szEncoder struct {
- trans SzTransformer
-}
+type szEncoder struct{}
// WriteZettel writes the encoded zettel to the writer.
-func (enc *szEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode) (int, error) {
- content := enc.trans.GetSz(&zn.BlocksAST)
- meta := enc.trans.GetMeta(zn.InhMeta)
- return sx.MakeList(meta, content).Print(w)
+func (enc *szEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
+ meta := ast.GetMetaSz(zn.InhMeta)
+ _, err := sx.MakeList(meta, zn.Blocks).Print(w)
+ return err
}
// WriteMeta encodes meta data as s-expression.
-func (enc *szEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
- return enc.trans.GetMeta(m).Print(w)
+func (enc *szEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
+ _, err := ast.GetMetaSz(m).Print(w)
+ return err
}
-// WriteBlocks writes a block slice to the writer
-func (enc *szEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) {
- return enc.trans.GetSz(bs).Print(w)
+// WriteSz encodes SZ represented zettel content.
+func (*szEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
+ _, err := node.Print(w)
+ return err
}
DELETED internal/encoder/sztransform.go
Index: internal/encoder/sztransform.go
==================================================================
--- internal/encoder/sztransform.go
+++ /dev/null
@@ -1,359 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2022-present Detlef Stern
-//
-// This file is part of Zettelstore.
-//
-// Zettelstore 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: 2022-present Detlef Stern
-//-----------------------------------------------------------------------------
-
-package encoder
-
-import (
- "encoding/base64"
- "fmt"
- "strings"
-
- "t73f.de/r/sx"
- "t73f.de/r/zsc/domain/meta"
- "t73f.de/r/zsc/sz"
- "t73f.de/r/zsx"
-
- "zettelstore.de/z/internal/ast"
-)
-
-// NewSzTransformer returns a new transformer to create s-expressions from AST nodes.
-func NewSzTransformer() SzTransformer {
- return SzTransformer{}
-}
-
-// SzTransformer contains all data needed to transform into a s-expression.
-type SzTransformer struct {
- inVerse bool
-}
-
-// GetSz transforms the given node into a sx list.
-func (t *SzTransformer) GetSz(node ast.Node) *sx.Pair {
- switch n := node.(type) {
- case *ast.BlockSlice:
- return zsx.MakeBlockList(t.getBlockList(n))
- case *ast.InlineSlice:
- return zsx.MakeInlineList(t.getInlineList(*n))
- case *ast.ParaNode:
- return zsx.MakeParaList(t.getInlineList(n.Inlines))
- case *ast.VerbatimNode:
- return zsx.MakeVerbatim(mapGetS(mapVerbatimKindS, n.Kind), getAttributes(n.Attrs), string(n.Content))
- case *ast.RegionNode:
- return t.getRegion(n)
- case *ast.HeadingNode:
- return zsx.MakeHeading(n.Level, getAttributes(n.Attrs), t.getInlineList(n.Inlines), n.Slug, n.Fragment)
- case *ast.HRuleNode:
- return zsx.MakeThematic(getAttributes(n.Attrs))
- case *ast.NestedListNode:
- return t.getNestedList(n)
- case *ast.DescriptionListNode:
- return t.getDescriptionList(n)
- case *ast.TableNode:
- return t.getTable(n)
- case *ast.TranscludeNode:
- return zsx.MakeTransclusion(getAttributes(n.Attrs), getReference(n.Ref), t.getInlineList(n.Inlines))
- case *ast.BLOBNode:
- return t.getBLOB(n)
- case *ast.TextNode:
- return zsx.MakeText(n.Text)
- case *ast.BreakNode:
- if n.Hard {
- return zsx.MakeHard()
- }
- return zsx.MakeSoft()
- case *ast.LinkNode:
- return t.getLink(n)
- case *ast.EmbedRefNode:
- return zsx.MakeEmbed(getAttributes(n.Attrs), getReference(n.Ref), n.Syntax, t.getInlineList(n.Inlines))
- case *ast.EmbedBLOBNode:
- return t.getEmbedBLOB(n)
- case *ast.CiteNode:
- return zsx.MakeCite(getAttributes(n.Attrs), n.Key, t.getInlineList(n.Inlines))
- case *ast.FootnoteNode:
- return zsx.MakeEndnote(getAttributes(n.Attrs), t.getInlineList(n.Inlines))
- case *ast.MarkNode:
- return zsx.MakeMark(n.Mark, n.Slug, n.Fragment, t.getInlineList(n.Inlines))
- case *ast.FormatNode:
- return zsx.MakeFormat(mapGetS(mapFormatKindS, n.Kind), getAttributes(n.Attrs), t.getInlineList(n.Inlines))
- case *ast.LiteralNode:
- return zsx.MakeLiteral(mapGetS(mapLiteralKindS, n.Kind), getAttributes(n.Attrs), string(n.Content))
- }
- return sx.MakeList(zsx.SymUnknown, sx.MakeString(fmt.Sprintf("%T %v", node, node)))
-}
-
-var mapVerbatimKindS = map[ast.VerbatimKind]*sx.Symbol{
- ast.VerbatimZettel: zsx.SymVerbatimZettel,
- ast.VerbatimCode: zsx.SymVerbatimCode,
- ast.VerbatimEval: zsx.SymVerbatimEval,
- ast.VerbatimMath: zsx.SymVerbatimMath,
- ast.VerbatimComment: zsx.SymVerbatimComment,
- ast.VerbatimHTML: zsx.SymVerbatimHTML,
-}
-
-var mapFormatKindS = map[ast.FormatKind]*sx.Symbol{
- ast.FormatEmph: zsx.SymFormatEmph,
- ast.FormatStrong: zsx.SymFormatStrong,
- ast.FormatDelete: zsx.SymFormatDelete,
- ast.FormatInsert: zsx.SymFormatInsert,
- ast.FormatSuper: zsx.SymFormatSuper,
- ast.FormatSub: zsx.SymFormatSub,
- ast.FormatQuote: zsx.SymFormatQuote,
- ast.FormatMark: zsx.SymFormatMark,
- ast.FormatSpan: zsx.SymFormatSpan,
-}
-
-var mapLiteralKindS = map[ast.LiteralKind]*sx.Symbol{
- ast.LiteralCode: zsx.SymLiteralCode,
- ast.LiteralInput: zsx.SymLiteralInput,
- ast.LiteralOutput: zsx.SymLiteralOutput,
- ast.LiteralComment: zsx.SymLiteralComment,
- ast.LiteralMath: zsx.SymLiteralMath,
-}
-
-var mapRegionKindS = map[ast.RegionKind]*sx.Symbol{
- ast.RegionSpan: zsx.SymRegionBlock,
- ast.RegionQuote: zsx.SymRegionQuote,
- ast.RegionVerse: zsx.SymRegionVerse,
-}
-
-func (t *SzTransformer) getRegion(rn *ast.RegionNode) *sx.Pair {
- saveInVerse := t.inVerse
- if rn.Kind == ast.RegionVerse {
- t.inVerse = true
- }
- symBlocks := t.getBlockList(&rn.Blocks)
- t.inVerse = saveInVerse
- return zsx.MakeRegion(
- mapGetS(mapRegionKindS, rn.Kind),
- getAttributes(rn.Attrs), symBlocks,
- t.getInlineList(rn.Inlines),
- )
-}
-
-var mapNestedListKindS = map[ast.NestedListKind]*sx.Symbol{
- ast.NestedListOrdered: zsx.SymListOrdered,
- ast.NestedListUnordered: zsx.SymListUnordered,
- ast.NestedListQuote: zsx.SymListQuote,
-}
-
-func (t *SzTransformer) getNestedList(ln *ast.NestedListNode) *sx.Pair {
- var items sx.ListBuilder
- isCompact := isCompactList(ln.Items)
- for _, item := range ln.Items {
- if isCompact && len(item) > 0 {
- paragraph := t.GetSz(item[0])
- items.Add(zsx.MakeInlineList(paragraph.Tail()))
- continue
- }
- var itemObjs sx.ListBuilder
- for _, in := range item {
- itemObjs.Add(t.GetSz(in))
- }
- if isCompact {
- items.Add(zsx.MakeInlineList(itemObjs.List()))
- } else {
- items.Add(zsx.MakeBlockList(itemObjs.List()))
- }
- }
- return zsx.MakeList(mapGetS(mapNestedListKindS, ln.Kind), getAttributes(ln.Attrs), items.List())
-}
-func isCompactList(itemSlice []ast.ItemSlice) bool {
- for _, items := range itemSlice {
- if len(items) > 1 {
- return false
- }
- if len(items) == 1 {
- if _, ok := items[0].(*ast.ParaNode); !ok {
- return false
- }
- }
- }
- return true
-}
-
-func (t *SzTransformer) getDescriptionList(dn *ast.DescriptionListNode) *sx.Pair {
- var dlObjs sx.ListBuilder
- for _, def := range dn.Descriptions {
- dlObjs.Add(t.getInlineList(def.Term))
- var descObjs sx.ListBuilder
- for _, b := range def.Descriptions {
- var dVal sx.ListBuilder
- for _, dn := range b {
- dVal.Add(t.GetSz(dn))
- }
- descObjs.Add(zsx.MakeBlockList(dVal.List()))
- }
- dlObjs.Add(zsx.MakeBlockList(descObjs.List()))
- }
- return dlObjs.List().Cons(getAttributes(dn.Attrs)).Cons(zsx.SymDescription)
-}
-
-func (t *SzTransformer) getTable(tn *ast.TableNode) *sx.Pair {
- var lb sx.ListBuilder
- lb.AddN(zsx.SymTable, t.getHeader(tn.Header))
- for _, row := range tn.Rows {
- lb.Add(t.getRow(row))
- }
- return lb.List()
-}
-func (t *SzTransformer) getHeader(header ast.TableRow) *sx.Pair {
- if len(header) == 0 {
- return nil
- }
- return t.getRow(header)
-}
-func (t *SzTransformer) getRow(row ast.TableRow) *sx.Pair {
- var lb sx.ListBuilder
- for _, cell := range row {
- lb.Add(t.getCell(cell))
- }
- return lb.List()
-}
-
-func (t *SzTransformer) getCell(cell *ast.TableCell) *sx.Pair {
- var attrs *sx.Pair
- switch cell.Align {
- case ast.AlignCenter:
- attrs = sx.Cons(sx.Cons(zsx.SymAttrAlign, zsx.AttrAlignCenter), nil)
- case ast.AlignLeft:
- attrs = sx.Cons(sx.Cons(zsx.SymAttrAlign, zsx.AttrAlignLeft), nil)
- case ast.AlignRight:
- attrs = sx.Cons(sx.Cons(zsx.SymAttrAlign, zsx.AttrAlignRight), nil)
- }
- return zsx.MakeCell(attrs, t.getInlineList(cell.Inlines))
-}
-
-func (t *SzTransformer) getBLOB(bn *ast.BLOBNode) *sx.Pair {
- var content string
- if bn.Syntax == meta.ValueSyntaxSVG {
- content = string(bn.Blob)
- } else {
- content = getBase64String(bn.Blob)
- }
- return zsx.MakeBLOB(getAttributes(bn.Attrs), t.getInlineList(bn.Description), bn.Syntax, content)
-}
-
-func (t *SzTransformer) getLink(ln *ast.LinkNode) *sx.Pair {
- return zsx.MakeLink(
- getAttributes(ln.Attrs),
- getReference(ln.Ref),
- t.getInlineList(ln.Inlines),
- )
-}
-
-func (t *SzTransformer) getEmbedBLOB(en *ast.EmbedBLOBNode) *sx.Pair {
- var content string
- if en.Syntax == meta.ValueSyntaxSVG {
- content = string(en.Blob)
- } else {
- content = getBase64String(en.Blob)
- }
- return zsx.MakeEmbedBLOB(getAttributes(en.Attrs), en.Syntax, content, t.getInlineList(en.Inlines))
-}
-
-func (t *SzTransformer) getBlockList(bs *ast.BlockSlice) *sx.Pair {
- var lb sx.ListBuilder
- for _, n := range *bs {
- lb.Add(t.GetSz(n))
- }
- return lb.List()
-}
-func (t *SzTransformer) getInlineList(is ast.InlineSlice) *sx.Pair {
- var lb sx.ListBuilder
- for _, n := range is {
- lb.Add(t.GetSz(n))
- }
- return lb.List()
-}
-
-func getAttributes(a zsx.Attributes) *sx.Pair {
- if a.IsEmpty() {
- return sx.Nil()
- }
- keys := a.Keys()
- var lb sx.ListBuilder
- for _, k := range keys {
- lb.Add(sx.Cons(sx.MakeString(k), sx.MakeString(a[k])))
- }
- return lb.List()
-}
-
-var mapRefStateS = map[ast.RefState]*sx.Symbol{
- ast.RefStateInvalid: zsx.SymRefStateInvalid,
- ast.RefStateZettel: sz.SymRefStateZettel,
- ast.RefStateSelf: zsx.SymRefStateSelf,
- ast.RefStateFound: sz.SymRefStateFound,
- ast.RefStateBroken: sz.SymRefStateBroken,
- ast.RefStateHosted: zsx.SymRefStateHosted,
- ast.RefStateBased: sz.SymRefStateBased,
- ast.RefStateQuery: sz.SymRefStateQuery,
- ast.RefStateExternal: zsx.SymRefStateExternal,
-}
-
-func getReference(ref *ast.Reference) *sx.Pair {
- return sx.MakeList(mapGetS(mapRefStateS, ref.State), sx.MakeString(ref.Value))
-}
-
-var mapMetaTypeS = map[*meta.DescriptionType]*sx.Symbol{
- meta.TypeCredential: sz.SymTypeCredential,
- meta.TypeEmpty: sz.SymTypeEmpty,
- meta.TypeID: sz.SymTypeID,
- meta.TypeIDSet: sz.SymTypeIDSet,
- meta.TypeNumber: sz.SymTypeNumber,
- meta.TypeString: sz.SymTypeString,
- meta.TypeTagSet: sz.SymTypeTagSet,
- meta.TypeTimestamp: sz.SymTypeTimestamp,
- meta.TypeURL: sz.SymTypeURL,
- meta.TypeWord: sz.SymTypeWord,
-}
-
-// GetMeta transforms the given metadata into a sx list.
-func (t *SzTransformer) GetMeta(m *meta.Meta) *sx.Pair {
- var lb sx.ListBuilder
- lb.Add(sz.SymMeta)
- for key, val := range m.Computed() {
- ty := m.Type(key)
- symType := mapGetS(mapMetaTypeS, ty)
- var obj sx.Object
- if ty.IsSet {
- var setObjs sx.ListBuilder
- for _, val := range val.AsSlice() {
- setObjs.Add(sx.MakeString(val))
- }
- obj = setObjs.List()
- } else {
- obj = sx.MakeString(string(val))
- }
- lb.Add(sx.Nil().Cons(obj).Cons(sx.MakeSymbol(key)).Cons(symType))
- }
- return lb.List()
-}
-
-func mapGetS[T comparable](m map[T]*sx.Symbol, k T) *sx.Symbol {
- if result, found := m[k]; found {
- return result
- }
- return sx.MakeSymbol(fmt.Sprintf("**%v:NOT-FOUND**", k))
-}
-
-func getBase64String(data []byte) string {
- var sb strings.Builder
- encoder := base64.NewEncoder(base64.StdEncoding, &sb)
- _, err := encoder.Write(data)
- if err == nil {
- err = encoder.Close()
- }
- if err == nil {
- return sb.String()
- }
- return ""
-}
Index: internal/encoder/textenc.go
==================================================================
--- internal/encoder/textenc.go
+++ internal/encoder/textenc.go
@@ -17,41 +17,41 @@
import (
"io"
"iter"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsx"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/ast"
)
// TextEncoder encodes just the text and ignores any formatting.
type TextEncoder struct{}
// WriteZettel writes metadata and content.
-func (te *TextEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode) (int, error) {
+func (te *TextEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
v := newTextVisitor(w)
- _, _ = te.WriteMeta(&v.b, zn.InhMeta)
- v.visitBlockSlice(&zn.BlocksAST)
- length, err := v.b.Flush()
- return length, err
+ _ = te.WriteMeta(&v.b, zn.InhMeta)
+ v.walk(zn.Blocks, nil)
+ return v.b.Flush()
}
// WriteMeta encodes metadata as text.
-func (te *TextEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
+func (te *TextEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
buf := newEncWriter(w)
for key, val := range m.Computed() {
if meta.Type(key) == meta.TypeTagSet {
writeTagSet(&buf, val.Elems())
} else {
buf.WriteString(string(val))
}
buf.WriteLn()
}
- length, err := buf.Flush()
- return length, err
+ return buf.Flush()
}
func writeTagSet(buf *encWriter, tags iter.Seq[meta.Value]) {
first := true
for tag := range tags {
@@ -59,181 +59,171 @@
buf.WriteSpace()
}
first = false
buf.WriteString(string(tag.CleanTag()))
}
-
-}
-
-// WriteBlocks writes the content of a block slice to the writer.
-func (*TextEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) {
- v := newTextVisitor(w)
- v.visitBlockSlice(bs)
- length, err := v.b.Flush()
- return length, err
-}
-
-// WriteInlines writes an inline slice to the writer
-func (*TextEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) {
- v := newTextVisitor(w)
- ast.Walk(&v, is)
- length, err := v.b.Flush()
- return length, err
-}
-
-// textVisitor writes the abstract syntax tree to an io.Writer.
-type textVisitor struct {
- b encWriter
- inlinePos int
-}
+}
+
+// WriteSz writes SZ encoded content to the writer.
+func (*TextEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
+ v := newTextVisitor(w)
+ v.walk(node, nil)
+ return v.b.Flush()
+}
+
+// textVisitor writes the sx.Object-based AST to an io.Writer.
+type textVisitor struct{ b encWriter }
func newTextVisitor(w io.Writer) textVisitor {
return textVisitor{b: newEncWriter(w)}
}
-
-func (v *textVisitor) Visit(node ast.Node) ast.Visitor {
- switch n := node.(type) {
- case *ast.BlockSlice:
- v.visitBlockSlice(n)
- return nil
- case *ast.InlineSlice:
- v.visitInlineSlice(n)
- return nil
- case *ast.VerbatimNode:
- v.visitVerbatim(n)
- return nil
- case *ast.RegionNode:
- v.visitBlockSlice(&n.Blocks)
- if len(n.Inlines) > 0 {
- v.b.WriteLn()
- ast.Walk(v, &n.Inlines)
- }
- return nil
- case *ast.NestedListNode:
- v.visitNestedList(n)
- return nil
- case *ast.DescriptionListNode:
- v.visitDescriptionList(n)
- return nil
- case *ast.TableNode:
- v.visitTable(n)
- return nil
- case *ast.TranscludeNode:
- ast.Walk(v, &n.Inlines)
- case *ast.BLOBNode:
- return nil
- case *ast.TextNode:
- v.visitText(n.Text)
- return nil
- case *ast.BreakNode:
- if n.Hard {
- v.b.WriteLn()
- } else {
- v.b.WriteSpace()
- }
- return nil
- case *ast.LinkNode:
- if len(n.Inlines) > 0 {
- ast.Walk(v, &n.Inlines)
- }
- return nil
- case *ast.MarkNode:
- if len(n.Inlines) > 0 {
- ast.Walk(v, &n.Inlines)
- }
- return nil
- case *ast.FootnoteNode:
- if v.inlinePos > 0 {
- v.b.WriteSpace()
- }
- // No 'return nil' to write text
- case *ast.LiteralNode:
- if n.Kind != ast.LiteralComment {
- _, _ = v.b.Write(n.Content)
- }
- }
- return v
-}
-
-func (v *textVisitor) visitVerbatim(vn *ast.VerbatimNode) {
- if vn.Kind != ast.VerbatimComment {
- _, _ = v.b.Write(vn.Content)
- }
-}
-
-func (v *textVisitor) visitNestedList(ln *ast.NestedListNode) {
- for i, item := range ln.Items {
- v.writePosChar(i, '\n')
- for j, it := range item {
- v.writePosChar(j, '\n')
- ast.Walk(v, it)
- }
- }
-}
-
-func (v *textVisitor) visitDescriptionList(dl *ast.DescriptionListNode) {
- for i, descr := range dl.Descriptions {
- v.writePosChar(i, '\n')
- ast.Walk(v, &descr.Term)
- for _, b := range descr.Descriptions {
- v.b.WriteLn()
- for k, d := range b {
- v.writePosChar(k, '\n')
- ast.Walk(v, d)
- }
- }
- }
-}
-
-func (v *textVisitor) visitTable(tn *ast.TableNode) {
- if len(tn.Header) > 0 {
- v.writeRow(tn.Header)
- v.b.WriteLn()
- }
- for i, row := range tn.Rows {
- v.writePosChar(i, '\n')
- v.writeRow(row)
- }
-}
-
-func (v *textVisitor) writeRow(row ast.TableRow) {
- for i, cell := range row {
- v.writePosChar(i, ' ')
- ast.Walk(v, &cell.Inlines)
- }
-}
-
-func (v *textVisitor) visitBlockSlice(bs *ast.BlockSlice) {
- for i, bn := range *bs {
- v.writePosChar(i, '\n')
- ast.Walk(v, bn)
- }
-}
-
-func (v *textVisitor) visitInlineSlice(is *ast.InlineSlice) {
- for i, in := range *is {
- v.inlinePos = i
- ast.Walk(v, in)
- }
- v.inlinePos = 0
-}
-
-func (v *textVisitor) visitText(s string) {
- spaceFound := false
- for _, ch := range s {
- if input.IsSpace(ch) {
- if !spaceFound {
- v.b.WriteSpace()
- spaceFound = true
- }
- continue
- }
- spaceFound = false
- v.b.WriteString(string(ch))
- }
-}
-
-func (v *textVisitor) writePosChar(pos int, ch byte) {
- if pos > 0 {
- _ = v.b.WriteByte(ch)
- }
-}
+func (v *textVisitor) walk(node, alst *sx.Pair) { zsx.WalkIt(v, node, alst) }
+func (v *textVisitor) walkList(lst, alst *sx.Pair) { zsx.WalkItList(v, lst, 0, alst) }
+func (v *textVisitor) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymText:
+ s := zsx.GetText(node)
+ spaceFound := false
+ for _, ch := range s {
+ if input.IsSpace(ch) {
+ if !spaceFound {
+ v.b.WriteSpace()
+ spaceFound = true
+ }
+ continue
+ }
+ spaceFound = false
+ v.b.WriteString(string(ch))
+ }
+
+ case zsx.SymHard:
+ v.b.WriteLn()
+ case zsx.SymSoft:
+ _ = v.b.WriteByte(' ')
+
+ case zsx.SymEndnote:
+ if zsx.GetWalkPos(alst) > 0 {
+ _ = v.b.WriteByte(' ')
+ }
+ return false
+
+ case zsx.SymLiteralCode, zsx.SymLiteralInput, zsx.SymLiteralMath, zsx.SymLiteralOutput:
+ if s, found := sx.GetString(node.Tail().Tail().Car()); found {
+ v.b.WriteString(s.GetValue())
+ }
+ case zsx.SymLiteralComment:
+ // Do nothing
+
+ case zsx.SymBlock:
+ blocks := zsx.GetBlock(node)
+ first := true
+ for n := range blocks.Pairs() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.walk(n.Head(), alst)
+ }
+ case zsx.SymInline:
+ inlines := zsx.GetInline(node)
+ first := true
+ for n := range inlines.Pairs() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.walk(n.Head(), alst)
+ }
+
+ case zsx.SymListOrdered, zsx.SymListUnordered, zsx.SymListQuote:
+ _, _, items := zsx.GetList(node)
+ first := true
+ for n := range items.Pairs() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.walk(n.Head(), alst)
+ }
+
+ case zsx.SymTable:
+ _, header, rowList := zsx.GetTable(node)
+ firstRow := true
+ for n := range rowList.Cons(header).Pairs() {
+ row := n.Head()
+ if row == nil {
+ continue
+ }
+ if firstRow {
+ firstRow = false
+ } else {
+ v.b.WriteLn()
+ }
+ firstCell := true
+ for elem := range row.Pairs() {
+ if firstCell {
+ firstCell = false
+ } else {
+ _ = v.b.WriteByte(' ')
+ }
+ v.walk(elem.Head(), alst)
+ }
+ }
+
+ case zsx.SymDescription:
+ _, termsVals := zsx.GetDescription(node)
+ first := true
+ for n := termsVals; n != nil; n = n.Tail() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.walkList(n.Head(), alst)
+ n = n.Tail()
+ if n == nil {
+ break
+ }
+ dvals := n.Head()
+ if zsx.SymBlock.IsEqual(dvals.Car()) {
+ for val := range dvals.Tail().Pairs() {
+ v.b.WriteLn()
+ v.walk(val.Head(), alst)
+ }
+ }
+ }
+
+ case zsx.SymRegionBlock, zsx.SymRegionQuote, zsx.SymRegionVerse:
+ _, _, content, inlines := zsx.GetRegion(node)
+ first := true
+ for n := range content.Pairs() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.walk(n.Head(), alst)
+ }
+ if inlines != nil {
+ v.b.WriteLn()
+ v.walkList(inlines, alst)
+ }
+
+ case zsx.SymVerbatimCode, zsx.SymVerbatimEval, zsx.SymVerbatimHTML, zsx.SymVerbatimMath, zsx.SymVerbatimZettel:
+ _, _, s := zsx.GetVerbatim(node)
+ v.b.WriteString(s)
+
+ case zsx.SymVerbatimComment, zsx.SymBLOB:
+ // Do nothing
+
+ default:
+ return false
+ }
+ return true
+ }
+ return false
+}
+func (v *textVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
Index: internal/encoder/write.go
==================================================================
--- internal/encoder/write.go
+++ internal/encoder/write.go
@@ -12,19 +12,19 @@
//-----------------------------------------------------------------------------
package encoder
import (
- "encoding/base64"
"io"
+
+ "t73f.de/r/zsc/domain/meta"
)
// encWriter is a specialized writer for encoding zettel.
type encWriter struct {
- w io.Writer // The io.Writer to write to
- err error // Collect error
- length int // Collected length
+ w io.Writer // The io.Writer to write to
+ err error // Collect error
}
// newEncWriter creates a new encWriter
func newEncWriter(w io.Writer) encWriter { return encWriter{w: w} }
@@ -32,22 +32,19 @@
func (w *encWriter) Write(p []byte) (l int, err error) {
if w.err != nil {
return 0, w.err
}
l, w.err = w.w.Write(p)
- w.length += l
return l, w.err
}
// WriteString writes the content of s.
func (w *encWriter) WriteString(s string) {
if w.err != nil {
return
}
- var l int
- l, w.err = io.WriteString(w.w, s)
- w.length += l
+ _, w.err = io.WriteString(w.w, s)
}
// WriteStrings writes the content of sl.
func (w *encWriter) WriteStrings(sl ...string) {
for _, s := range sl {
@@ -55,33 +52,19 @@
}
}
// WriteByte writes the content of b.
func (w *encWriter) WriteByte(b byte) error {
- var l int
- l, w.err = w.Write([]byte{b})
- w.length += l
+ if w.err == nil {
+ _, w.err = w.Write([]byte{b})
+ }
return w.err
}
// WriteBytes writes the content of bs.
func (w *encWriter) WriteBytes(bs ...byte) { _, _ = w.Write(bs) }
-// WriteBase64 writes the content of p, encoded with base64.
-func (w *encWriter) WriteBase64(p []byte) {
- if w.err == nil {
- encoder := base64.NewEncoder(base64.StdEncoding, w.w)
- var l int
- l, w.err = encoder.Write(p)
- w.length += l
- err1 := encoder.Close()
- if w.err == nil {
- w.err = err1
- }
- }
-}
-
// WriteLn writes a new line character.
func (w *encWriter) WriteLn() {
if w.err == nil {
w.err = w.WriteByte('\n')
}
@@ -91,8 +74,17 @@
func (w *encWriter) WriteSpace() {
if w.err == nil {
w.err = w.WriteByte(' ')
}
}
+
+// WriteMeta write the content of the meta data in a standard way:
+//
+// key: val
+func (w *encWriter) WriteMeta(m *meta.Meta) {
+ for key, val := range m.Computed() {
+ w.WriteStrings(key, ": ", string(val), "\n")
+ }
+}
// Flush returns the collected length and error.
-func (w *encWriter) Flush() (int, error) { return w.length, w.err }
+func (w *encWriter) Flush() error { return w.err }
Index: internal/encoder/zmkenc.go
==================================================================
--- internal/encoder/zmkenc.go
+++ internal/encoder/zmkenc.go
@@ -14,476 +14,491 @@
package encoder
// zmkenc encodes the abstract syntax tree back into Zettelmarkup.
import (
- "fmt"
"io"
"strings"
+ "t73f.de/r/sx"
"t73f.de/r/zero/set"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// zmkEncoder contains all data needed for encoding.
type zmkEncoder struct{}
// WriteZettel writes the encoded zettel to the writer.
-func (*zmkEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode) (int, error) {
+func (ze *zmkEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
v := newZmkVisitor(w)
- v.acceptMeta(zn.InhMeta)
+ v.b.WriteMeta(zn.InhMeta)
if zn.InhMeta.YamlSep {
v.b.WriteString("---\n")
} else {
v.b.WriteLn()
}
- ast.Walk(&v, &zn.BlocksAST)
- length, err := v.b.Flush()
- return length, err
+ v.walk(zn.Blocks, nil)
+ return v.b.Flush()
}
// WriteMeta encodes meta data as zmk.
-func (*zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
- v := newZmkVisitor(w)
- v.acceptMeta(m)
- length, err := v.b.Flush()
- return length, err
-}
-
-func (v *zmkVisitor) acceptMeta(m *meta.Meta) {
- for key, val := range m.Computed() {
- v.b.WriteStrings(key, ": ", string(val), "\n")
- }
-}
-
-// WriteBlocks writes the content of a block slice to the writer.
-func (*zmkEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) {
- v := newZmkVisitor(w)
- ast.Walk(&v, bs)
- length, err := v.b.Flush()
- return length, err
-}
-
-// zmkVisitor writes the abstract syntax tree to an io.Writer.
-type zmkVisitor struct {
- b encWriter
- textEnc TextEncoder
- prefix []byte
- inVerse bool
- inlinePos int
+func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
+ ew := newEncWriter(w)
+ ew.WriteMeta(m)
+ return ew.Flush()
+}
+
+// WriteSz encodes SZ represented zettel content.
+func (*zmkEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
+ v := newZmkVisitor(w)
+ zsx.WalkIt(&v, node, nil)
+ return v.b.Flush()
+}
+
+type zmkVisitor struct {
+ b encWriter
+ prefix []byte
}
func newZmkVisitor(w io.Writer) zmkVisitor { return zmkVisitor{b: newEncWriter(w)} }
-func (v *zmkVisitor) Visit(node ast.Node) ast.Visitor {
- switch n := node.(type) {
- case *ast.BlockSlice:
- v.visitBlockSlice(n)
- case *ast.InlineSlice:
- for i, in := range *n {
- v.inlinePos = i
- ast.Walk(v, in)
- }
- v.inlinePos = 0
- case *ast.VerbatimNode:
- v.visitVerbatim(n)
- case *ast.RegionNode:
- v.visitRegion(n)
- case *ast.HeadingNode:
- v.visitHeading(n)
- case *ast.HRuleNode:
- v.b.WriteString("---")
- v.visitAttributes(n.Attrs)
- case *ast.NestedListNode:
- v.visitNestedList(n)
- case *ast.DescriptionListNode:
- v.visitDescriptionList(n)
- case *ast.TableNode:
- v.visitTable(n)
- case *ast.TranscludeNode:
- v.b.WriteStrings("{{{", n.Ref.String(), "}}}") // FIXME n.Inlines
- v.visitAttributes(n.Attrs)
- case *ast.BLOBNode:
- v.visitBLOB(n)
- case *ast.TextNode:
- v.visitText(n)
- case *ast.BreakNode:
- v.visitBreak(n)
- case *ast.LinkNode:
- v.visitLink(n)
- case *ast.EmbedRefNode:
- v.visitEmbedRef(n)
- case *ast.EmbedBLOBNode:
- v.visitEmbedBLOB(n)
- case *ast.CiteNode:
- v.visitCite(n)
- case *ast.FootnoteNode:
- v.b.WriteString("[^")
- ast.Walk(v, &n.Inlines)
- _ = v.b.WriteByte(']')
- v.visitAttributes(n.Attrs)
- case *ast.MarkNode:
- v.visitMark(n)
- case *ast.FormatNode:
- v.visitFormat(n)
- case *ast.LiteralNode:
- v.visitLiteral(n)
- default:
- return v
- }
- return nil
-}
-
-func (v *zmkVisitor) visitBlockSlice(bs *ast.BlockSlice) {
- var lastWasParagraph bool
- for i, bn := range *bs {
- if i > 0 {
- v.b.WriteLn()
- if lastWasParagraph && !v.inVerse {
- if _, ok := bn.(*ast.ParaNode); ok {
- v.b.WriteLn()
- }
- }
- }
- ast.Walk(v, bn)
- _, lastWasParagraph = bn.(*ast.ParaNode)
- }
-}
-
-var mapVerbatimKind = map[ast.VerbatimKind]string{
- ast.VerbatimZettel: "@@@",
- ast.VerbatimComment: "%%%",
- ast.VerbatimHTML: "@@@", // Attribute is set to {="html"}
- ast.VerbatimCode: "```",
- ast.VerbatimEval: "~~~",
- ast.VerbatimMath: "$$$",
-}
-
-func (v *zmkVisitor) visitVerbatim(vn *ast.VerbatimNode) {
- kind, ok := mapVerbatimKind[vn.Kind]
- if !ok {
- panic(fmt.Sprintf("Unknown verbatim kind %d", vn.Kind))
- }
- attrs := vn.Attrs
- if vn.Kind == ast.VerbatimHTML {
- attrs = syntaxToHTML(attrs)
- }
-
- // TODO: scan cn.Lines to find embedded kind[0]s at beginning
- v.b.WriteString(kind)
- v.visitAttributes(attrs)
- v.b.WriteLn()
- _, _ = v.b.Write(vn.Content)
- v.b.WriteLn()
- v.b.WriteString(kind)
-}
-
-var mapRegionKind = map[ast.RegionKind]string{
- ast.RegionSpan: ":::",
- ast.RegionQuote: "<<<",
- ast.RegionVerse: "\"\"\"",
-}
-
-func (v *zmkVisitor) visitRegion(rn *ast.RegionNode) {
- // Scan rn.Blocks for embedded regions to adjust length of regionCode
- kind, ok := mapRegionKind[rn.Kind]
- if !ok {
- panic(fmt.Sprintf("Unknown region kind %d", rn.Kind))
- }
- v.b.WriteString(kind)
- v.visitAttributes(rn.Attrs)
- v.b.WriteLn()
- saveInVerse := v.inVerse
- v.inVerse = rn.Kind == ast.RegionVerse
- ast.Walk(v, &rn.Blocks)
- v.inVerse = saveInVerse
- v.b.WriteLn()
- v.b.WriteString(kind)
- if len(rn.Inlines) > 0 {
- v.b.WriteSpace()
- ast.Walk(v, &rn.Inlines)
- }
-}
-
-func (v *zmkVisitor) visitHeading(hn *ast.HeadingNode) {
- const headingSigns = "========= "
- v.b.WriteString(headingSigns[len(headingSigns)-hn.Level-3:])
- ast.Walk(v, &hn.Inlines)
- v.visitAttributes(hn.Attrs)
-}
-
-var mapNestedListKind = map[ast.NestedListKind]byte{
- ast.NestedListOrdered: '#',
- ast.NestedListUnordered: '*',
- ast.NestedListQuote: '>',
-}
-
-func (v *zmkVisitor) visitNestedList(ln *ast.NestedListNode) {
- v.prefix = append(v.prefix, mapNestedListKind[ln.Kind])
- for i, item := range ln.Items {
- if i > 0 {
- v.b.WriteLn()
- }
- _, _ = v.b.Write(v.prefix)
- v.b.WriteSpace()
- for j, in := range item {
- if j > 0 {
- v.b.WriteLn()
- if _, ok := in.(*ast.ParaNode); ok {
- v.writePrefixSpaces()
- }
- }
- ast.Walk(v, in)
+func (v *zmkVisitor) walk(node, alst *sx.Pair) { zsx.WalkIt(v, node, alst) }
+func (v *zmkVisitor) walkList(lst, alst *sx.Pair) { zsx.WalkItList(v, lst, 0, alst) }
+
+func (v *zmkVisitor) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymText:
+ v.writeText(zsx.GetText(node))
+ case zsx.SymSoft:
+ v.writeBreak(false)
+ case zsx.SymHard:
+ v.writeBreak(true)
+
+ case zsx.SymFormatEmph:
+ v.visitFormat(node, alst, "__")
+ case zsx.SymFormatStrong:
+ v.visitFormat(node, alst, "**")
+ case zsx.SymFormatInsert:
+ v.visitFormat(node, alst, ">>")
+ case zsx.SymFormatDelete:
+ v.visitFormat(node, alst, "~~")
+ case zsx.SymFormatSuper:
+ v.visitFormat(node, alst, "^^")
+ case zsx.SymFormatSub:
+ v.visitFormat(node, alst, ",,")
+ case zsx.SymFormatQuote:
+ v.visitFormat(node, alst, `""`)
+ case zsx.SymFormatMark:
+ v.visitFormat(node, alst, "##")
+ case zsx.SymFormatSpan:
+ v.visitFormat(node, alst, "::")
+
+ case zsx.SymLiteralCode:
+ _, attrs, content := zsx.GetLiteral(node)
+ v.writeLiteral('`', attrs, content)
+ case zsx.SymLiteralMath:
+ _, attrs, content := zsx.GetLiteral(node)
+ v.b.WriteStrings("$$", content, "$$")
+ v.writeAttributes(attrs)
+ case zsx.SymLiteralInput:
+ _, attrs, content := zsx.GetLiteral(node)
+ v.writeLiteral('\'', attrs, content)
+ case zsx.SymLiteralOutput:
+ _, attrs, content := zsx.GetLiteral(node)
+ v.writeLiteral('=', attrs, content)
+ case zsx.SymLiteralComment:
+ _, attrs, content := zsx.GetLiteral(node)
+ v.b.WriteString("%%")
+ v.writeAttributes(attrs)
+ v.b.WriteSpace()
+ v.b.WriteString(content)
+
+ case zsx.SymLink:
+ v.visitLink(node, alst)
+ case zsx.SymEmbed:
+ v.visitEmbedRef(node, alst)
+ case zsx.SymEndnote:
+ v.visitEndnote(node, alst)
+ case zsx.SymCite:
+ v.visitCite(node, alst)
+ case zsx.SymMark:
+ v.visitMark(node, alst)
+
+ case zsx.SymBlock:
+ v.visitBlock(node, alst)
+ case zsx.SymHeading:
+ v.visitHeading(node, alst)
+ case zsx.SymThematic:
+ attrs := zsx.GetThematic(node)
+ v.b.WriteString("---")
+ v.writeAttributes(attrs)
+
+ case zsx.SymListOrdered:
+ v.visitNestedList(node, alst, '#')
+ case zsx.SymListQuote:
+ v.visitNestedList(node, alst, '>')
+ case zsx.SymListUnordered:
+ v.visitNestedList(node, alst, '*')
+
+ case zsx.SymRegionBlock:
+ v.visitRegion(node, alst, ":::")
+ case zsx.SymRegionQuote:
+ v.visitRegion(node, alst, "<<<")
+ case zsx.SymRegionVerse:
+ v.visitRegion(node, alst, "\"\"\"")
+
+ case zsx.SymDescription:
+ v.visitDescription(node, alst)
+ case zsx.SymTable:
+ v.visitTable(node, alst)
+ case zsx.SymCell:
+ v.visitCell(node, alst)
+
+ case zsx.SymVerbatimCode:
+ v.visitVerbatim(node, "```")
+ case zsx.SymVerbatimComment:
+ v.visitVerbatim(node, "%%%")
+ case zsx.SymVerbatimEval:
+ v.visitVerbatim(node, "~~~")
+ case zsx.SymVerbatimHTML:
+ v.visitVerbatim(node, "@@@")
+ case zsx.SymVerbatimMath:
+ v.visitVerbatim(node, "$$$")
+ case zsx.SymVerbatimZettel:
+ v.visitVerbatim(node, "@@@")
+
+ case zsx.SymBLOB:
+ v.visitBLOB(node)
+ case zsx.SymTransclude:
+ v.visitTransclude(node, alst)
+ default:
+ return false
+ }
+ return true
+ }
+ return false
+}
+func (v *zmkVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+func (v *zmkVisitor) visitFormat(node *sx.Pair, alst *sx.Pair, delim string) {
+ _, attrs, inlines := zsx.GetFormat(node)
+ v.b.WriteString(delim)
+ v.walkList(inlines, alst)
+ v.b.WriteString(delim)
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) writeLiteral(code byte, attrs *sx.Pair, content string) {
+ v.b.WriteBytes(code, code)
+ v.writeEscaped(content, code)
+ v.b.WriteBytes(code, code)
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) visitLink(node *sx.Pair, alst *sx.Pair) {
+ attrs, ref, inlines := zsx.GetLink(node)
+ v.b.WriteString("[[")
+ if inlines != nil {
+ v.walkList(inlines, alst)
+ _ = v.b.WriteByte('|')
+ }
+ _ = sz.WriteReference(&v.b, ref)
+ v.b.WriteString("]]")
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) visitEmbedRef(node *sx.Pair, alst *sx.Pair) {
+ attrs, ref, _, inlines := zsx.GetEmbed(node)
+ v.b.WriteString("{{")
+ if inlines != nil {
+ v.walkList(inlines, alst)
+ _ = v.b.WriteByte('|')
+ }
+ _ = sz.WriteReference(&v.b, ref)
+ v.b.WriteString("}}")
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) visitEndnote(node *sx.Pair, alst *sx.Pair) {
+ attrs, inlines := zsx.GetEndnote(node)
+ v.b.WriteString("[^")
+ v.walkList(inlines, alst)
+ _ = v.b.WriteByte(']')
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) visitCite(node *sx.Pair, alst *sx.Pair) {
+ attrs, key, inlines := zsx.GetCite(node)
+ v.b.WriteStrings("[@", key)
+ if inlines != nil {
+ v.b.WriteSpace()
+ v.walkList(inlines, alst)
+ }
+ _ = v.b.WriteByte(']')
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) visitMark(node *sx.Pair, alst *sx.Pair) {
+ mark, _, _, inlines := zsx.GetMark(node)
+ v.b.WriteStrings("[!", mark)
+ if inlines != nil {
+ _ = v.b.WriteByte('|')
+ v.walkList(inlines, alst)
+ }
+ _ = v.b.WriteByte(']')
+}
+
+func (v *zmkVisitor) visitBlock(node *sx.Pair, alst *sx.Pair) {
+ blocks := zsx.GetBlock(node)
+ lastWasParagraph := false
+ first := true
+ for bn := range blocks.Pairs() {
+ blk := bn.Head()
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ if lastWasParagraph && alst.Assoc(zsx.SymRegionVerse) == nil {
+ if zsx.SymPara.IsEqual(blk.Car()) {
+ v.b.WriteLn()
+ }
+ }
+ }
+ v.walk(blk, alst)
+ lastWasParagraph = zsx.SymPara.IsEqual(blk.Car())
+ }
+}
+
+func (v *zmkVisitor) visitHeading(node *sx.Pair, alst *sx.Pair) {
+ level, attrs, inlines, _, _ := zsx.GetHeading(node)
+ const headingSigns = "========= "
+ v.b.WriteString(headingSigns[len(headingSigns)-level-3:])
+ v.walkList(inlines, alst)
+ v.writeAttributes(attrs)
+}
+
+func (v *zmkVisitor) visitNestedList(node *sx.Pair, alst *sx.Pair, code byte) {
+ _, _, items := zsx.GetList(node)
+ v.prefix = append(v.prefix, code)
+
+ first := true
+ for itm := range items.Pairs() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ _, _ = v.b.Write(v.prefix)
+ v.b.WriteSpace()
+ item := zsx.GetBlock(itm.Head())
+ second := false
+ for inn := range item.Pairs() {
+ inl := inn.Head()
+ if second {
+ v.b.WriteLn()
+ if zsx.SymPara.IsEqual(inl.Car()) {
+ v.writePrefixSpaces()
+ }
+ } else {
+ second = true
+ }
+ v.walk(inl, alst)
}
}
v.prefix = v.prefix[:len(v.prefix)-1]
}
-func (v *zmkVisitor) writePrefixSpaces() {
- if prefixLen := len(v.prefix); prefixLen > 0 {
- for i := 0; i <= prefixLen; i++ {
- v.b.WriteSpace()
- }
- }
-}
-
-func (v *zmkVisitor) visitDescriptionList(dn *ast.DescriptionListNode) {
- for i, descr := range dn.Descriptions {
- if i > 0 {
- v.b.WriteLn()
- }
- v.b.WriteString("; ")
- ast.Walk(v, &descr.Term)
-
- for _, b := range descr.Descriptions {
- v.b.WriteString("\n: ")
- for jj, dn := range b {
- if jj > 0 {
- v.b.WriteString("\n\n ")
- }
- ast.Walk(v, dn)
- }
- }
- }
-}
-
-var alignCode = map[ast.Alignment]string{
- ast.AlignDefault: "",
- ast.AlignLeft: "<",
- ast.AlignCenter: ":",
- ast.AlignRight: ">",
-}
-
-func (v *zmkVisitor) visitTable(tn *ast.TableNode) {
- if header := tn.Header; len(header) > 0 {
- v.writeTableHeader(header, tn.Align)
- v.b.WriteLn()
- }
- for i, row := range tn.Rows {
- if i > 0 {
- v.b.WriteLn()
- }
- v.writeTableRow(row, tn.Align)
- }
-}
-
-func (v *zmkVisitor) writeTableHeader(header ast.TableRow, align []ast.Alignment) {
- for pos, cell := range header {
- v.b.WriteString("|=")
- colAlign := align[pos]
- if cell.Align != colAlign {
- v.b.WriteString(alignCode[cell.Align])
- }
- ast.Walk(v, &cell.Inlines)
- if colAlign != ast.AlignDefault {
- v.b.WriteString(alignCode[colAlign])
- }
- }
-}
-
-func (v *zmkVisitor) writeTableRow(row ast.TableRow, align []ast.Alignment) {
- for pos, cell := range row {
- _ = v.b.WriteByte('|')
- if cell.Align != align[pos] {
- v.b.WriteString(alignCode[cell.Align])
- }
- ast.Walk(v, &cell.Inlines)
- }
-}
-
-func (v *zmkVisitor) visitBLOB(bn *ast.BLOBNode) {
- if bn.Syntax == meta.ValueSyntaxSVG {
- v.b.WriteStrings("@@@", bn.Syntax, "\n")
- _, _ = v.b.Write(bn.Blob)
- v.b.WriteString("\n@@@\n")
- return
- }
- var sb strings.Builder
- _, _ = v.textEnc.WriteInlines(&sb, &bn.Description)
- v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", bn.Syntax, "'.")
+func (v *zmkVisitor) visitRegion(node *sx.Pair, alst *sx.Pair, delim string) {
+ sym, attrs, blocks, inlines := zsx.GetRegion(node)
+ //TODO: Scan rn.Blocks for embedded regions to adjust length of regionCode
+ v.b.WriteString(delim)
+ v.writeAttributes(attrs)
+ v.b.WriteLn()
+ if zsx.SymRegionVerse.IsEqualSymbol(sym) {
+ alst = alst.Cons(sx.Cons(zsx.SymRegionVerse, sx.Nil()))
+ }
+ v.walk(zsx.MakeBlockList(blocks), alst)
+ v.b.WriteLn()
+ v.b.WriteString(delim)
+ if inlines != nil {
+ v.b.WriteSpace()
+ v.walkList(inlines, alst)
+ }
+}
+
+func (v *zmkVisitor) visitDescription(node *sx.Pair, alst *sx.Pair) {
+ _, termVals := zsx.GetDescription(node)
+ first := true
+ for n := termVals; n != nil; n = n.Tail() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.b.WriteString("; ")
+ if term := n.Head(); term != nil {
+ v.walkList(term, alst)
+ }
+ n = n.Tail()
+ if n == nil {
+ break
+ }
+ for bns := range zsx.GetBlock(n.Head()).Pairs() {
+ v.b.WriteString("\n: ")
+ second := false
+ for pn := range zsx.GetBlock(bns.Head()).Pairs() {
+ if second {
+ v.b.WriteString("\n\n ")
+ } else {
+ second = true
+ }
+ v.walk(pn.Head(), alst)
+ }
+ }
+ }
+}
+
+func (v *zmkVisitor) visitTable(node *sx.Pair, alst *sx.Pair) {
+ _, headerRow, rows := zsx.GetTable(node)
+ if headerRow != nil {
+ v.writeRow(headerRow, alst, "|=")
+ v.b.WriteLn()
+ }
+ first := true
+ for row := range rows.Pairs() {
+ if first {
+ first = false
+ } else {
+ v.b.WriteLn()
+ }
+ v.writeRow(row.Head(), alst, "|")
+ }
+}
+func (v *zmkVisitor) writeRow(row *sx.Pair, alst *sx.Pair, delim string) {
+ for n := range row.Pairs() {
+ v.b.WriteString(delim)
+ v.walk(n.Head(), alst)
+ }
+}
+func (v *zmkVisitor) visitCell(node *sx.Pair, alst *sx.Pair) {
+ attrs, inlines := zsx.GetCell(node)
+ align := ""
+ if alignPair := attrs.Assoc(zsx.SymAttrAlign); alignPair != nil {
+ if alignValue := alignPair.Cdr(); zsx.AttrAlignCenter.IsEqual(alignValue) {
+ align = ":"
+ } else if zsx.AttrAlignLeft.IsEqual(alignValue) {
+ align = "<"
+ } else if zsx.AttrAlignRight.IsEqual(alignValue) {
+ align = ">"
+ }
+ }
+ v.b.WriteString(align)
+ v.walkList(inlines, alst)
+}
+
+func (v *zmkVisitor) visitVerbatim(node *sx.Pair, delim string) {
+ sym, attrs, content := zsx.GetVerbatim(node)
+
+ if zsx.SymVerbatimHTML.IsEqualSymbol(sym) {
+ attrs = attrs.RemoveAssoc(sx.MakeString(meta.KeySyntax))
+ attrs = attrs.Cons(sx.Cons(sx.MakeString(""), sx.MakeString(meta.ValueSyntaxHTML)))
+ }
+
+ // TODO: scan cn.Lines to find embedded kind[0]s at beginning
+ v.b.WriteString(delim)
+ v.writeAttributes(attrs)
+ v.b.WriteLn()
+ v.b.WriteString(content)
+ v.b.WriteLn()
+ v.b.WriteString(delim)
+}
+
+func (v *zmkVisitor) visitBLOB(node *sx.Pair) {
+ _, syntax, content, inlines := zsx.GetBLOBuncode(node)
+ if syntax == meta.ValueSyntaxSVG {
+ v.b.WriteString("@@@")
+ v.b.WriteStrings(syntax, "\n", content, "\n@@@\n")
+ return
+ }
+ var sb strings.Builder
+ var textEnc TextEncoder
+ _ = textEnc.WriteSz(&sb, zsx.MakeInlineList(inlines))
+ v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", syntax, "'.")
+}
+
+func (v *zmkVisitor) visitTransclude(node *sx.Pair, alst *sx.Pair) {
+ attrs, ref, inlines := zsx.GetTransclusion(node)
+ v.b.WriteString("{{{")
+ if inlines != nil {
+ v.walkList(inlines, alst)
+ _ = v.b.WriteByte('|')
+ }
+ _ = sz.WriteReference(&v.b, ref)
+ v.b.WriteString("}}}")
+ v.writeAttributes(attrs)
}
var escapeSeqs = set.New(
"\\", "__", "**", "~~", "^^", ",,", ">>", `""`, "::", "''", "``", "++", "==", "##",
)
-func (v *zmkVisitor) visitText(tn *ast.TextNode) {
+func (v *zmkVisitor) writeText(text string) {
last := 0
- for i := 0; i < len(tn.Text); i++ {
- if b := tn.Text[i]; b == '\\' {
- v.b.WriteString(tn.Text[last:i])
+ for i := 0; i < len(text); i++ {
+ if b := text[i]; b == '\\' {
+ v.b.WriteString(text[last:i])
v.b.WriteBytes('\\', b)
last = i + 1
continue
}
- if i < len(tn.Text)-1 {
- s := tn.Text[i : i+2]
+ if i < len(text)-1 {
+ s := text[i : i+2]
if escapeSeqs.Contains(s) {
- v.b.WriteString(tn.Text[last:i])
+ v.b.WriteString(text[last:i])
for j := range len(s) {
v.b.WriteBytes('\\', s[j])
}
i++
last = i + 1
continue
}
}
}
- v.b.WriteString(tn.Text[last:])
+ v.b.WriteString(text[last:])
}
-
-func (v *zmkVisitor) visitBreak(bn *ast.BreakNode) {
- if bn.Hard {
+func (v *zmkVisitor) writeBreak(isHard bool) {
+ if isHard {
v.b.WriteString("\\\n")
} else {
v.b.WriteLn()
}
v.writePrefixSpaces()
}
-func (v *zmkVisitor) visitLink(ln *ast.LinkNode) {
- v.b.WriteString("[[")
- if len(ln.Inlines) > 0 {
- ast.Walk(v, &ln.Inlines)
- _ = v.b.WriteByte('|')
- }
- if ln.Ref.State == ast.RefStateBased {
- _ = v.b.WriteByte('/')
- }
- v.b.WriteStrings(ln.Ref.String(), "]]")
-}
-
-func (v *zmkVisitor) visitEmbedRef(en *ast.EmbedRefNode) {
- v.b.WriteString("{{")
- if len(en.Inlines) > 0 {
- ast.Walk(v, &en.Inlines)
- _ = v.b.WriteByte('|')
- }
- v.b.WriteStrings(en.Ref.String(), "}}")
-}
-
-func (v *zmkVisitor) visitEmbedBLOB(en *ast.EmbedBLOBNode) {
- if en.Syntax == meta.ValueSyntaxSVG {
- v.b.WriteString("@@")
- _, _ = v.b.Write(en.Blob)
- v.b.WriteStrings("@@{=", en.Syntax, "}")
- return
- }
- v.b.WriteString("{{TODO: display inline BLOB}}")
-}
-
-func (v *zmkVisitor) visitCite(cn *ast.CiteNode) {
- v.b.WriteStrings("[@", cn.Key)
- if len(cn.Inlines) > 0 {
- v.b.WriteSpace()
- ast.Walk(v, &cn.Inlines)
- }
- _ = v.b.WriteByte(']')
- v.visitAttributes(cn.Attrs)
-}
-
-func (v *zmkVisitor) visitMark(mn *ast.MarkNode) {
- v.b.WriteStrings("[!", mn.Mark)
- if len(mn.Inlines) > 0 {
- _ = v.b.WriteByte('|')
- ast.Walk(v, &mn.Inlines)
- }
- _ = v.b.WriteByte(']')
-
-}
-
-var mapFormatKind = map[ast.FormatKind][]byte{
- ast.FormatEmph: []byte("__"),
- ast.FormatStrong: []byte("**"),
- ast.FormatInsert: []byte(">>"),
- ast.FormatDelete: []byte("~~"),
- ast.FormatSuper: []byte("^^"),
- ast.FormatSub: []byte(",,"),
- ast.FormatQuote: []byte(`""`),
- ast.FormatMark: []byte("##"),
- ast.FormatSpan: []byte("::"),
-}
-
-func (v *zmkVisitor) visitFormat(fn *ast.FormatNode) {
- kind, ok := mapFormatKind[fn.Kind]
- if !ok {
- panic(fmt.Sprintf("Unknown format kind %d", fn.Kind))
- }
- _, _ = v.b.Write(kind)
- ast.Walk(v, &fn.Inlines)
- _, _ = v.b.Write(kind)
- v.visitAttributes(fn.Attrs)
-}
-
-func (v *zmkVisitor) visitLiteral(ln *ast.LiteralNode) {
- switch ln.Kind {
- case ast.LiteralCode:
- v.writeLiteral('`', ln.Attrs, ln.Content)
- case ast.LiteralMath:
- v.b.WriteStrings("$$", string(ln.Content), "$$")
- v.visitAttributes(ln.Attrs)
- case ast.LiteralInput:
- v.writeLiteral('\'', ln.Attrs, ln.Content)
- case ast.LiteralOutput:
- v.writeLiteral('=', ln.Attrs, ln.Content)
- case ast.LiteralComment:
- v.b.WriteString("%%")
- v.visitAttributes(ln.Attrs)
- v.b.WriteSpace()
- _, _ = v.b.Write(ln.Content)
- default:
- panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind))
- }
-}
-
-func (v *zmkVisitor) writeLiteral(code byte, a zsx.Attributes, content []byte) {
- v.b.WriteBytes(code, code)
- v.writeEscaped(string(content), code)
- v.b.WriteBytes(code, code)
- v.visitAttributes(a)
-}
-
-// visitAttributes write HTML attributes
-func (v *zmkVisitor) visitAttributes(a zsx.Attributes) {
+func (v *zmkVisitor) writeAttributes(attrs *sx.Pair) {
+ a := zsx.GetAttributes(attrs)
if a.IsEmpty() {
return
}
_ = v.b.WriteByte('{')
for i, k := range a.Keys() {
if i > 0 {
v.b.WriteSpace()
}
- if k == "-" {
+ if k == zsx.DefaultAttribute {
_ = v.b.WriteByte('-')
continue
}
v.b.WriteString(k)
if vl := a[k]; len(vl) > 0 {
- v.b.WriteStrings("=\"", vl)
+ v.b.WriteString("=\"")
+ v.writeEscaped(vl, '"')
_ = v.b.WriteByte('"')
}
}
_ = v.b.WriteByte('}')
}
@@ -498,8 +513,12 @@
}
}
v.b.WriteString(s[last:])
}
-func syntaxToHTML(a zsx.Attributes) zsx.Attributes {
- return a.Clone().Set("", meta.ValueSyntaxHTML).Remove(meta.KeySyntax)
+func (v *zmkVisitor) writePrefixSpaces() {
+ if prefixLen := len(v.prefix); prefixLen > 0 {
+ for i := 0; i <= prefixLen; i++ {
+ v.b.WriteSpace()
+ }
+ }
}
ADDED internal/evaluator/block.go
Index: internal/evaluator/block.go
==================================================================
--- /dev/null
+++ internal/evaluator/block.go
@@ -0,0 +1,138 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021-present Detlef Stern
+//
+// This file is part of Zettelstore.
+//
+// Zettelstore 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: 2021-present Detlef Stern
+//-----------------------------------------------------------------------------
+
+// Package evaluator interprets and evaluates the AST.
+package evaluator
+
+import (
+ "errors"
+
+ "t73f.de/r/sx"
+ "t73f.de/r/zsc/domain/id"
+ "t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
+ "t73f.de/r/zsx"
+
+ "zettelstore.de/z/internal/box"
+ "zettelstore.de/z/internal/parser"
+ "zettelstore.de/z/internal/query"
+ "zettelstore.de/z/internal/zettel"
+)
+
+func (e *evaluator) evalVerbatimEval(node *sx.Pair) *sx.Pair {
+ _, attrs, content := zsx.GetVerbatim(node)
+ if p := attrs.Assoc(sx.MakeString("")); p != nil {
+ if s, isString := sx.GetString(p.Cdr()); isString && s.GetValue() == meta.ValueSyntaxDraw {
+ return parser.ParseDrawBlock(attrs, []byte(content))
+ }
+ }
+ return node
+}
+
+func (e *evaluator) evalVerbatimZettel(vn *sx.Pair) *sx.Pair {
+ _, attrs, content := zsx.GetVerbatim(vn)
+ m := meta.New(id.Invalid)
+ m.Set(meta.KeySyntax, getSyntax(attrs, meta.ValueSyntaxText))
+ zettel := zettel.Zettel{
+ Meta: m,
+ Content: zettel.NewContent([]byte(content)),
+ }
+ e.transcludeCount++
+ zn := e.evaluateEmbeddedZettel(zettel)
+ return splicedBlocks(zn.Blocks)
+}
+
+func (e *evaluator) evalTransclusion(tn *sx.Pair) *sx.Pair {
+ attrs, ref, text := zsx.GetTransclusion(tn)
+ refSym, refVal := zsx.GetReference(ref)
+
+ // To prevent e.embedCount from counting
+ if errText := e.checkMaxTransclusions(ref); errText != nil {
+ return makeBlock(errText)
+ }
+ if !sz.SymRefStateZettel.IsEqualSymbol(refSym) {
+ switch refSym {
+ case zsx.SymRefStateInvalid, sz.SymRefStateBroken:
+ e.transcludeCount++
+ return makeBlock(createInlineErrorText(ref, "Invalid or broken transclusion reference"))
+ case zsx.SymRefStateSelf:
+ e.transcludeCount++
+ return makeBlock(createInlineErrorText(ref, "Self transclusion reference"))
+ case sz.SymRefStateFound, zsx.SymRefStateExternal:
+ return tn
+ case zsx.SymRefStateHosted, sz.SymRefStateBased:
+ return makeBlock(e.evalEmbed(zsx.MakeEmbed(attrs, ref, "", text)))
+ case sz.SymRefStateQuery:
+ e.transcludeCount++
+ return e.evalQueryTransclusion(refVal)
+ default:
+ return makeBlock(createInlineErrorText(ref, "Illegal reference symvol "+refSym.GetValue()))
+ }
+ }
+
+ zid := mustParseZid(ref, refVal)
+
+ cost, ok := e.costMap[zid]
+ zn := cost.zn
+ if zn == e.marker {
+ e.transcludeCount++
+ return makeBlock(createInlineErrorText(ref, "Recursive transclusion"))
+ }
+ if !ok {
+ zettel, err1 := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
+ if err1 != nil {
+ if errors.Is(err1, &box.ErrNotAllowed{}) {
+ return nil
+ }
+ e.transcludeCount++
+ return makeBlock(createInlineErrorText(ref, "Unable to get zettel"))
+ }
+ setMetadataFromAttributes(zettel.Meta, attrs)
+ ec := e.transcludeCount
+ e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec}
+ zn = e.evaluateEmbeddedZettel(zettel)
+ e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec}
+ e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first.
+ }
+ e.transcludeCount++
+ if ec := cost.ec; ec > 0 {
+ e.transcludeCount += cost.ec
+ }
+ return splicedBlocks(zn.Blocks)
+}
+
+func (e *evaluator) evalQueryTransclusion(expr string) *sx.Pair {
+ q := query.Parse(expr)
+ ml, err := e.port.QueryMeta(e.ctx, q)
+ if err != nil {
+ if errors.Is(err, &box.ErrNotAllowed{}) {
+ return nil
+ }
+ return makeBlock(createInlineErrorText(nil, "Unable to search zettel"))
+ }
+ result, _ := QueryAction(e.ctx, q, ml)
+ if result != nil {
+ result = mustPair(zsx.Walk(e, result, nil))
+ }
+ return result
+}
+
+func makeBlock(inl *sx.Pair) *sx.Pair { return zsx.MakePara(inl) }
+
+func splicedBlocks(block *sx.Pair) *sx.Pair {
+ blocks := zsx.GetBlock(block)
+ if blocks.Tail() == nil {
+ return blocks.Head()
+ }
+ return blocks.Cons(zsx.SymSpecialSplice)
+}
Index: internal/evaluator/evaluator.go
==================================================================
--- internal/evaluator/evaluator.go
+++ internal/evaluator/evaluator.go
@@ -13,27 +13,21 @@
// Package evaluator interprets and evaluates the AST.
package evaluator
import (
- "bytes"
"context"
- "errors"
"fmt"
- "path"
- "slices"
"strconv"
- "strings"
- "t73f.de/r/sx/sxbuiltins"
- "t73f.de/r/sx/sxreader"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
- "zettelstore.de/z/internal/box"
"zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/parser"
"zettelstore.de/z/internal/query"
"zettelstore.de/z/internal/zettel"
)
@@ -44,581 +38,150 @@
QueryMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error)
}
// EvaluateZettel evaluates the given zettel in the given context, with the
// given ports, and the given environment.
-func EvaluateZettel(ctx context.Context, port Port, rtConfig config.Config, zn *ast.ZettelNode) {
+func EvaluateZettel(ctx context.Context, port Port, rtConfig config.Config, zn *ast.Zettel) {
switch zn.Syntax {
case meta.ValueSyntaxNone:
// AST is empty, evaluate to a description list of metadata.
- zn.BlocksAST = evaluateMetadata(zn.Meta)
+ zn.Blocks = evaluateMetadata(zn.Meta)
case meta.ValueSyntaxSxn:
- zn.BlocksAST = evaluateSxn(zn.BlocksAST)
+ zn.Blocks = evaluateSxn(zn.Blocks)
default:
- EvaluateBlock(ctx, port, rtConfig, &zn.BlocksAST)
- }
-}
-
-func evaluateSxn(bs ast.BlockSlice) ast.BlockSlice {
- // Check for structure made in parser/plain/plain.go:parseSxnBlocks
- if len(bs) == 1 {
- // If len(bs) > 1 --> an error was found during parsing
- if vn, isVerbatim := bs[0].(*ast.VerbatimNode); isVerbatim && vn.Kind == ast.VerbatimCode {
- if classAttr, hasClass := vn.Attrs.Get(""); hasClass && classAttr == meta.ValueSyntaxSxn {
- rd := sxreader.MakeReader(bytes.NewReader(vn.Content))
- if objs, err := rd.ReadAll(); err == nil {
- result := make(ast.BlockSlice, len(objs))
- for i, obj := range objs {
- var buf bytes.Buffer
- _, _ = sxbuiltins.Print(&buf, obj)
- result[i] = &ast.VerbatimNode{
- Kind: ast.VerbatimCode,
- Attrs: zsx.Attributes{"": classAttr},
- Content: buf.Bytes(),
- }
- }
- return result
- }
- }
- }
- }
- return bs
+ zn.Blocks = EvaluateBlock(ctx, port, rtConfig, zn.Blocks)
+ }
}
// EvaluateBlock evaluates the given block list in the given context, with
// the given ports, and the given environment.
-func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, bns *ast.BlockSlice) {
+func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, block *sx.Pair) *sx.Pair {
e := evaluator{
ctx: ctx,
port: port,
rtConfig: rtConfig,
transcludeMax: rtConfig.GetMaxTransclusions(),
transcludeCount: 0,
costMap: map[id.Zid]transcludeCost{},
- embedMap: map[string]ast.InlineSlice{},
- marker: &ast.ZettelNode{},
+ embedMap: map[string]*sx.Pair{},
+ marker: &ast.Zettel{},
}
- ast.Walk(&e, bns)
- parser.Clean(bns, true)
+ return mustPair(zsx.Walk(&e, block, nil))
}
type evaluator struct {
ctx context.Context
port Port
rtConfig config.Config
transcludeMax int
transcludeCount int
costMap map[id.Zid]transcludeCost
- marker *ast.ZettelNode
- embedMap map[string]ast.InlineSlice
-}
-
-type transcludeCost struct {
- zn *ast.ZettelNode
- ec int
-}
-
-func (e *evaluator) Visit(node ast.Node) ast.Visitor {
- switch n := node.(type) {
- case *ast.BlockSlice:
- e.visitBlockSlice(n)
- case *ast.InlineSlice:
- e.visitInlineSlice(n)
- default:
- return e
- }
- return nil
-}
-
-func (e *evaluator) visitBlockSlice(bs *ast.BlockSlice) {
- for i := 0; i < len(*bs); i++ {
- bn := (*bs)[i]
- ast.Walk(e, bn)
- switch n := bn.(type) {
- case *ast.VerbatimNode:
- i += transcludeNode(bs, i, e.evalVerbatimNode(n))
- case *ast.TranscludeNode:
- i += transcludeNode(bs, i, e.evalTransclusionNode(n))
- }
- }
-}
-
-func transcludeNode(bln *ast.BlockSlice, i int, bn ast.BlockNode) int {
- if ln, ok := bn.(*ast.BlockSlice); ok {
- *bln = replaceWithBlockNodes(*bln, i, *ln)
- return len(*ln) - 1
- }
- if bn == nil {
- (*bln) = (*bln)[:i+copy((*bln)[i:], (*bln)[i+1:])]
- return -1
- }
- (*bln)[i] = bn
- return 0
-}
-
-func replaceWithBlockNodes(bns []ast.BlockNode, i int, replaceBns []ast.BlockNode) []ast.BlockNode {
- if len(replaceBns) == 1 {
- bns[i] = replaceBns[0]
- return bns
- }
- newIns := make([]ast.BlockNode, 0, len(bns)+len(replaceBns)-1)
- if i > 0 {
- newIns = append(newIns, bns[:i]...)
- }
- if len(replaceBns) > 0 {
- newIns = append(newIns, replaceBns...)
- }
- if i+1 < len(bns) {
- newIns = append(newIns, bns[i+1:]...)
- }
- return newIns
-}
-
-func (e *evaluator) evalVerbatimNode(vn *ast.VerbatimNode) ast.BlockNode {
- switch vn.Kind {
- case ast.VerbatimZettel:
- return e.evalVerbatimZettel(vn)
- case ast.VerbatimEval:
- if syntax, found := vn.Attrs.Get(""); found && syntax == meta.ValueSyntaxDraw {
- return parser.ParseDrawBlock(vn)
- }
- }
- return vn
-}
-
-func (e *evaluator) evalVerbatimZettel(vn *ast.VerbatimNode) ast.BlockNode {
- m := meta.New(id.Invalid)
- m.Set(meta.KeySyntax, getSyntax(vn.Attrs, meta.ValueSyntaxText))
- zettel := zettel.Zettel{
- Meta: m,
- Content: zettel.NewContent(vn.Content),
- }
- e.transcludeCount++
- zn := e.evaluateEmbeddedZettel(zettel)
- return &zn.BlocksAST
-}
-
-func getSyntax(a zsx.Attributes, defSyntax meta.Value) meta.Value {
- if a != nil {
- if val, ok := a.Get(meta.KeySyntax); ok {
- return meta.Value(val)
- }
- if val, ok := a.Get(""); ok {
- return meta.Value(val)
+ marker *ast.Zettel
+ embedMap map[string]*sx.Pair
+}
+
+type transcludeCost struct {
+ zn *ast.Zettel
+ ec int
+}
+
+func (e *evaluator) VisitBefore(_ *sx.Pair, _ *sx.Pair) (sx.Object, bool) {
+ return sx.Nil(), false
+}
+func (e *evaluator) VisitAfter(node *sx.Pair, _ *sx.Pair) sx.Object {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymLink:
+ return e.evalLink(node)
+ case zsx.SymEmbed:
+ return e.evalEmbed(node)
+ case zsx.SymVerbatimEval:
+ return e.evalVerbatimEval(node)
+ case zsx.SymTransclude:
+ return e.evalTransclusion(node)
+ case zsx.SymVerbatimZettel:
+ return e.evalVerbatimZettel(node)
+ }
+ }
+ return node
+}
+
+func (e *evaluator) evaluateEmbeddedZettel(zettel zettel.Zettel) *ast.Zettel {
+ zn := parser.ParseZettel(e.ctx, zettel, string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), e.rtConfig)
+ parser.Clean(zn.Blocks)
+ zn.Blocks = mustPair(zsx.Walk(e, zn.Blocks, nil))
+ return zn
+}
+
+func setMetadataFromAttributes(m *meta.Meta, attrs *sx.Pair) {
+ for obj := range attrs.Values() {
+ if pair, isPair := sx.GetPair(obj); isPair {
+ if key, isKey := sx.GetString(pair.Car()); isKey && meta.KeyIsValid(key.GetValue()) {
+ if val, isVal := sx.GetString(pair.Cdr()); isVal {
+ m.Set(key.GetValue(), meta.Value(val.GetValue()))
+ }
+ }
+ }
+ }
+}
+
+func mustPair(obj sx.Object) *sx.Pair {
+ p, isPair := sx.GetPair(obj)
+ if !isPair {
+ panic(fmt.Sprintf("not a pair after evaluate: %T/%v", obj, obj))
+ }
+ return p
+}
+
+func mustParseZid(ref *sx.Pair, refVal string) id.Zid {
+ baseVal, _ := sz.SplitFragment(refVal)
+ zid, err := id.Parse(baseVal)
+ if err == nil {
+ return zid
+ }
+ refState, _ := zsx.GetReference(ref)
+ panic(fmt.Sprintf("%v: %q (state %v) -> %v", err, refVal, refState, ref))
+}
+
+func getSyntax(attrs *sx.Pair, defSyntax meta.Value) meta.Value {
+ for a := range attrs.Values() {
+ if pair, isPair := sx.GetPair(a); isPair {
+ car := pair.Car()
+ if car.IsEqual(sx.MakeString(meta.KeySyntax)) || car.IsEqual(sx.MakeString("")) {
+ if val, isString := sx.GetString(pair.Cdr()); isString {
+ return meta.Value(val.GetValue())
+ }
+ }
}
}
return defSyntax
}
-func (e *evaluator) evalTransclusionNode(tn *ast.TranscludeNode) ast.BlockNode {
- ref := tn.Ref
-
- // To prevent e.embedCount from counting
- if errText := e.checkMaxTransclusions(ref); errText != nil {
- return makeBlockNode(errText)
- }
- switch ref.State {
- case ast.RefStateZettel:
- // Only zettel references will be evaluated.
- case ast.RefStateInvalid, ast.RefStateBroken:
- e.transcludeCount++
- return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference"))
- case ast.RefStateSelf:
- e.transcludeCount++
- return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference"))
- case ast.RefStateFound, ast.RefStateExternal:
- return tn
- case ast.RefStateHosted, ast.RefStateBased:
- if n := createEmbeddedNodeLocal(ref); n != nil {
- n.Attrs = tn.Attrs
- return makeBlockNode(n)
- }
- return tn
- case ast.RefStateQuery:
- e.transcludeCount++
- return e.evalQueryTransclusion(tn.Ref.Value)
- default:
- return makeBlockNode(createInlineErrorText(ref, "Illegal", "block", "state", strconv.Itoa(int(ref.State))))
- }
-
- zid, err := id.Parse(ref.URL.Path)
- if err != nil {
- panic(err)
- }
-
- cost, ok := e.costMap[zid]
- zn := cost.zn
- if zn == e.marker {
- e.transcludeCount++
- return makeBlockNode(createInlineErrorText(ref, "Recursive", "transclusion"))
- }
- if !ok {
- zettel, err1 := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
- if err1 != nil {
- if errors.Is(err1, &box.ErrNotAllowed{}) {
- return nil
- }
- e.transcludeCount++
- return makeBlockNode(createInlineErrorText(ref, "Unable", "to", "get", "zettel"))
- }
- setMetadataFromAttributes(zettel.Meta, tn.Attrs)
- ec := e.transcludeCount
- e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec}
- zn = e.evaluateEmbeddedZettel(zettel)
- e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec}
- e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first.
- }
- e.transcludeCount++
- if ec := cost.ec; ec > 0 {
- e.transcludeCount += cost.ec
- }
- return &zn.BlocksAST
-}
-
-func (e *evaluator) evalQueryTransclusion(expr string) ast.BlockNode {
- q := query.Parse(expr)
- ml, err := e.port.QueryMeta(e.ctx, q)
- if err != nil {
- if errors.Is(err, &box.ErrNotAllowed{}) {
- return nil
- }
- return makeBlockNode(createInlineErrorText(nil, "Unable", "to", "search", "zettel"))
- }
- result, _ := QueryAction(e.ctx, q, ml)
- if result != nil {
- ast.Walk(e, result)
- }
- return result
-}
-
-func (e *evaluator) checkMaxTransclusions(ref *ast.Reference) ast.InlineNode {
- if maxTrans := e.transcludeMax; e.transcludeCount > maxTrans {
- e.transcludeCount = maxTrans + 1
- return createInlineErrorText(ref,
- "Too", "many", "transclusions", "(must", "be", "at", "most", strconv.Itoa(maxTrans)+",",
- "see", "runtime", "configuration", "key", "max-transclusions)")
- }
- return nil
-}
-
-func makeBlockNode(in ast.InlineNode) ast.BlockNode { return ast.CreateParaNode(in) }
-
-func setMetadataFromAttributes(m *meta.Meta, a zsx.Attributes) {
- for aKey, aVal := range a {
- if meta.KeyIsValid(aKey) {
- m.Set(aKey, meta.Value(aVal))
- }
- }
-}
-
-func (e *evaluator) visitInlineSlice(is *ast.InlineSlice) {
- for i := 0; i < len(*is); i++ {
- in := (*is)[i]
- ast.Walk(e, in)
- switch n := in.(type) {
- case *ast.LinkNode:
- (*is)[i] = e.evalLinkNode(n)
- case *ast.EmbedRefNode:
- i += embedNode(is, i, e.evalEmbedRefNode(n))
- }
- }
-}
-
-func embedNode(is *ast.InlineSlice, i int, in ast.InlineNode) int {
- if ln, ok := in.(*ast.InlineSlice); ok {
- *is = replaceWithInlineNodes(*is, i, *ln)
- return len(*ln) - 1
- }
- if in == nil {
- (*is) = (*is)[:i+copy((*is)[i:], (*is)[i+1:])]
- return -1
- }
- (*is)[i] = in
- return 0
-}
-
-func replaceWithInlineNodes(ins ast.InlineSlice, i int, replaceIns ast.InlineSlice) ast.InlineSlice {
- if len(replaceIns) == 1 {
- ins[i] = replaceIns[0]
- return ins
- }
- newIns := make(ast.InlineSlice, 0, len(ins)+len(replaceIns)-1)
- if i > 0 {
- newIns = append(newIns, ins[:i]...)
- }
- if len(replaceIns) > 0 {
- newIns = append(newIns, replaceIns...)
- }
- if i+1 < len(ins) {
- newIns = append(newIns, ins[i+1:]...)
- }
- return newIns
-}
-
-func (e *evaluator) evalLinkNode(ln *ast.LinkNode) ast.InlineNode {
- if len(ln.Inlines) == 0 {
- ln.Inlines = ast.InlineSlice{&ast.TextNode{Text: ln.Ref.Value}}
- }
- ref := ln.Ref
- if ref == nil || ref.State != ast.RefStateZettel {
- return ln
- }
-
- zid := mustParseZid(ref)
- _, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
- if errors.Is(err, &box.ErrNotAllowed{}) {
- return &ast.FormatNode{
- Kind: ast.FormatSpan,
- Attrs: ln.Attrs,
- Inlines: getLinkInline(ln),
- }
- } else if err != nil {
- ln.Ref.State = ast.RefStateBroken
- return ln
- }
-
- ln.Ref.State = ast.RefStateZettel
- return ln
-}
-
-func getLinkInline(ln *ast.LinkNode) ast.InlineSlice {
- if ln.Inlines != nil {
- return ln.Inlines
- }
- return ast.InlineSlice{&ast.TextNode{Text: ln.Ref.Value}}
-}
-
-func (e *evaluator) evalEmbedRefNode(en *ast.EmbedRefNode) ast.InlineNode {
- ref := en.Ref
-
- // To prevent e.embedCount from counting
- if errText := e.checkMaxTransclusions(ref); errText != nil {
- return errText
- }
-
- switch ref.State {
- case ast.RefStateZettel:
- // Only zettel references will be evaluated.
- case ast.RefStateInvalid, ast.RefStateBroken:
- e.transcludeCount++
- return createInlineErrorImage(en)
- case ast.RefStateSelf:
- e.transcludeCount++
- return createInlineErrorText(ref, "Self", "embed", "reference")
- case ast.RefStateFound, ast.RefStateExternal:
- return en
- case ast.RefStateHosted, ast.RefStateBased:
- if n := createEmbeddedNodeLocal(ref); n != nil {
- n.Attrs = en.Attrs
- n.Inlines = en.Inlines
- return n
- }
- return en
- default:
- return createInlineErrorText(ref, "Illegal", "inline", "state", strconv.Itoa(int(ref.State)))
- }
-
- zid := mustParseZid(ref)
- zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
- if err != nil {
- if errors.Is(err, &box.ErrNotAllowed{}) {
- return nil
- }
- e.transcludeCount++
- return createInlineErrorImage(en)
- }
-
- if syntax := string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)); parser.IsImageFormat(syntax) {
- e.updateImageRefNode(en, zettel.Meta, syntax)
- return en
- } else if !parser.IsASTParser(syntax) {
- // Not embeddable.
- e.transcludeCount++
- return createInlineErrorText(ref, "Not", "embeddable (syntax="+syntax+")")
- }
-
- cost, ok := e.costMap[zid]
- zn := cost.zn
- if zn == e.marker {
- e.transcludeCount++
- return createInlineErrorText(ref, "Recursive", "transclusion")
- }
- if !ok {
- ec := e.transcludeCount
- e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec}
- zn = e.evaluateEmbeddedZettel(zettel)
- e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec}
- e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first.
- }
- e.transcludeCount++
-
- result, ok := e.embedMap[ref.Value]
- if !ok {
- // Search for text to be embedded.
- result = findInlineSlice(&zn.BlocksAST, ref.URL.Fragment)
- e.embedMap[ref.Value] = result
- }
- if len(result) == 0 {
- return &ast.LiteralNode{
- Kind: ast.LiteralComment,
- Attrs: map[string]string{"-": ""},
- Content: append([]byte("Nothing to transclude: "), ref.String()...),
- }
- }
-
- if ec := cost.ec; ec > 0 {
- e.transcludeCount += cost.ec
- }
- return &result
-}
-
-func mustParseZid(ref *ast.Reference) id.Zid {
- zid, err := id.Parse(ref.URL.Path)
- if err != nil {
- panic(fmt.Sprintf("%v: %q (state %v) -> %v", err, ref.URL.Path, ref.State, ref))
- }
- return zid
-}
-
-func (e *evaluator) updateImageRefNode(en *ast.EmbedRefNode, m *meta.Meta, syntax string) {
- en.Syntax = syntax
- if len(en.Inlines) == 0 {
- is := parser.ParseDescriptionAST(m)
- if len(is) > 0 {
- ast.Walk(e, &is)
- if len(is) > 0 {
- en.Inlines = is
- }
- }
- }
-}
-
-func createInlineErrorImage(en *ast.EmbedRefNode) *ast.EmbedRefNode {
- errorZid := id.ZidEmoji
- en.Ref = ast.ParseReference(errorZid.String())
- if len(en.Inlines) == 0 {
- en.Inlines = ast.InlineSlice{&ast.TextNode{Text: "Error placeholder"}}
- }
- return en
-}
-
-func createInlineErrorText(ref *ast.Reference, msgWords ...string) ast.InlineNode {
- text := strings.Join(msgWords, " ")
- if ref != nil {
- text += ": " + ref.String() + "."
- }
- ln := &ast.LiteralNode{
- Kind: ast.LiteralInput,
- Content: []byte(text),
- }
- fn := &ast.FormatNode{
- Kind: ast.FormatStrong,
- Inlines: ast.InlineSlice{ln},
- }
- fn.Attrs = fn.Attrs.AddClass("error")
- return fn
-}
-
-func createEmbeddedNodeLocal(ref *ast.Reference) *ast.EmbedRefNode {
- ext := path.Ext(ref.Value)
- if ext != "" && ext[0] == '.' {
- ext = ext[1:]
- }
- pinfo := parser.Get(ext)
- if pinfo == nil || !pinfo.IsImageFormat {
- return nil
- }
- return &ast.EmbedRefNode{
- Ref: ref,
- Syntax: ext,
- }
-}
-
-func (e *evaluator) evaluateEmbeddedZettel(zettel zettel.Zettel) *ast.ZettelNode {
- zn := parser.ParseZettel(e.ctx, zettel, string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), e.rtConfig)
- ast.Walk(e, &zn.BlocksAST)
- return zn
-}
-
-func findInlineSlice(bs *ast.BlockSlice, fragment string) ast.InlineSlice {
- if fragment == "" {
- return firstInlinesToEmbed(*bs)
- }
- fs := fragmentSearcher{fragment: fragment}
- ast.Walk(&fs, bs)
- return fs.result
-}
-
-func firstInlinesToEmbed(bs ast.BlockSlice) ast.InlineSlice {
- if ins := bs.FirstParagraphInlines(); ins != nil {
- return ins
- }
- if len(bs) == 0 {
- return nil
- }
- if bn, ok := bs[0].(*ast.BLOBNode); ok {
- return ast.InlineSlice{&ast.EmbedBLOBNode{
- Blob: bn.Blob,
- Syntax: bn.Syntax,
- Inlines: bn.Description,
- }}
- }
- return nil
-}
-
-type fragmentSearcher struct {
- fragment string
- result ast.InlineSlice
-}
-
-func (fs *fragmentSearcher) Visit(node ast.Node) ast.Visitor {
- if len(fs.result) > 0 {
- return nil
- }
- switch n := node.(type) {
- case *ast.BlockSlice:
- fs.visitBlockSlice(n)
- case *ast.InlineSlice:
- fs.visitInlineSlice(n)
- default:
- return fs
- }
- return nil
-}
-
-func (fs *fragmentSearcher) visitBlockSlice(bs *ast.BlockSlice) {
- for i, bn := range *bs {
- if hn, ok := bn.(*ast.HeadingNode); ok && hn.Fragment == fs.fragment {
- fs.result = (*bs)[i+1:].FirstParagraphInlines()
- return
- }
- ast.Walk(fs, bn)
- }
-}
-
-func (fs *fragmentSearcher) visitInlineSlice(is *ast.InlineSlice) {
- for i, in := range *is {
- if mn, ok := in.(*ast.MarkNode); ok && mn.Fragment == fs.fragment {
- ris := skipBreakeNodes((*is)[i+1:])
- if len(mn.Inlines) > 0 {
- fs.result = slices.Clone(mn.Inlines)
- fs.result = append(fs.result, &ast.TextNode{Text: " "})
- fs.result = append(fs.result, ris...)
- } else {
- fs.result = ris
- }
- return
- }
- ast.Walk(fs, in)
- }
-}
-
-func skipBreakeNodes(ins ast.InlineSlice) ast.InlineSlice {
- for i, in := range ins {
- switch in.(type) {
- case *ast.BreakNode:
- default:
- return ins[i:]
- }
- }
- return nil
+func (e *evaluator) checkMaxTransclusions(ref *sx.Pair) *sx.Pair {
+ if maxTrans := e.transcludeMax; e.transcludeCount > maxTrans {
+ e.transcludeCount = maxTrans + 1
+ return createInlineErrorText(ref,
+ "Too many transclusions (must be at most "+strconv.Itoa(maxTrans)+
+ ", see runtime configuration key max-transclusions)")
+ }
+ return nil
+}
+
+func createInlineErrorText(ref *sx.Pair, message string) *sx.Pair {
+ text := message
+ if ref != nil {
+ text += ": " + sz.ReferenceString(ref) + "."
+ }
+ ln := zsx.MakeLiteral(zsx.SymLiteralOutput, nil, text)
+ fn := zsx.MakeFormat(zsx.SymFormatStrong,
+ sx.MakeList(sx.Cons(sx.MakeString("class"), sx.MakeString("error"))),
+ sx.MakeList(ln))
+ return fn
+}
+
+func createInlineErrorImage(attrs *sx.Pair, text *sx.Pair) *sx.Pair {
+ ref := sz.ScanReference(id.ZidEmoji.String())
+ if text == nil {
+ text = sx.MakeList(zsx.MakeText("Error placeholder"))
+ }
+ return zsx.MakeEmbed(attrs, ref, "", text)
}
ADDED internal/evaluator/inline.go
Index: internal/evaluator/inline.go
==================================================================
--- /dev/null
+++ internal/evaluator/inline.go
@@ -0,0 +1,259 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021-present Detlef Stern
+//
+// This file is part of Zettelstore.
+//
+// Zettelstore 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: 2021-present Detlef Stern
+//-----------------------------------------------------------------------------
+
+// Package evaluator interprets and evaluates the AST.
+package evaluator
+
+import (
+ "errors"
+ "path"
+
+ "t73f.de/r/sx"
+ "t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
+ "t73f.de/r/zsx"
+
+ "zettelstore.de/z/internal/box"
+ "zettelstore.de/z/internal/parser"
+)
+
+func (e *evaluator) evalLink(node *sx.Pair) *sx.Pair {
+ attrs, ref, inlines := zsx.GetLink(node)
+ refState, refVal := zsx.GetReference(ref)
+ newInlines := inlines
+ if inlines == nil {
+ newInlines = sx.MakeList(zsx.MakeText(refVal))
+ }
+ if !sz.SymRefStateZettel.IsEqualSymbol(refState) {
+ if newInlines != inlines {
+ return zsx.MakeLink(attrs, ref, newInlines)
+ }
+ return node
+ }
+
+ zid := mustParseZid(ref, refVal)
+ _, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
+ if errors.Is(err, &box.ErrNotAllowed{}) {
+ return zsx.MakeFormat(zsx.SymFormatSpan, attrs, newInlines)
+ }
+ if err != nil {
+ return zsx.MakeLink(attrs, zsx.MakeReference(sz.SymRefStateBroken, refVal), newInlines)
+ }
+
+ if newInlines != inlines {
+ return zsx.MakeLink(attrs, ref, newInlines)
+ }
+ return node
+}
+
+func (e *evaluator) evalEmbed(en *sx.Pair) *sx.Pair {
+ attrs, ref, _, inlines := zsx.GetEmbed(en)
+ refSym, refVal := zsx.GetReference(ref)
+
+ // To prevent e.embedCount from counting
+ if errText := e.checkMaxTransclusions(ref); errText != nil {
+ return errText
+ }
+
+ if !sz.SymRefStateZettel.IsEqualSymbol(refSym) {
+ switch refSym {
+ case zsx.SymRefStateInvalid, sz.SymRefStateBroken:
+ e.transcludeCount++
+ return createInlineErrorImage(attrs, inlines)
+ case zsx.SymRefStateSelf:
+ e.transcludeCount++
+ return createInlineErrorText(ref, "Self embed reference")
+ case sz.SymRefStateFound, zsx.SymRefStateExternal:
+ return en
+ case zsx.SymRefStateHosted, sz.SymRefStateBased:
+ if n := createLocalEmbedded(attrs, ref, refVal, inlines); n != nil {
+ return n
+ }
+ return en
+ case sz.SymRefStateQuery:
+ return createInlineErrorText(ref, "Query reference not allowed here")
+ default:
+ return createInlineErrorText(ref, "Illegal inline state "+refSym.GetValue())
+ }
+ }
+
+ zid := mustParseZid(ref, refVal)
+ zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
+ if err != nil {
+ if errors.Is(err, &box.ErrNotAllowed{}) {
+ return nil
+ }
+ e.transcludeCount++
+ return createInlineErrorImage(attrs, inlines)
+ }
+
+ if syntax := string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)); parser.IsImageFormat(syntax) {
+ return e.updateImageRefNode(attrs, ref, inlines, zettel.Meta, syntax)
+ } else if !parser.IsASTParser(syntax) {
+ // Not embeddable.
+ e.transcludeCount++
+ return createInlineErrorText(ref, "Not embeddable (syntax="+syntax+")")
+ }
+
+ cost, ok := e.costMap[zid]
+ zn := cost.zn
+ if zn == e.marker {
+ e.transcludeCount++
+ return createInlineErrorText(ref, "Recursive transclusion")
+ }
+ if !ok {
+ ec := e.transcludeCount
+ e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec}
+ zn = e.evaluateEmbeddedZettel(zettel)
+ e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec}
+ e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first.
+ }
+ e.transcludeCount++
+
+ result, ok := e.embedMap[refVal]
+ if !ok {
+ // Search for text to be embedded.
+ _, fragment := sz.SplitFragment(refVal)
+ blocks := zsx.GetBlock(zn.Blocks)
+ if fragment == "" {
+ result = firstInlinesToEmbed(blocks)
+ } else {
+ result = findFragmentInBlocks(blocks, fragment)
+ }
+ e.embedMap[refVal] = result
+ }
+ if result == nil {
+ return zsx.MakeLiteral(zsx.SymLiteralComment,
+ sx.MakeList(sx.Cons(sx.MakeString(zsx.DefaultAttribute), sx.MakeString(""))),
+ "Nothing to transclude: "+sz.ReferenceString(ref),
+ )
+ }
+
+ if ec := cost.ec; ec > 0 {
+ e.transcludeCount += cost.ec
+ }
+ if result.Tail() == nil {
+ return result.Head()
+ }
+ return result.Cons(zsx.SymSpecialSplice)
+}
+
+func (e *evaluator) updateImageRefNode(
+ attrs *sx.Pair, ref *sx.Pair, inlines *sx.Pair, m *meta.Meta, syntax string,
+) *sx.Pair {
+ if inlines != nil {
+ if is := parser.ParseDescription(m); is != nil {
+ if is = mustPair(zsx.Walk(e, is, nil)); is != nil {
+ inlines = is
+ }
+ }
+ }
+ return zsx.MakeEmbed(attrs, ref, syntax, inlines)
+}
+
+func findFragmentInBlocks(blocks *sx.Pair, fragment string) *sx.Pair {
+ fs := fragmentSearcher{fragment: fragment}
+ zsx.WalkItList(&fs, blocks, 0, nil)
+ return fs.result
+}
+
+type fragmentSearcher struct {
+ result *sx.Pair
+ fragment string
+}
+
+func (fs *fragmentSearcher) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
+ if fs.result != nil {
+ return true
+ }
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymHeading:
+ _, _, _, _, frag := zsx.GetHeading(node)
+ if frag == fs.fragment {
+ bn := zsx.GetWalkList(alst)
+ fs.result = firstInlinesToEmbed(bn.Tail())
+ return true
+ }
+
+ case zsx.SymMark:
+ _, _, frag, inlines := zsx.GetMark(node)
+ if frag == fs.fragment {
+ next := zsx.GetWalkList(alst).Tail()
+ for ; next != nil; next = next.Tail() {
+ car := next.Head().Car()
+ if !zsx.SymSoft.IsEqual(car) && !zsx.SymHard.IsEqual(car) {
+ break
+ }
+ }
+ if next == nil { // Mark is last in inline list
+ fs.result = inlines
+ return true
+ }
+
+ var lb sx.ListBuilder
+ if inlines != nil {
+ lb.Collect(inlines.Values())
+ }
+ lb.Collect(next.Values())
+ fs.result = lb.List()
+ return true
+ }
+
+ }
+ }
+ return false
+}
+func (fs *fragmentSearcher) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+func firstInlinesToEmbed(blocks *sx.Pair) *sx.Pair {
+ if blocks != nil {
+ if ins := firstParagraphInlines(blocks); ins != nil {
+ return ins
+ }
+
+ blk := blocks.Head()
+ if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol && zsx.SymBLOB.IsEqualSymbol(sym) {
+ attrs, syntax, content, inlines := zsx.GetBLOBuncode(blk)
+ return sx.MakeList(zsx.MakeEmbedBLOBuncode(attrs, syntax, content, inlines))
+ }
+ }
+ return nil
+}
+
+// firstParagraphInlines returns the inline list of the first paragraph that
+// contains a inline list.
+func firstParagraphInlines(blocks *sx.Pair) *sx.Pair {
+ for blockObj := range blocks.Values() {
+ if block, isPair := sx.GetPair(blockObj); isPair {
+ if sym, isSymbol := sx.GetSymbol(block.Car()); isSymbol && zsx.SymPara.IsEqualSymbol(sym) {
+ if inlines := zsx.GetPara(block); inlines != nil {
+ return inlines
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func createLocalEmbedded(attrs *sx.Pair, ref *sx.Pair, refValue string, inlines *sx.Pair) *sx.Pair {
+ ext := path.Ext(refValue)
+ if ext != "" && ext[0] == '.' {
+ ext = ext[1:]
+ }
+ pinfo := parser.Get(ext)
+ if pinfo == nil || !pinfo.IsImageFormat {
+ return nil
+ }
+ return zsx.MakeEmbed(attrs, ref, ext, inlines)
+}
Index: internal/evaluator/list.go
==================================================================
--- internal/evaluator/list.go
+++ internal/evaluator/list.go
@@ -19,25 +19,26 @@
"math"
"slices"
"strconv"
"strings"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
- "zettelstore.de/z/internal/ast"
"zettelstore.de/z/internal/query"
)
-// QueryAction transforms a list of metadata according to query actions into a AST nested list.
-func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta) (ast.BlockNode, int) {
+// QueryAction transforms a list of metadata according to query actions into a SZ nested list.
+func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta) (*sx.Pair, int) {
ap := actionPara{
ctx: ctx,
q: q,
ml: ml,
- kind: ast.NestedListUnordered,
+ kind: zsx.SymListUnordered,
minVal: -1,
maxVal: -1,
}
actions := q.Actions()
if len(actions) == 0 {
@@ -45,11 +46,11 @@
}
acts := make([]string, 0, len(actions))
for _, act := range actions {
if strings.HasPrefix(act, api.NumberedAction[0:1]) {
- ap.kind = ast.NestedListOrdered
+ ap.kind = zsx.SymListOrdered
continue
}
if strings.HasPrefix(act, api.MinAction) {
if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 {
ap.minVal = num
@@ -82,10 +83,11 @@
}
if firstUnknowAct == "" {
firstUnknowAct = act
}
}
+
bn, numItems := ap.createBlockNodeMeta(strings.ToLower(firstUnknowAct))
if bn != nil && numItems == 0 && firstUnknowAct == strings.ToUpper(firstUnknowAct) {
bn, numItems = ap.createBlockNodeMeta("")
}
return bn, numItems
@@ -93,73 +95,69 @@
type actionPara struct {
ctx context.Context
q *query.Query
ml []*meta.Meta
- kind ast.NestedListKind
+ kind *sx.Symbol
minVal int
maxVal int
}
-func (ap *actionPara) createBlockNodeWord(key string) (ast.BlockNode, int) {
+func (ap *actionPara) createBlockNodeWord(key string) (*sx.Pair, int) {
var buf bytes.Buffer
ccs, bufLen := ap.prepareCatAction(key, &buf)
if len(ccs) == 0 {
return nil, 0
}
- items := make([]ast.ItemSlice, 0, len(ccs))
+
ccs.SortByName()
+ var items sx.ListBuilder
+ count := 0
for _, cat := range ccs {
buf.WriteString(string(cat.Name))
- items = append(items, ast.ItemSlice{ast.CreateParaNode(&ast.LinkNode{
- Attrs: nil,
- Ref: ast.ParseReference(buf.String()),
- Inlines: ast.InlineSlice{&ast.TextNode{Text: string(cat.Name)}},
- })})
+ items.Add(zsx.MakeBlock(zsx.MakePara(
+ zsx.MakeLink(nil,
+ sz.ScanReference(buf.String()),
+ sx.MakeList(zsx.MakeText(string(cat.Name)))),
+ )))
+ count++
buf.Truncate(bufLen)
}
- return &ast.NestedListNode{
- Kind: ap.kind,
- Items: items,
- Attrs: nil,
- }, len(items)
+ return zsx.MakeList(ap.kind, nil, items.List()), count
}
-func (ap *actionPara) createBlockNodeTagSet(key string) (ast.BlockNode, int) {
+func (ap *actionPara) createBlockNodeTagSet(key string) (*sx.Pair, int) {
var buf bytes.Buffer
ccs, bufLen := ap.prepareCatAction(key, &buf)
if len(ccs) == 0 {
return nil, 0
}
ccs.SortByCount()
ccs = ap.limitTags(ccs)
- countMap := ap.calcFontSizes(ccs)
+ countClassAttrs := ap.calcFontSizes(ccs)
- para := make(ast.InlineSlice, 0, len(ccs))
ccs.SortByName()
+ var tags sx.ListBuilder
+ count := 0
for i, cat := range ccs {
if i > 0 {
- para = append(para, &ast.TextNode{Text: " "})
+ tags.Add(zsx.MakeText(" "))
}
buf.WriteString(string(cat.Name))
- para = append(para,
- &ast.LinkNode{
- Attrs: countMap[cat.Count],
- Ref: ast.ParseReference(buf.String()),
- Inlines: ast.InlineSlice{
- &ast.TextNode{Text: string(cat.Name)},
- },
- },
- &ast.FormatNode{
- Kind: ast.FormatSuper,
- Attrs: nil,
- Inlines: ast.InlineSlice{&ast.TextNode{Text: strconv.Itoa(cat.Count)}},
- },
- )
+ tags.AddN(
+ zsx.MakeLink(
+ countClassAttrs[cat.Count],
+ sz.ScanReference(buf.String()),
+ sx.MakeList(zsx.MakeText(string(cat.Name)))),
+ zsx.MakeFormat(zsx.SymFormatSuper,
+ nil,
+ sx.MakeList(zsx.MakeText(strconv.Itoa(cat.Count)))),
+ )
+ count++
buf.Truncate(bufLen)
}
- return &ast.ParaNode{Inlines: para}, len(ccs)
+ return zsx.MakeParaList(tags.List()), count
}
func (ap *actionPara) limitTags(ccs meta.CountedCategories) meta.CountedCategories {
if minVal, maxVal := ap.minVal, ap.maxVal; minVal > 0 || maxVal > 0 {
if minVal < 0 {
@@ -179,11 +177,11 @@
}
}
return ccs
}
-func (ap *actionPara) createBlockNodeMetaKeys() (ast.BlockNode, int) {
+func (ap *actionPara) createBlockNodeMetaKeys() (*sx.Pair, int) {
arr := make(meta.Arrangement, 128)
for _, m := range ap.ml {
for k := range m.Map() {
arr[k] = append(arr[k], m)
}
@@ -194,11 +192,12 @@
ccs := arr.Counted()
ccs.SortByName()
var buf bytes.Buffer
bufLen := ap.prepareSimpleQuery(&buf)
- items := make([]ast.ItemSlice, 0, len(ccs))
+ var items sx.ListBuilder
+ count := 0
for _, cat := range ccs {
buf.WriteString(string(cat.Name))
buf.WriteString(api.ExistOperator)
q1 := buf.String()
buf.Truncate(bufLen)
@@ -205,55 +204,46 @@
buf.WriteString(api.ActionSeparator)
buf.WriteString(string(cat.Name))
q2 := buf.String()
buf.Truncate(bufLen)
- items = append(items, ast.ItemSlice{ast.CreateParaNode(
- &ast.LinkNode{
- Attrs: nil,
- Ref: ast.ParseReference(q1),
- Inlines: ast.InlineSlice{&ast.TextNode{Text: string(cat.Name)}},
- },
- &ast.TextNode{Text: " "},
- &ast.TextNode{Text: "(" + strconv.Itoa(cat.Count) + ", "},
- &ast.LinkNode{
- Attrs: nil,
- Ref: ast.ParseReference(q2),
- Inlines: ast.InlineSlice{&ast.TextNode{Text: "values"}},
- },
- &ast.TextNode{Text: ")"},
- )})
- }
- return &ast.NestedListNode{
- Kind: ap.kind,
- Items: items,
- Attrs: nil,
- }, len(items)
-}
-
-func (ap *actionPara) createBlockNodeMeta(key string) (ast.BlockNode, int) {
+ items.Add(zsx.MakeBlock(zsx.MakePara(
+ zsx.MakeLink(nil,
+ sz.ScanReference(q1),
+ sx.MakeList(zsx.MakeText(string(cat.Name)))),
+ zsx.MakeText(" "),
+ zsx.MakeText("("+strconv.Itoa(cat.Count)+", "),
+ zsx.MakeLink(nil,
+ sz.ScanReference(q2),
+ sx.MakeList(zsx.MakeText("values"))),
+ zsx.MakeText(")"),
+ )))
+ count++
+ }
+ return zsx.MakeList(ap.kind, nil, items.List()), count
+}
+
+func (ap *actionPara) createBlockNodeMeta(key string) (*sx.Pair, int) {
if len(ap.ml) == 0 {
return nil, 0
}
- items := make([]ast.ItemSlice, 0, len(ap.ml))
+ var items sx.ListBuilder
+ count := 0
for _, m := range ap.ml {
if key != "" {
if _, found := m.Get(key); !found {
continue
}
}
- items = append(items, ast.ItemSlice{ast.CreateParaNode(&ast.LinkNode{
- Attrs: nil,
- Ref: ast.ParseReference(m.Zid.String()),
- Inlines: ast.ParseSpacedText(m.GetTitle()),
- })})
- }
- return &ast.NestedListNode{
- Kind: ap.kind,
- Items: items,
- Attrs: nil,
- }, len(items)
+ items.Add(zsx.MakeBlock(zsx.MakePara(
+ zsx.MakeLink(nil,
+ sz.ScanReference(m.Zid.String()),
+ sx.MakeList(zsx.MakeText(sz.NormalizedSpacedText(m.GetTitle())))),
+ )))
+ count++
+ }
+ return zsx.MakeList(ap.kind, nil, items.List()), count
}
func (ap *actionPara) prepareCatAction(key string, buf *bytes.Buffer) (meta.CountedCategories, int) {
if len(ap.ml) == 0 {
return nil, 0
@@ -272,26 +262,25 @@
}
func (ap *actionPara) prepareSimpleQuery(buf *bytes.Buffer) int {
sea := ap.q.Clone()
sea.RemoveActions()
- buf.WriteString(ast.QueryPrefix)
+ buf.WriteString(api.QueryPrefix)
sea.Print(buf)
- if buf.Len() > len(ast.QueryPrefix) {
+ if buf.Len() > len(api.QueryPrefix) {
buf.WriteByte(' ')
}
return buf.Len()
}
const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css
const fontSizes64 = float64(fontSizes)
-func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]zsx.Attributes {
- var fsAttrs [fontSizes]zsx.Attributes
- var a zsx.Attributes
+func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]*sx.Pair {
+ var fsAttrs [fontSizes]*sx.Pair
for i := range fontSizes {
- fsAttrs[i] = a.AddClass("zs-font-size-" + strconv.Itoa(i))
+ fsAttrs[i] = sx.MakeList(sx.Cons(sx.MakeString("class"), sx.MakeString("zs-font-size-"+strconv.Itoa(i))))
}
countMap := make(map[int]int, len(ccs))
for _, cat := range ccs {
countMap[cat.Count]++
@@ -301,11 +290,11 @@
for count := range countMap {
countList = append(countList, count)
}
slices.Sort(countList)
- result := make(map[int]zsx.Attributes, len(countList))
+ result := make(map[int]*sx.Pair, len(countList))
if len(countList) <= fontSizes {
// If we have less different counts, center them inside the fsAttrs vector.
curSize := (fontSizes - len(countList)) / 2
for _, count := range countList {
result[count] = fsAttrs[curSize]
Index: internal/evaluator/metadata.go
==================================================================
--- internal/evaluator/metadata.go
+++ internal/evaluator/metadata.go
@@ -12,56 +12,52 @@
//-----------------------------------------------------------------------------
package evaluator
import (
- "t73f.de/r/zsc/domain/meta"
-
- "zettelstore.de/z/internal/ast"
-)
-
-func evaluateMetadata(m *meta.Meta) ast.BlockSlice {
- descrlist := &ast.DescriptionListNode{}
- for key, val := range m.All() {
- descrlist.Descriptions = append(
- descrlist.Descriptions, getMetadataDescription(key, val))
- }
- return ast.BlockSlice{descrlist}
-}
-
-func getMetadataDescription(key string, value meta.Value) ast.Description {
- is := convertMetavalueToInlineSlice(value, meta.Type(key))
- return ast.Description{
- Term: ast.InlineSlice{&ast.TextNode{Text: key}},
- Descriptions: []ast.DescriptionSlice{{&ast.ParaNode{Inlines: is}}},
- }
-}
-
-func convertMetavalueToInlineSlice(value meta.Value, dt *meta.DescriptionType) ast.InlineSlice {
- var sliceData []string
- if dt.IsSet {
- sliceData = value.AsSlice()
- if len(sliceData) == 0 {
- return nil
- }
- } else {
- sliceData = []string{string(value)}
+ "iter"
+
+ "t73f.de/r/sx"
+ zeroiter "t73f.de/r/zero/iter"
+ "t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
+ "t73f.de/r/zsx"
+)
+
+func evaluateMetadata(m *meta.Meta) *sx.Pair {
+ var lb sx.ListBuilder
+ lb.AddN(zsx.SymDescription, nil)
+ for key, val := range m.All() {
+ lb.Add(sx.MakeList(zsx.MakeText(key)))
+ inlines := convertMetavalueToInlineSlice(val, meta.Type(key))
+ lb.Add(zsx.MakeBlock(zsx.MakeBlock(zsx.MakeParaList(inlines))))
+ }
+ return zsx.MakeBlock(lb.List())
+}
+
+func convertMetavalueToInlineSlice(val meta.Value, dt *meta.DescriptionType) *sx.Pair {
+ var vals iter.Seq[string]
+ if dt.IsSet {
+ vals = val.Fields()
+ } else {
+ vals = zeroiter.OneSeq(string(val))
}
makeLink := dt == meta.TypeID || dt == meta.TypeIDSet
- result := make(ast.InlineSlice, 0, 2*len(sliceData)-1)
- for i, val := range sliceData {
- if i > 0 {
- result = append(result, &ast.TextNode{Text: " "})
- }
- tn := &ast.TextNode{Text: val}
- if makeLink {
- result = append(result, &ast.LinkNode{
- Ref: ast.ParseReference(val),
- Inlines: ast.InlineSlice{tn},
- })
- } else {
- result = append(result, tn)
- }
- }
- return result
+ var lb sx.ListBuilder
+ first := true
+ for s := range vals {
+ if first {
+ first = false
+ } else {
+ lb.Add(zsx.MakeText(" "))
+ }
+ tn := zsx.MakeText(s)
+ if makeLink {
+ lb.Add(zsx.MakeLink(nil, sz.ScanReference(s), sx.MakeList(tn)))
+ } else {
+ lb.Add(tn)
+ }
+ }
+
+ return lb.List()
}
ADDED internal/evaluator/sxn.go
Index: internal/evaluator/sxn.go
==================================================================
--- /dev/null
+++ internal/evaluator/sxn.go
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021-present Detlef Stern
+//
+// This file is part of Zettelstore.
+//
+// Zettelstore 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: 2021-present Detlef Stern
+//-----------------------------------------------------------------------------
+
+package evaluator
+
+import (
+ "strings"
+
+ "t73f.de/r/sx"
+ "t73f.de/r/sx/sxbuiltins"
+ "t73f.de/r/sx/sxreader"
+ "t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsx"
+)
+
+func evaluateSxn(block *sx.Pair) *sx.Pair {
+ if blocks := zsx.GetBlock(block); blocks != nil {
+ blk := blocks.Head()
+ if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol && zsx.SymVerbatimCode.IsEqualSymbol(sym) {
+ _, attrs, content := zsx.GetVerbatim(blk)
+ if classAttr, hasClass := zsx.GetAttributes(attrs).Get(""); hasClass && classAttr == meta.ValueSyntaxSxn {
+ rd := sxreader.MakeReader(strings.NewReader(content))
+ if objs, err := rd.ReadAll(); err == nil {
+ var lb sx.ListBuilder
+ for _, obj := range objs {
+ var sb strings.Builder
+ _, _ = sxbuiltins.Print(&sb, obj)
+ lb.Add(zsx.MakeVerbatim(zsx.SymVerbatimCode, attrs, sb.String()))
+ }
+ return zsx.MakeBlockList(lb.List())
+ }
+ }
+ }
+ }
+ return block
+}
Index: internal/parser/blob.go
==================================================================
--- internal/parser/blob.go
+++ internal/parser/blob.go
@@ -55,11 +55,11 @@
IsImageFormat: true,
Parse: parseBlob,
})
}
-func parseBlob(inp *input.Input, m *meta.Meta, syntax string) *sx.Pair {
+func parseBlob(inp *input.Input, m *meta.Meta, syntax string, _ *sx.Pair) *sx.Pair {
if p := Get(syntax); p != nil {
syntax = p.Name
}
- return zsx.MakeBlock(zsx.MakeBLOB(nil, ParseDescription(m), syntax, string(inp.Src)))
+ return zsx.MakeBlock(zsx.MakeBLOB(nil, syntax, inp.Src, ParseDescription(m)))
}
Index: internal/parser/cleaner.go
==================================================================
--- internal/parser/cleaner.go
+++ internal/parser/cleaner.go
@@ -17,150 +17,91 @@
import (
"strconv"
"strings"
+ "t73f.de/r/sx"
zerostrings "t73f.de/r/zero/strings"
+ "t73f.de/r/zsx"
- "zettelstore.de/z/internal/ast"
"zettelstore.de/z/internal/encoder"
)
-// Clean cleans the given block list.
-func Clean(bs *ast.BlockSlice, allowHTML bool) {
- cv := cleanVisitor{
- allowHTML: allowHTML,
- hasMark: false,
- doMark: false,
- }
- ast.Walk(&cv, bs)
- if cv.hasMark {
- cv.doMark = true
- ast.Walk(&cv, bs)
- }
-}
-
-type cleanVisitor struct {
- textEnc encoder.TextEncoder
- ids map[string]ast.Node
- allowHTML bool
- hasMark bool
- doMark bool
-}
-
-func (cv *cleanVisitor) Visit(node ast.Node) ast.Visitor {
- switch n := node.(type) {
- case *ast.BlockSlice:
- if !cv.allowHTML {
- cv.visitBlockSlice(n)
- return nil
- }
- case *ast.InlineSlice:
- if !cv.allowHTML {
- cv.visitInlineSlice(n)
- return nil
- }
- case *ast.HeadingNode:
- cv.visitHeading(n)
- return nil
- case *ast.MarkNode:
- cv.visitMark(n)
- return nil
- }
- return cv
-}
-
-func (cv *cleanVisitor) visitBlockSlice(bs *ast.BlockSlice) {
- if bs == nil {
- return
- }
- if len(*bs) == 0 {
- *bs = nil
- return
- }
- for _, bn := range *bs {
- ast.Walk(cv, bn)
- }
-
- fromPos, toPos := 0, 0
- for fromPos < len(*bs) {
- (*bs)[toPos] = (*bs)[fromPos]
- fromPos++
- switch bn := (*bs)[toPos].(type) {
- case *ast.VerbatimNode:
- if bn.Kind != ast.VerbatimHTML {
- toPos++
- }
- default:
- toPos++
- }
- }
- for pos := toPos; pos < len(*bs); pos++ {
- (*bs)[pos] = nil // Allow excess nodes to be garbage collected.
- }
- *bs = (*bs)[:toPos:toPos]
-}
-
-func (cv *cleanVisitor) visitInlineSlice(is *ast.InlineSlice) {
- if is == nil {
- return
- }
- if len(*is) == 0 {
- *is = nil
- return
- }
- for _, bn := range *is {
- ast.Walk(cv, bn)
- }
-}
-
-func (cv *cleanVisitor) visitHeading(hn *ast.HeadingNode) {
- if cv.doMark || hn == nil || len(hn.Inlines) == 0 {
- return
- }
- if hn.Slug == "" {
- var sb strings.Builder
- _, err := cv.textEnc.WriteInlines(&sb, &hn.Inlines)
- if err != nil {
- return
- }
- hn.Slug = zerostrings.Slugify(sb.String())
- }
- if hn.Slug != "" {
- hn.Fragment = cv.addIdentifier(hn.Slug, hn)
- }
-}
-
-func (cv *cleanVisitor) visitMark(mn *ast.MarkNode) {
- if !cv.doMark {
- cv.hasMark = true
- return
- }
- if mn.Mark == "" {
- mn.Slug = ""
- mn.Fragment = cv.addIdentifier("*", mn)
- return
- }
- if mn.Slug == "" {
- mn.Slug = zerostrings.Slugify(mn.Mark)
- }
- mn.Fragment = cv.addIdentifier(mn.Slug, mn)
-}
-
-func (cv *cleanVisitor) addIdentifier(id string, node ast.Node) string {
- if cv.ids == nil {
- cv.ids = map[string]ast.Node{id: node}
- return id
- }
- if n, ok := cv.ids[id]; ok && n != node {
+// Clean the given SZ syntax tree.
+func Clean(node *sx.Pair) {
+ v1 := cleanPhase1{ids: idsNode{}}
+ zsx.WalkIt(&v1, node, nil)
+ if v1.hasMark {
+ v2 := cleanPhase2{ids: v1.ids}
+ zsx.WalkIt(&v2, node, nil)
+ }
+}
+
+type cleanPhase1 struct {
+ ids idsNode
+ hasMark bool // Mark nodes will be cleaned in phase 2 only
+}
+
+func (v *cleanPhase1) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymHeading:
+ levelNode := node.Tail()
+ attrsNode := levelNode.Tail()
+ slugNode := attrsNode.Tail()
+ fragmentNode := slugNode.Tail()
+
+ textNode := fragmentNode.Tail()
+ var sb strings.Builder
+ var textEnc encoder.TextEncoder
+ if err := textEnc.WriteSz(&sb, textNode.Cons(zsx.SymPara)); err == nil {
+ slugText := zerostrings.Slugify(sb.String())
+ slugNode.SetCar(sx.MakeString(slugText))
+ fragmentNode.SetCar(sx.MakeString(v.ids.addIdentifier(slugText, node)))
+ }
+ case zsx.SymMark:
+ v.hasMark = true
+ }
+ }
+ return false
+}
+func (v *cleanPhase1) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+type cleanPhase2 struct {
+ ids idsNode
+}
+
+func (v *cleanPhase2) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
+ switch sym {
+ case zsx.SymMark:
+ stringNode := node.Tail()
+ if markString, isString := sx.GetString(stringNode.Car()); isString {
+ slugNode := stringNode.Tail()
+ fragmentNode := slugNode.Tail()
+
+ slugText := zerostrings.Slugify(markString.GetValue())
+ slugNode.SetCar(sx.MakeString(slugText))
+ fragmentNode.SetCar(sx.MakeString(v.ids.addIdentifier(slugText, node)))
+ }
+ }
+ }
+ return false
+}
+func (v *cleanPhase2) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+type idsNode map[string]*sx.Pair
+
+func (ids idsNode) addIdentifier(id string, node *sx.Pair) string {
+ if n, ok := ids[id]; ok && n != node {
prefix := id + "-"
for count := 1; ; count++ {
newID := prefix + strconv.Itoa(count)
- if n2, ok2 := cv.ids[newID]; !ok2 || n2 == node {
- cv.ids[newID] = node
+ if n2, ok2 := ids[newID]; !ok2 || n2 == node {
+ ids[newID] = node
return newID
}
}
}
- cv.ids[id] = node
+ ids[id] = node
return id
}
ADDED internal/parser/cleaner_test.go
Index: internal/parser/cleaner_test.go
==================================================================
--- /dev/null
+++ internal/parser/cleaner_test.go
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2025-present Detlef Stern
+//
+// This file is part of Zettelstore.
+//
+// Zettelstore 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: 2025-present Detlef Stern
+//-----------------------------------------------------------------------------
+
+package parser_test
+
+import (
+ "strings"
+ "testing"
+
+ "t73f.de/r/sx"
+ "t73f.de/r/sx/sxreader"
+ "zettelstore.de/z/internal/parser"
+)
+
+func TestCleaner(t *testing.T) {
+ var testcases = []struct {
+ name string
+ src string
+ exp string
+ }{
+ {name: "nil", src: "()", exp: "()"},
+
+ {name: "simple heading",
+ src: "(HEADING 1 () \"\" \"\" (TEXT \"Heading\"))",
+ exp: "(HEADING 1 () \"heading\" \"heading\" (TEXT \"Heading\"))"},
+ {name: "same simple heading",
+ src: "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"Heading\")) (HEADING 1 () \"\" \"\" (TEXT \"Heading\")))",
+ exp: "(BLOCK (HEADING 1 () \"heading\" \"heading\" (TEXT \"Heading\")) (HEADING 1 () \"heading\" \"heading-1\" (TEXT \"Heading\")))"},
+
+ {name: "simple mark, no text",
+ src: "(MARK \"m\" \"\" \"\")",
+ exp: "(MARK \"m\" \"m\" \"m\")"},
+ {name: "same simple mark, no text",
+ src: "(PARA (MARK \"m\" \"\" \"\") (MARK \"m\" \"\" \"\"))",
+ exp: "(PARA (MARK \"m\" \"m\" \"m\") (MARK \"m\" \"m\" \"m-1\"))"},
+ {name: "mark before heading",
+ src: "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"x\")) (PARA (MARK \"x\" \"\" \"\")))",
+ exp: "(BLOCK (HEADING 1 () \"x\" \"x\" (TEXT \"x\")) (PARA (MARK \"x\" \"x\" \"x-1\")))"},
+ {name: "mark in mark with text",
+ src: `(MARK "m" "" "" (MARK "m" "" "" (TEXT "x")))`,
+ exp: `(MARK "m" "m" "m" (MARK "m" "m" "m-1" (TEXT "x")))`},
+ }
+
+ for _, tc := range testcases {
+ t.Run(tc.name, func(t *testing.T) {
+ rd := sxreader.MakeReader(strings.NewReader(tc.src))
+ obj, err := rd.Read()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ node, isPair := sx.GetPair(obj)
+ if !isPair {
+ t.Error("not a pair:", obj)
+ }
+ parser.Clean(node)
+ if got := node.String(); got != tc.exp {
+ t.Errorf("\nexpected: %q\n but got: %q", tc.exp, got)
+ }
+ })
+ }
+}
Index: internal/parser/draw.go
==================================================================
--- internal/parser/draw.go
+++ internal/parser/draw.go
@@ -21,12 +21,10 @@
"t73f.de/r/sx"
"t73f.de/r/webs/aasvg"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
-
- "zettelstore.de/z/internal/ast"
)
func init() {
register(&Info{
Name: meta.ValueSyntaxDraw,
@@ -42,11 +40,11 @@
defaultFont = ""
defaultScaleX = 10
defaultScaleY = 20
)
-func parseDraw(inp *input.Input, m *meta.Meta, _ string) *sx.Pair {
+func parseDraw(inp *input.Input, m *meta.Meta, _ string, _ *sx.Pair) *sx.Pair {
font := m.GetDefault("font", defaultFont)
scaleX := m.GetNumber("x-scale", defaultScaleX)
scaleY := m.GetNumber("y-scale", defaultScaleY)
if scaleX < 1 || 1000000 < scaleX {
scaleX = defaultScaleX
@@ -61,44 +59,46 @@
}
svg := aasvg.CanvasToSVG(canvas, string(font), int(scaleX), int(scaleY))
if len(svg) == 0 {
return zsx.MakeBlock(zsx.MakeParaList(noSVGErrMsg()))
}
- return zsx.MakeBlock(zsx.MakeBLOB(nil, ParseDescription(m), meta.ValueSyntaxSVG, string(svg)))
+ return zsx.MakeBlock(zsx.MakeBLOB(nil, meta.ValueSyntaxSVG, svg, ParseDescription(m)))
}
// ParseDrawBlock parses the content of an eval verbatim node into an SVG image BLOB.
-func ParseDrawBlock(vn *ast.VerbatimNode) ast.BlockNode {
+func ParseDrawBlock(attrs *sx.Pair, content []byte) *sx.Pair {
+ a := zsx.GetAttributes(attrs)
font := defaultFont
- if val, found := vn.Attrs.Get("font"); found {
+ if val, found := a.Get("font"); found {
font = val
}
- scaleX := getScale(vn.Attrs, "x-scale", defaultScaleX)
- scaleY := getScale(vn.Attrs, "y-scale", defaultScaleY)
+ scaleX := getScaleAST(a, "x-scale", defaultScaleX)
+ scaleY := getScaleAST(a, "y-scale", defaultScaleY)
- canvas, err := aasvg.NewCanvas(vn.Content)
+ canvas, err := aasvg.NewCanvas(content)
if err != nil {
- return ast.CreateParaNode(ast.InlineSlice{&ast.TextNode{Text: "Error: " + err.Error()}}...)
+ return zsx.MakePara(zsx.MakeText("Error: " + err.Error()))
}
if scaleX < 1 || 1000000 < scaleX {
scaleX = defaultScaleX
}
if scaleY < 1 || 1000000 < scaleY {
scaleY = defaultScaleY
}
svg := aasvg.CanvasToSVG(canvas, font, scaleX, scaleY)
if len(svg) == 0 {
- return ast.CreateParaNode(ast.InlineSlice{&ast.TextNode{Text: "NO IMAGE"}}...)
+ return zsx.MakePara(zsx.MakeText("NO IMAGE"))
}
- return &ast.BLOBNode{
- Description: nil, // TODO: look for attribute "summary" / "title"
- Syntax: meta.ValueSyntaxSVG,
- Blob: svg,
- }
+ return zsx.MakeBLOB(
+ nil,
+ meta.ValueSyntaxSVG,
+ svg,
+ nil, // TODO: look for attribute "summary" / "title" for a description.
+ )
}
-func getScale(a zsx.Attributes, key string, defVal int) int {
+func getScaleAST(a zsx.Attributes, key string, defVal int) int {
if val, found := a.Get(key); found {
if n, err := strconv.Atoi(val); err == nil && 0 < n && n < 100000 {
return n
}
}
Index: internal/parser/draw_test.go
==================================================================
--- internal/parser/draw_test.go
+++ internal/parser/draw_test.go
@@ -17,16 +17,15 @@
"testing"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
- "zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/parser"
)
func FuzzParseDraw(f *testing.F) {
f.Fuzz(func(t *testing.T, src []byte) {
t.Parallel()
inp := input.NewInput(src)
- parser.Parse(inp, nil, meta.ValueSyntaxDraw, config.NoHTML)
+ parser.Parse(inp, nil, meta.ValueSyntaxDraw, nil)
})
}
Index: internal/parser/markdown.go
==================================================================
--- internal/parser/markdown.go
+++ internal/parser/markdown.go
@@ -24,17 +24,14 @@
gm "github.com/yuin/goldmark"
gmAst "github.com/yuin/goldmark/ast"
gmText "github.com/yuin/goldmark/text"
"t73f.de/r/sx"
- "t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
-
- "zettelstore.de/z/internal/encoder"
)
func init() {
register(&Info{
Name: meta.ValueSyntaxMarkdown,
@@ -44,23 +41,22 @@
IsImageFormat: false,
Parse: parseMarkdown,
})
}
-func parseMarkdown(inp *input.Input, _ *meta.Meta, _ string) *sx.Pair {
+func parseMarkdown(inp *input.Input, _ *meta.Meta, _ string, alst *sx.Pair) *sx.Pair {
source := []byte(inp.Src[inp.Pos:])
parser := gm.DefaultParser()
node := parser.Parse(gmText.NewReader(source))
- textEnc := encoder.Create(api.EncoderText, nil)
- p := mdP{source: source, docNode: node, textEnc: textEnc}
+ p := mdP{source: source, docNode: node, allowHTML: alst.Assoc(SymAllowHTML) != nil}
return p.acceptBlockChildren(p.docNode)
}
type mdP struct {
- source []byte
- docNode gmAst.Node
- textEnc encoder.Encoder
+ source []byte
+ docNode gmAst.Node
+ allowHTML bool
}
func (p *mdP) acceptBlockChildren(docNode gmAst.Node) *sx.Pair {
if docNode.Type() != gmAst.TypeDocument {
panic(fmt.Sprintf("Expected document, but got node type %v", docNode.Type()))
@@ -177,11 +173,11 @@
func (p *mdP) acceptItemSlice(node gmAst.Node) *sx.Pair {
var result sx.ListBuilder
for elem := node.FirstChild(); elem != nil; elem = elem.NextSibling() {
if item := p.acceptBlock(elem); item != nil {
- result.Add(item)
+ result.Add(zsx.MakeBlock(item))
}
}
return result.List()
}
@@ -202,11 +198,14 @@
if len(content) > 1 {
content = append(content, '\n')
}
content = append(content, closure...)
}
- return zsx.MakeVerbatim(zsx.SymVerbatimHTML, nil, string(content))
+ if p.allowHTML {
+ return zsx.MakeVerbatim(zsx.SymVerbatimHTML, nil, string(content))
+ }
+ return zsx.MakeVerbatim(zsx.SymVerbatimCode, makeAttrHTML(), string(content))
}
func (p *mdP) acceptInlineChildren(node gmAst.Node) *sx.Pair {
var result sx.ListBuilder
for child := node.FirstChild(); child != nil; child = child.NextSibling() {
@@ -367,11 +366,11 @@
segs := make([][]byte, 0, node.Segments.Len())
for i := range node.Segments.Len() {
segment := node.Segments.At(i)
segs = append(segs, segment.Value(p.source))
}
- return zsx.MakeLiteral(
- zsx.SymLiteralCode,
- sx.Cons(sx.Cons(sx.MakeString(""), sx.MakeString("html")), sx.Nil()),
- string(bytes.Join(segs, nil)),
- ), nil
+ return zsx.MakeLiteral(zsx.SymLiteralCode, makeAttrHTML(), string(bytes.Join(segs, nil))), nil
+}
+
+func makeAttrHTML() *sx.Pair {
+ return sx.Cons(sx.Cons(sx.MakeString(""), sx.MakeString("html")), sx.Nil())
}
Index: internal/parser/none.go
==================================================================
--- internal/parser/none.go
+++ internal/parser/none.go
@@ -14,10 +14,11 @@
package parser
import (
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx/input"
)
// none provides a none-parser, e.g. for zettel with just metadata.
@@ -26,8 +27,10 @@
Name: meta.ValueSyntaxNone,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: false,
IsImageFormat: false,
- Parse: func(*input.Input, *meta.Meta, string) *sx.Pair { return nil },
+ Parse: func(inp *input.Input, _ *meta.Meta, _ string, _ *sx.Pair) *sx.Pair {
+ return sz.ParseNoneBlocks(inp)
+ },
})
}
Index: internal/parser/parser.go
==================================================================
--- internal/parser/parser.go
+++ internal/parser/parser.go
@@ -15,19 +15,18 @@
package parser
import (
"context"
"fmt"
- "log"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/ast"
- "zettelstore.de/z/internal/ast/sztrans"
"zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/zettel"
)
// Info describes a single parser.
@@ -38,11 +37,13 @@
Name string
AltNames []string
IsASTParser bool
IsTextFormat bool
IsImageFormat bool
- Parse func(*input.Input, *meta.Meta, string) *sx.Pair
+
+ // Parse the input, with the given metadata, the given syntax, and the given config.
+ Parse func(*input.Input, *meta.Meta, string, *sx.Pair) *sx.Pair
}
var registry = map[string]*Info{}
// register the parser (info) for later retrieval.
@@ -95,77 +96,58 @@
return false
}
return pi.IsImageFormat
}
-// Parse parses some input and returns a slice of block nodes.
-func Parse(inp *input.Input, m *meta.Meta, syntax string, hi config.HTMLInsecurity) ast.BlockSlice {
- if obj := Get(syntax).Parse(inp, m, syntax); obj != nil {
- bs, err := sztrans.GetBlockSlice(obj)
- if err == nil {
- Clean(&bs, hi.AllowHTML(syntax))
- return bs
- }
- log.Printf("sztrans error: %v, for %v\n", err, obj)
- }
- return nil
-}
-
-// ParseDescriptionAST returns a suitable description stored in the metadata as an inline slice.
-// This is done for an image in most cases.
-func ParseDescriptionAST(m *meta.Meta) ast.InlineSlice {
- if m == nil {
- return nil
- }
- if summary, found := m.Get(meta.KeySummary); found {
- return ast.ParseSpacedText(string(summary))
- }
- if title, found := m.Get(meta.KeyTitle); found {
- return ast.ParseSpacedText(string(title))
- }
- return ast.InlineSlice{&ast.TextNode{Text: "Zettel without title/summary: " + m.Zid.String()}}
-}
+// Parse parses some input and returns both a Sx.Object and a slice of block nodes.
+func Parse(inp *input.Input, m *meta.Meta, syntax string, alst *sx.Pair) *sx.Pair {
+ return Get(syntax).Parse(inp, m, syntax, alst)
+}
+
+// SymAllowHTML signals a parser to allow HTML content during parsing.
+var SymAllowHTML = sx.MakeSymbol("ALLOW-HTML")
// ParseDescription returns a suitable description stored in the metadata as an inline list.
// This is done for an image in most cases.
func ParseDescription(m *meta.Meta) *sx.Pair {
if m == nil {
return nil
}
if summary, found := m.Get(meta.KeySummary); found {
- return sx.Cons(zsx.MakeText(ast.NormalizedSpacedText(string(summary))), sx.Nil())
+ return sx.Cons(zsx.MakeText(sz.NormalizedSpacedText(string(summary))), sx.Nil())
}
if title, found := m.Get(meta.KeyTitle); found {
- return sx.Cons(zsx.MakeText(ast.NormalizedSpacedText(string(title))), sx.Nil())
+ return sx.Cons(zsx.MakeText(sz.NormalizedSpacedText(string(title))), sx.Nil())
}
return sx.Cons(zsx.MakeText("Zettel without title/summary: "+m.Zid.String()), sx.Nil())
}
// ParseZettel parses the zettel based on the syntax.
-func ParseZettel(ctx context.Context, zettel zettel.Zettel, syntax string, rtConfig config.Config) *ast.ZettelNode {
+func ParseZettel(ctx context.Context, zettel zettel.Zettel, syntax string, rtConfig config.Config) *ast.Zettel {
m := zettel.Meta
inhMeta := m
if rtConfig != nil {
inhMeta = rtConfig.AddDefaultValues(ctx, inhMeta)
}
if syntax == "" {
syntax = string(inhMeta.GetDefault(meta.KeySyntax, meta.DefaultSyntax))
}
+ var alst *sx.Pair
+ if rtConfig != nil && rtConfig.GetHTMLInsecurity().AllowHTML(syntax) {
+ alst = alst.Cons(sx.Cons(SymAllowHTML, nil))
+ }
+
parseMeta := inhMeta
if syntax == meta.ValueSyntaxNone {
parseMeta = m
}
- hi := config.NoHTML
- if rtConfig != nil {
- hi = rtConfig.GetHTMLInsecurity()
- }
- bs := Parse(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax, hi)
- return &ast.ZettelNode{
- Meta: m,
- Content: zettel.Content,
- Zid: m.Zid,
- InhMeta: inhMeta,
- BlocksAST: bs,
- Syntax: syntax,
+ rootNode := Parse(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax, alst)
+ return &ast.Zettel{
+ Meta: m,
+ Content: zettel.Content,
+ Zid: m.Zid,
+ InhMeta: inhMeta,
+ Blocks: rootNode,
+ Syntax: syntax,
}
}
Index: internal/parser/plain.go
==================================================================
--- internal/parser/plain.go
+++ internal/parser/plain.go
@@ -19,10 +19,11 @@
"bytes"
"t73f.de/r/sx"
"t73f.de/r/sx/sxreader"
"t73f.de/r/zsc/domain/meta"
+ "t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
)
func init() {
@@ -38,11 +39,11 @@
Name: meta.ValueSyntaxHTML,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
- Parse: parsePlainHTML,
+ Parse: parsePlain,
})
register(&Info{
Name: meta.ValueSyntaxCSS,
AltNames: []string{},
IsASTParser: false,
@@ -66,38 +67,43 @@
IsImageFormat: false,
Parse: parsePlainSxn,
})
}
-func parsePlain(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair {
- return doParsePlain(inp, syntax, zsx.SymVerbatimCode)
-}
-func parsePlainHTML(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair {
- return doParsePlain(inp, syntax, zsx.SymVerbatimHTML)
-}
-func doParsePlain(inp *input.Input, syntax string, kind *sx.Symbol) *sx.Pair {
- return zsx.MakeBlock(zsx.MakeVerbatim(
- kind,
- sx.Cons(sx.Cons(sx.MakeString(""), sx.MakeString(syntax)), sx.Nil()),
- string(inp.ScanLineContent()),
- ))
-}
-
-func parsePlainSVG(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair {
+func parsePlain(inp *input.Input, _ *meta.Meta, syntax string, alst *sx.Pair) *sx.Pair {
+ result := sz.ParsePlainBlocks(inp, syntax)
+ if syntax == meta.ValueSyntaxHTML && alst.Assoc(SymAllowHTML) == nil {
+ zsx.WalkIt(removeHTMLVisitor{}, result, nil)
+ }
+ return result
+}
+
+type removeHTMLVisitor struct{}
+
+func (removeHTMLVisitor) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
+ if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol && zsx.SymVerbatimHTML.IsEqualSymbol(sym) {
+ node.SetCar(zsx.SymVerbatimCode)
+ return true
+ }
+ return false
+}
+func (removeHTMLVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
+
+func parsePlainSVG(inp *input.Input, _ *meta.Meta, syntax string, _ *sx.Pair) *sx.Pair {
is := parseSVGInlines(inp, syntax)
if is == nil {
- return nil
+ return zsx.MakeBlock()
}
return zsx.MakeBlock(zsx.MakeParaList(is))
}
func parseSVGInlines(inp *input.Input, syntax string) *sx.Pair {
svgSrc := scanSVG(inp)
if svgSrc == "" {
return nil
}
- return sx.Cons(zsx.MakeEmbedBLOB(nil, syntax, svgSrc, nil), sx.Nil())
+ return sx.Cons(zsx.MakeEmbedBLOBuncode(nil, syntax, svgSrc, nil), sx.Nil())
}
func scanSVG(inp *input.Input) string {
inp.SkipSpace()
pos := inp.Pos
@@ -110,11 +116,11 @@
return string(inp.Src[pos:])
}
return ""
}
-func parsePlainSxn(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair {
+func parsePlainSxn(inp *input.Input, _ *meta.Meta, syntax string, _ *sx.Pair) *sx.Pair {
rd := sxreader.MakeReader(bytes.NewReader(inp.Src))
_, err := rd.ReadAll()
var blocks sx.ListBuilder
blocks.Add(zsx.MakeVerbatim(
Index: internal/parser/plain_test.go
==================================================================
--- internal/parser/plain_test.go
+++ internal/parser/plain_test.go
@@ -14,38 +14,72 @@
package parser_test
import (
"testing"
+ "t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
- "zettelstore.de/z/internal/config"
- "zettelstore.de/z/internal/encoder"
"zettelstore.de/z/internal/parser"
)
-func TestParseSVG(t *testing.T) {
+func TestParsePlain(t *testing.T) {
testCases := []struct {
- name string
- src string
- exp string
+ name string
+ syntax string
+ src string
+ allowHTML bool
+ exp string
}{
- {"common", "