Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.22.0 To trunk
2025-10-20
| ||
16:46 | Implement evaluators for links and verbatim-eval-draw as a sz-based walker ... (Leaf check-in: 9c53c97eb3 user: stern tags: trunk) | |
13:19 | Remove almost all external ast references in evaluator ... (check-in: 845172de7d user: stern tags: trunk) | |
2025-07-08
| ||
13:41 | Fix manual w.r.t. key types ... (check-in: c997eeffe6 user: stern tags: trunk) | |
2025-07-07
| ||
08:54 | Version 0.22.0 ... (check-in: 5336da12eb user: stern tags: trunk, release, v0.22.0) | |
07:31 | Update dependencies ... (check-in: 46803d88ec user: stern tags: trunk) | |
Deleted .github/dependabot.yml.
|
| < < < < < < < < < < < < |
Changes to VERSION.
|
| | | 1 | 0.23.0-dev |
Changes to cmd/cmd_file.go.
︙ | ︙ | |||
43 44 45 46 47 48 49 50 51 52 53 54 55 56 | zettel.Zettel{ Meta: m, Content: zettel.NewContent(inp.Src[inp.Pos:]), }, string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), nil, ) 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 } | > > | < | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | zettel.Zettel{ Meta: m, Content: zettel.NewContent(inp.Src[inp.Pos:]), }, string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), nil, ) parser.Clean(z.Blocks, false) parser.CleanAST(&z.BlocksAST, false) 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 } if err = encdr.WriteZettel(os.Stdout, z); err != nil { return 2, err } fmt.Println() return 0, nil } |
︙ | ︙ |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
91 92 93 94 95 96 97 98 99 100 | webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) 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)) } // Web user interface if !authManager.IsReadonly() { | > > | | | | | | | | | | | | | | | | | | | | | | | | | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) 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(!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(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(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)) } } |
︙ | ︙ |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 6 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 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. Therefore, only the owner of the computer on which Zettelstore runs can change this information. |
︙ | ︙ | |||
47 48 49 50 51 52 53 | During startup, __X__ is incremented, starting with one, until no key is found. This allows you to configure than one box. 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). | < | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | During startup, __X__ is incremented, starting with one, until no key is found. This allows you to configure than one box. 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). Sets [[''log-level''|#log-level]] to ""debug"". Enables [[''runtime-profiling''|#runtime-profiling]]. Do not enable it for a production server. Default: ""false"" ; [!default-dir-box-type|''default-dir-box-type''] |
︙ | ︙ |
Changes to docs/manual/00001004051000.zettel.
1 2 3 4 5 6 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20250828135353 === ``zettelstore run`` This starts the web service. ``` zettelstore run [-a PORT] [-c CONFIGFILE] [-d DIR] [-debug] [-p PORT] [-r] [-v] ``` |
︙ | ︙ | |||
22 23 24 25 26 27 28 | Default: tries to read the following files in the ""current directory"": ''zettelstore.cfg'', ''zsconfig.txt'', ''zscfg.txt'', ''_zscfg'', and ''.zscfg''. ; [!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''] | | | < < | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | Default: tries to read the following files in the ""current directory"": ''zettelstore.cfg'', ''zsconfig.txt'', ''zscfg.txt'', ''_zscfg'', and ''.zscfg''. ; [!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 debugging of the internal web server. Same as setting [[''debug-mode''|00001004010000#debug-mode]] to ""true"". ; [!p|''-p PORT''] : Specifies the integer value ''PORT'' as the TCP port, where the Zettelstore web server listens for requests. Default: 23123. Zettelstore listens only on ''127.0.0.1'', e.g. only requests from the current computer will be processed. If you want to listen on network card to process requests from other computer, please use [[''listen-addr''|00001004010000#listen-addr]] of the configuration file as described below. |
︙ | ︙ |
Changes to docs/manual/00001006030000.zettel.
1 2 3 4 5 6 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 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]] | ''-zids'' | [[IdentifierSet|00001006032500]] | any other suffix | [[EString|00001006031500]] |
︙ | ︙ |
Changes to docs/manual/00001010000000.zettel.
1 2 3 4 5 6 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 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. === Local first The Zettelstore is designed to run on your local computer. |
︙ | ︙ | |||
61 62 63 64 65 66 67 | But you can put a server in front of it, which is able to handle encryption. Most generic web server software allow this. 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. | | | 61 62 63 64 65 66 67 68 | But you can put a server in front of it, which is able to handle encryption. Most generic web server software allow this. 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, also for encryption|00001010090100]] |
Changes to docs/manual/00001010090100.zettel.
1 | id: 00001010090100 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010090100 title: External server, also to encrypt message transport role: manual tags: #configuration #encryption #manual #security #zettelstore syntax: zmk created: 20210126175322 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. In most cases, you need to deploy a ""public-key encryption"" process, where your side publishes a public encryption key that only works with a corresponding private decryption key. Technically, this is not trivial. |
︙ | ︙ | |||
65 66 67 68 69 70 71 | } ``` 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. | > > > > > > > > > > > > > | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } ``` 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. |
Changes to docs/manual/00001012053800.zettel.
1 2 3 4 5 6 | id: 00001012053800 title: API: Retrieve references of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20250415154139 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012053800 title: API: Retrieve references of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20250415154139 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]]. It may also be referenced within the [[metadata|00001006000000]] of a zettel, when a key of [[type|00001006030000]] [[URL|00001006035000]] is given. |
︙ | ︙ | |||
37 38 39 40 41 42 43 | https://commonmark.org/ https://xkcd.com/927/ https://github.github.com/gfm/ https://spec.commonmark.org/0.31.2/ https://github.com/yuin/goldmark ``` | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | https://commonmark.org/ https://xkcd.com/927/ https://github.github.com/gfm/ https://spec.commonmark.org/0.31.2/ https://github.com/yuin/goldmark ``` 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 https://github.github.com/gfm/ https://spec.commonmark.org/0.31.2/ |
︙ | ︙ |
Changes to docs/manual/00001012920525.zettel.
1 2 3 4 5 6 | id: 00001012920525 title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012920525 title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 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. In contrast to HTML, SHTML is easier to parse and to manipulate. For example, take a look at the SHTML encoding of this page, which is available via the ""Info"" sub-page of this zettel: |
︙ | ︙ | |||
28 29 30 31 32 33 34 | A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms. Before the last element of a list of at least two elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements. This allows a more space economic storage of data. An HTML tag like ``< a href="link">Text</a>`` 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. | | | | 28 29 30 31 32 33 34 35 36 | A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms. Before the last element of a list of at least two elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements. This allows a more space economic storage of data. An HTML tag like ``< a href="link">Text</a>`` 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). |
Changes to docs/manual/00001012931400.zettel.
1 2 3 4 5 6 | id: 00001012931400 title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931400 title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 modified: 20251007144738 === ''PARA'' :::syntax __Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] … '')''. ::: A paragraph is just a list of inline elements. |
︙ | ︙ | |||
27 28 29 30 31 32 33 | ::: === ''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 | | | | < < < < < | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | ::: === ''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]] [[__Block__|00001012931000#block]] … '')''. __UnorderedList__ **=** ''(UNORDERED'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''. __QuotationList__ **=** ''(QUOTATION'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''. ::: === ''DESCRIPTION'' :::syntax __Description__ **=** ''(DESCRIPTION'' [[__Attributes__|00001012931000#attribute]] __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''. ::: A description is a sequence of one or more terms and values. :::syntax |
︙ | ︙ | |||
147 148 149 150 151 152 153 | :::syntax __ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax | | | | 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | :::syntax __ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax __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. Otherwise the (binary) image data is encoded with base64. === ''TRANSCLUDE'' :::syntax __Transclude__ **=** ''(TRANSCLUDE'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] [[__InlineElement__|00001012931600]] … '')''. ::: A transclude list only occurs for a parsed zettel, but not for a evaluated zettel. Evaluating a zettel also means that all transclusions are resolved. __Reference__ denotes the zettel to be transcluded. |
Changes to docs/manual/00001012931600.zettel.
1 2 3 4 5 6 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 modified: 20251007144653 === ''TEXT'' :::syntax __Text__ **=** ''(TEXT'' String '')''. ::: Specifies the string as some text content, including white space characters. |
︙ | ︙ | |||
45 46 47 48 49 50 51 | If the string value is empty, it is an inline transclusion. Otherwise it contains the syntax of an image. The __InlineElement__ at the end of the list is interpreted as describing text. === ''EMBED-BLOB'' :::syntax | | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | If the string value is empty, it is an inline transclusion. Otherwise it contains the syntax of an image. 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,, [[__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. Otherwise a base64 encoding is used. === ''CITE'' :::syntax __CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. ::: The string contains the citation key. |
︙ | ︙ | |||
137 138 139 140 141 142 143 | The string contains text that should be treated as executable code. :::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. | < < < < < < < < < < | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | The string contains text that should be treated as executable code. :::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 __InputLiteral__ **=** ''(LITERAL-INPUT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as input entered by a user. :::syntax __MathLiteral__ **=** ''(LITERAL-MATH'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as special code to be interpreted as mathematical formulas. :::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. |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 6 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 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. Therefore, it will not start Zettelstore. ** **Solution:** Press the ''Ctrl'' key while opening the context menu of the Zettelstore executable with a right-click. A dialog is then opened where you can acknowledge that you understand the possible risks when you start Zettelstore. This dialog is only presented once for a given Zettelstore executable. * **Problem:** When you double-click on the Zettelstore executable icon, Windows complains that Zettelstore is an application from an unknown developer. ** **Solution:** Windows displays a dialog where you can acknowledge possible risks and allow to start Zettelstore. === Authentication * **Problem:** [[Authentication is enabled|00001010040100]] for a local running Zettelstore and there is a valid [[user zettel|00001010040200]] for the owner. But entering user name and password at the [[web user interface|00001014000000]] seems to be ignored, while entering a wrong password will result in an error message. ** **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. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you can find in the menu ""Lists""). |
︙ | ︙ |
Changes to go.mod.
1 2 | module zettelstore.de/z | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | module zettelstore.de/z go 1.25 require ( github.com/fsnotify/fsnotify v1.9.0 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-20251017161911-2f8291157520 t73f.de/r/sxwebs v0.0.0-20251017162422-9f8d0174bc1f t73f.de/r/webs v0.0.0-20250930141330-11da1688d11c t73f.de/r/zero v0.0.0-20251017150835-a8859ec900ed t73f.de/r/zsc v0.0.0-20251020122118-ed75b99a947d t73f.de/r/zsx v0.0.0-20251020123811-57d9a3e9bbb9 ) require ( golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect ) |
Changes to go.sum.
1 2 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= | | | | | | | | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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.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-20251017161911-2f8291157520 h1:XtiOULaPBescy4AI1MgrE/klJca1gtVDK9awrW6zjCc= t73f.de/r/sx v0.0.0-20251017161911-2f8291157520/go.mod h1:qOhNY+S+pcINETviISh5YIEfNUMk81QTDij78fIVa+Q= t73f.de/r/sxwebs v0.0.0-20251017162422-9f8d0174bc1f h1:E5UpgzY4MrjZiHqrctMLi/0xyonimxozFbhQN439Dd0= t73f.de/r/sxwebs v0.0.0-20251017162422-9f8d0174bc1f/go.mod h1:FUgkRA2F031cYtEir1hnyFbZYpGbybaBPdqxWjpxxuI= t73f.de/r/webs v0.0.0-20250930141330-11da1688d11c h1:6bHMcSJPl6mDWHZu2DuiC2FcoOt/+TxxvbIm5E63sPs= t73f.de/r/webs v0.0.0-20250930141330-11da1688d11c/go.mod h1:G3vn6fCTvYWwQby5cVNmXzHlOGhgBDfbbo/9OgIxy0g= t73f.de/r/zero v0.0.0-20251017150835-a8859ec900ed h1:Omh9Beo5pupvpC8yHnvlRlw1CBcWm8PrgWI0uhQ7Xk4= t73f.de/r/zero v0.0.0-20251017150835-a8859ec900ed/go.mod h1:cNaE2o9BWPFqLkmDuYaWrMJQS7GOo+wwmB9y8VfAF6c= t73f.de/r/zsc v0.0.0-20251020122118-ed75b99a947d h1:r6bbERNxOjkUMfRQL3lwhUkJobYR2kXh6pzDQ3tcFTg= t73f.de/r/zsc v0.0.0-20251020122118-ed75b99a947d/go.mod h1:JVgWkDy24MTAgrYqImA2A9DltUV7YsyoKGsSFsti+Yo= t73f.de/r/zsx v0.0.0-20251020123811-57d9a3e9bbb9 h1:bDGcg8PIKd9/YhVClY7K+IFwAJ0Qe4Qkx92yKi3r3DA= t73f.de/r/zsx v0.0.0-20251020123811-57d9a3e9bbb9/go.mod h1:/wqH6y+cX2WnLYs8sqFydWPPiht90Jm1KTg12Rn33jU= |
Changes to internal/ast/ast.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package ast provides the abstract syntax tree for parsed zettel content. package ast | | < < < < < < < < < < < < < < < < < < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package ast provides the abstract syntax tree for parsed zettel content. package ast import "net/url" // Node is the interface, all nodes must implement. type Node interface { WalkChildren(v Visitor) } // BlockNode is the interface that all block nodes must implement. |
︙ | ︙ | |||
87 88 89 90 91 92 93 | 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 ) | < < < < < < < < < < | 69 70 71 72 73 74 75 | 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 ) |
Changes to internal/ast/block.go.
︙ | ︙ | |||
296 297 298 299 300 301 302 | Syntax string Blob []byte } func (*BLOBNode) blockNode() { /* Just a marker */ } // WalkChildren does nothing. | | | 296 297 298 299 300 301 302 303 | Syntax string Blob []byte } func (*BLOBNode) blockNode() { /* Just a marker */ } // WalkChildren does nothing. func (bn *BLOBNode) WalkChildren(v Visitor) { Walk(v, &bn.Description) } |
Added internal/ast/sztrans/szenc.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | //----------------------------------------------------------------------------- // 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 sztrans import ( "fmt" "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 for _, item := range ln.Items { var itemObjs sx.ListBuilder for _, in := range item { itemObjs.Add(t.GetSz(in)) } items.Add(zsx.MakeBlockList(itemObjs.List())) } return zsx.MakeList(mapGetS(mapNestedListKindS, ln.Kind), getAttributes(ln.Attrs), items.List()) } 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, sx.Nil(), 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 { return zsx.MakeBLOB(getAttributes(bn.Attrs), bn.Syntax, bn.Blob, t.getInlineList(bn.Description)) } 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 { return zsx.MakeEmbedBLOB(getAttributes(en.Attrs), en.Syntax, en.Blob, 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, } // 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)) } |
Changes to internal/ast/sztrans/sztrans.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // 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 | | < > > > > > > > > > > > > > > > > > > | | | < | | < | | | | | | | | | | | | | | < < < | | | | | < < | | < < < | | | | | | < < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | // 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 and vice versa. package sztrans import ( "fmt" "t73f.de/r/sx" "t73f.de/r/zsc/sz" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" ) type transformer struct{} // MustGetBlock returns the sz representation as an AST BlockNode. Panic on error. func MustGetBlock(pair *sx.Pair) ast.BlockNode { if pair == nil { return nil } var t transformer if obj := zsx.Walk(&t, pair, nil); !obj.IsNil() { if sxn, isNode := obj.(sxNode); isNode { if bn, ok := sxn.node.(ast.BlockNode); ok { return bn } panic(fmt.Sprintf("no BlockNode AST: %T/%v for %v", sxn.node, sxn.node, pair)) } panic(fmt.Sprintf("no AST for %v: %v", pair, obj)) } panic(fmt.Sprintf("error walking %v", pair)) } // GetBlockSlice returns the sz representation as an 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(node *sx.Pair, _ *sx.Pair) (sx.Object, bool) { if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol { switch sym { case zsx.SymText: if s := zsx.GetText(node); s != "" { return sxNode{&ast.TextNode{Text: s}}, 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, node) case zsx.SymLiteralComment: return handleLiteral(ast.LiteralComment, node) case zsx.SymLiteralInput: return handleLiteral(ast.LiteralInput, node) case zsx.SymLiteralMath: return handleLiteral(ast.LiteralMath, node) case zsx.SymLiteralOutput: return handleLiteral(ast.LiteralOutput, node) case zsx.SymThematic: return sxNode{&ast.HRuleNode{Attrs: zsx.GetAttributes(node.Tail().Head())}}, true case zsx.SymVerbatimComment: return handleVerbatim(ast.VerbatimComment, node) case zsx.SymVerbatimEval: return handleVerbatim(ast.VerbatimEval, node) case zsx.SymVerbatimHTML: return handleVerbatim(ast.VerbatimHTML, node) case zsx.SymVerbatimMath: return handleVerbatim(ast.VerbatimMath, node) case zsx.SymVerbatimCode: return handleVerbatim(ast.VerbatimCode, node) case zsx.SymVerbatimZettel: return handleVerbatim(ast.VerbatimZettel, node) } } return sx.Nil(), false } func handleLiteral(kind ast.LiteralKind, node *sx.Pair) (sx.Object, bool) { if sym, attrs, content := zsx.GetLiteral(node); sym != nil { return sxNode{&ast.LiteralNode{ Kind: kind, Attrs: zsx.GetAttributes(attrs), Content: []byte(content)}}, true } return nil, false } func handleVerbatim(kind ast.VerbatimKind, node *sx.Pair) (sx.Object, bool) { if sym, attrs, content := zsx.GetVerbatim(node); sym != nil { return sxNode{&ast.VerbatimNode{ Kind: kind, Attrs: zsx.GetAttributes(attrs), Content: []byte(content), }}, true } return nil, false } func (t *transformer) VisitAfter(node *sx.Pair, _ *sx.Pair) sx.Object { if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol { switch sym { case zsx.SymBlock: bns := collectBlocks(node.Tail()) return sxNode{&bns} case zsx.SymPara: return sxNode{&ast.ParaNode{Inlines: collectInlines(node.Tail())}} case zsx.SymHeading: return handleHeading(node) case zsx.SymListOrdered: return handleList(ast.NestedListOrdered, node) case zsx.SymListUnordered: return handleList(ast.NestedListUnordered, node) case zsx.SymListQuote: return handleList(ast.NestedListQuote, node) case zsx.SymDescription: return handleDescription(node) case zsx.SymTable: return handleTable(node) case zsx.SymCell: return handleCell(node) case zsx.SymRegionBlock: return handleRegion(ast.RegionSpan, node) case zsx.SymRegionQuote: return handleRegion(ast.RegionQuote, node) case zsx.SymRegionVerse: return handleRegion(ast.RegionVerse, node) case zsx.SymTransclude: return handleTransclude(node) case zsx.SymBLOB: return handleBLOB(node) case zsx.SymLink: return handleLink(node) case zsx.SymEmbed: return handleEmbed(node) case zsx.SymEmbedBLOB: return handleEmbedBLOB(node) case zsx.SymCite: return handleCite(node) case zsx.SymMark: return handleMark(node) case zsx.SymEndnote: return handleEndnote(node) case zsx.SymFormatDelete: return handleFormat(ast.FormatDelete, node) case zsx.SymFormatEmph: return handleFormat(ast.FormatEmph, node) case zsx.SymFormatInsert: return handleFormat(ast.FormatInsert, node) case zsx.SymFormatMark: return handleFormat(ast.FormatMark, node) case zsx.SymFormatQuote: return handleFormat(ast.FormatQuote, node) case zsx.SymFormatSpan: return handleFormat(ast.FormatSpan, node) case zsx.SymFormatSub: return handleFormat(ast.FormatSub, node) case zsx.SymFormatSuper: return handleFormat(ast.FormatSuper, node) case zsx.SymFormatStrong: return handleFormat(ast.FormatStrong, node) } } return node } 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) |
︙ | ︙ | |||
206 207 208 209 210 211 212 | result = append(result, in) } } } return result } | | < < < | < < < < | | | | | | | | < < < < < < < | | | < | | | < < | > | | 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | result = append(result, in) } } } return result } func handleHeading(node *sx.Pair) sx.Object { if level, attrs, inlines, slug, fragment := zsx.GetHeading(node); level > 0 && level < 6 { return sxNode{&ast.HeadingNode{ Level: level, Attrs: zsx.GetAttributes(attrs), Slug: slug, Fragment: fragment, Inlines: collectInlines(inlines), }} } return node } func handleList(kind ast.NestedListKind, node *sx.Pair) sx.Object { if sym, attrs, items := zsx.GetList(node); sym != nil { return sxNode{&ast.NestedListNode{ Kind: kind, Items: collectItemSlices(items), Attrs: zsx.GetAttributes(attrs), }} } return node } 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 { |
︙ | ︙ | |||
271 272 273 274 275 276 277 | result = append(result, items) } } } return result } | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < < | | | | | | | | | | | | | | | | | | | | | < < < < | | | | | | | | | | | | | | | | | | > > > > > > > > > | | | < | < | < | | | | | | < < | | | < < < | | < | > > | > > | | > > > > > | > > > > | | | < < | < < < < | | | | < > | | | > | | > > > > > > > | | < < | | | < < < < < < < < < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < | < | < < | | | | | | < < < | | < < < < < < > | | | | | | | > | | < < < < < < < | | < | | < | | | < | | < | > | 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 | result = append(result, items) } } } return result } func handleDescription(node *sx.Pair) sx.Object { attrs, termsVals := zsx.GetDescription(node) var descs []ast.Description for curr := termsVals; 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: zsx.GetAttributes(attrs), Descriptions: descs, }} } return node } func handleTable(node *sx.Pair) sx.Object { _, headerRow, rowList := zsx.GetTable(node) header := collectRow(headerRow) cols := len(header) var rows []ast.TableRow for curr := range rowList.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, }} } 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(node *sx.Pair) sx.Object { attrs, inlines := zsx.GetCell(node) align := ast.AlignDefault if alignPair := attrs.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(inlines), }} } func handleRegion(kind ast.RegionKind, node *sx.Pair) sx.Object { if sym, attrs, blocks, inlines := zsx.GetRegion(node); sym != nil { return sxNode{&ast.RegionNode{ Kind: kind, Attrs: zsx.GetAttributes(attrs), Blocks: collectBlocks(blocks), Inlines: collectInlines(inlines), }} } return node } func handleTransclude(node *sx.Pair) sx.Object { if attrs, reference, inlines := zsx.GetTransclusion(node); reference != nil { if ref := collectReference(reference); ref != nil { return sxNode{&ast.TranscludeNode{ Attrs: zsx.GetAttributes(attrs), Ref: ref, Inlines: collectInlines(inlines), }} } } return node } func handleBLOB(node *sx.Pair) sx.Object { if attrs, syntax, data, inlines := zsx.GetBLOB(node); data != nil { return sxNode{&ast.BLOBNode{ Attrs: zsx.GetAttributes(attrs), Description: collectInlines(inlines), Syntax: syntax, Blob: data, }} } return node } func handleLink(node *sx.Pair) sx.Object { if attrs, reference, inlines := zsx.GetLink(node); reference != nil { if ref := collectReference(reference); ref != nil { return sxNode{&ast.LinkNode{ Attrs: zsx.GetAttributes(attrs), Ref: ref, Inlines: collectInlines(inlines), }} } } return node } func handleEmbed(node *sx.Pair) sx.Object { if attrs, reference, syntax, inlines := zsx.GetEmbed(node); reference != nil { if ref := collectReference(reference); ref != nil { return sxNode{&ast.EmbedRefNode{ Attrs: zsx.GetAttributes(attrs), Ref: ref, Syntax: syntax, Inlines: collectInlines(inlines), }} } } return node } func handleEmbedBLOB(node *sx.Pair) sx.Object { if attrs, syntax, data, inlines := zsx.GetEmbedBLOB(node); data != nil { return sxNode{&ast.EmbedBLOBNode{ Attrs: zsx.GetAttributes(attrs), Syntax: syntax, Blob: data, Inlines: collectInlines(inlines), }} } return node } 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 collectReference(node *sx.Pair) *ast.Reference { if sym, val := zsx.GetReference(node); sym != nil { ref := ast.ParseReference(val) ref.State = mapRefState[sym] return ref } return nil } func handleCite(node *sx.Pair) sx.Object { if attrs, key, inlines := zsx.GetCite(node); key != "" { return sxNode{&ast.CiteNode{ Attrs: zsx.GetAttributes(attrs), Key: key, Inlines: collectInlines(inlines), }} } return node } func handleMark(node *sx.Pair) sx.Object { if mark, slug, fragment, inlines := zsx.GetMark(node); mark != "" { return sxNode{&ast.MarkNode{ Mark: mark, Slug: slug, Fragment: fragment, Inlines: collectInlines(inlines), }} } return node } func handleEndnote(node *sx.Pair) sx.Object { if attrs, inlines := zsx.GetEndnote(node); inlines != nil { return sxNode{&ast.FootnoteNode{ Attrs: zsx.GetAttributes(attrs), Inlines: collectInlines(inlines), }} } return node } func handleFormat(kind ast.FormatKind, node *sx.Pair) sx.Object { if sym, attrs, inlines := zsx.GetFormat(node); sym != nil { return sxNode{&ast.FormatNode{ Kind: kind, Attrs: zsx.GetAttributes(attrs), Inlines: collectInlines(inlines), }} } return node } type sxNode struct { node ast.Node } func (sxNode) IsNil() bool { return false } func (sxNode) IsAtom() bool { return true } func (sxNode) IsTrue() 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() } |
Added internal/ast/zettel.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //----------------------------------------------------------------------------- // 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 provides the abstract syntax tree for parsed zettel content. package ast import ( "t73f.de/r/sx" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "zettelstore.de/z/internal/zettel" ) // Zettel is the root node of the abstract syntax tree. // It is *not* part of the visitor pattern. 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. BlocksAST BlockSlice // Zettel abstract syntax tree is a sequence of block nodes. Syntax string // Syntax / parser that produced the Ast } |
Changes to internal/box/constbox/base.sxn.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(@@@@ | | | | | | | | | | | | | | | | | | | | | | | | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(@@@@ (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-HEADER (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")) ,(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!")))) ))) |
Changes to internal/box/constbox/constbox.go.
︙ | ︙ | |||
162 163 164 165 166 167 168 | zettel.NewContent(contentDependencies)}, id.ZidBaseTemplate: { constHeader{ meta.KeyTitle: "Zettelstore Base HTML Template", meta.KeyRole: meta.ValueRoleConfiguration, meta.KeySyntax: meta.ValueSyntaxSxn, meta.KeyCreated: "20230510155100", | | | | | | | | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | zettel.NewContent(contentDependencies)}, id.ZidBaseTemplate: { constHeader{ meta.KeyTitle: "Zettelstore Base HTML Template", meta.KeyRole: meta.ValueRoleConfiguration, meta.KeySyntax: meta.ValueSyntaxSxn, meta.KeyCreated: "20230510155100", 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: "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: "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: "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: "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: "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: "20250806182600", meta.KeyVisibility: meta.ValueVisibilityExpert, }, zettel.NewContent(contentListZettelSxn)}, id.ZidErrorTemplate: { constHeader{ meta.KeyTitle: "Zettelstore Error HTML Template", meta.KeyRole: meta.ValueRoleConfiguration, |
︙ | ︙ | |||
253 254 255 256 257 258 259 | zettel.NewContent(contentStartCodeSxn)}, id.ZidSxnBase: { constHeader{ meta.KeyTitle: "Zettelstore Sxn Base Code", meta.KeyRole: meta.ValueRoleConfiguration, meta.KeySyntax: meta.ValueSyntaxSxn, meta.KeyCreated: "20230619132800", | | | 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | zettel.NewContent(contentStartCodeSxn)}, id.ZidSxnBase: { constHeader{ meta.KeyTitle: "Zettelstore Sxn Base Code", meta.KeyRole: meta.ValueRoleConfiguration, meta.KeySyntax: meta.ValueSyntaxSxn, meta.KeyCreated: "20230619132800", meta.KeyModified: "20250806182700", meta.KeyReadOnly: meta.ValueTrue, meta.KeyVisibility: meta.ValueVisibilityExpert, }, zettel.NewContent(contentBaseCodeSxn)}, id.ZidBaseCSS: { constHeader{ meta.KeyTitle: "Zettelstore Base CSS", |
︙ | ︙ |
Changes to internal/box/constbox/delete.sxn.
︙ | ︙ | |||
11 12 13 14 15 16 17 | ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Delete Zettel " ,zid)) (p "Do you really want to delete this zettel?") ,@(if shadowed-box | | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Delete Zettel " ,zid)) (p "Do you really want to delete this zettel?") ,@(if shadowed-box `((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")) (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")) (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")))) ) |
Changes to internal/box/constbox/form.sxn.
︙ | ︙ | |||
9 10 11 12 13 14 15 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) | | | | | | | | | | | | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) (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") (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") (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") (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") (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") (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") (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"))) )) ) |
Changes to internal/box/constbox/info.sxn.
︙ | ︙ | |||
10 11 12 13 14 15 16 | ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Information for Zettel " ,zid) (p | | | | | | | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Information for Zettel " ,zid) (p ,(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 " · [") ,(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 " · ") ,(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") ,@(if local-links `((h3 "Local") (ul ,@(map wui-local-link local-links)))) ,@(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))) ) (h2 "Parts and encodings") ,(wui-enc-matrix enc-eval) (h3 "Parsed (not evaluated)") ,(wui-enc-matrix enc-parsed) ,@(if shadow-links `((h2 "Shadowed Boxes") |
︙ | ︙ |
Changes to internal/box/constbox/listzettel.sxn.
︙ | ︙ | |||
9 10 11 12 13 14 15 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) | | | | | | | | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; 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) (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)) ) ,@(if (symbol-bound? '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)) ) ,@(if (symbol-bound? '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))) ,(if (symbol-bound? 'data-url) `(@L "Other encodings" ,(if (> num-entries 3) `(@L " of these " ,num-entries " entries: ") ": ") ,(wui-href data-url "data") ", " ,(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"))) ) ) ) ) |
Changes to internal/box/constbox/login.sxn.
︙ | ︙ | |||
9 10 11 12 13 14 15 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Login")) | | | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; 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")))) ) ) |
Changes to internal/box/constbox/wuicode.sxn.
︙ | ︙ | |||
15 16 17 18 19 20 21 | ;; wui-list-item returns the argument as a HTML list item. (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) | | > > > | | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | ;; wui-list-item returns the argument as a HTML list item. (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)))) ;; 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 ,(wui-href l l))) ;; wui-link takes a link (title . url) and returns a HTML reference. (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))) ;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside ;; a table data item. (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 ,(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)))) ;; 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))))) ;; 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)))) ;; wui-meta-desc returns a HTML description list made from the list of pairs ;; given. |
︙ | ︙ | |||
65 66 67 68 69 70 71 | (lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row)))) matrix))) ;; 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) | | | | | | | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | (lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row)))) matrix))) ;; 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) (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. ;; Referenced in function "ROLE-DEFAULT-meta". (defvar CSS-ROLE-map '()) ;; ROLE-DEFAULT-meta returns some metadata for the base template. Any role ;; 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)))))) ) ) ) ) ;; ACTION-SEPARATOR defines a HTML value that separates actions links. (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 " · ") ,(wui-href copy-url "Copy")))) ,@(let ((sequel-url (resolve-symbol 'sequel-url frame))) (if (defined? sequel-url) `((@H " · ") ,(wui-href sequel-url "Sequel")))) ,@(let ((folge-url (resolve-symbol 'folge-url frame))) (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 ,(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 ,(wui-href (query->url (concat "role:" title)) "Zettel")) ) ) ) ) ;; ROLE-DEFAULT-heading returns the default text for headings, below the ;; references of a zettel. In most cases it should be called from an |
︙ | ︙ |
Changes to internal/box/constbox/zettel.sxn.
︙ | ︙ | |||
10 11 12 13 14 15 16 | ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading) | | | | | | | | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading) (div ((class "zs-meta")) ,@(if (symbol-bound? 'edit-url) `(,(wui-href edit-url "Edit") (@H " · "))) ,zid (@H " · ") ,(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 " → ") ,(wui-href folge-role-url meta-folge-role))) ")" ,@(if tag-refs `((@H " · ") ,@tag-refs)) (@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)) ,@(ROLE-DEFAULT-heading (current-frame)) ,@(let* ((frame (current-frame))(reh (resolve-symbol 'ROLE-EXTRA-heading frame))) (if (defined? reh) (reh frame))) ) ) ,@content ,endnotes ,@(if (or folge-links sequel-links back-links subordinate-links) `((nav ,@(if folge-links `((details ((,folge-open)) (summary ,(wui-optional-link "Folgezettel" 'folge-query-url)) (ul ,@(map wui-item-link folge-links))))) ,@(if sequel-links `((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))))) )) ) ) |
Changes to internal/box/manager/collect.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- package manager import ( "strings" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/id/idset" | > > > < | | | > | | | | > > | | > > | | > | | > | | > | | > | | > | > > > > > > > > > > > > < < < < < < < < < < < < < < < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | //----------------------------------------------------------------------------- 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/box/manager/store" ) type collectData struct { refs *idset.Set words store.WordSet urls store.WordSet } func (data *collectData) initialize() { data.refs = idset.New() data.words = store.NewWordSet() data.urls = store.NewWordSet() } func collectZettelIndexData(blocks *sx.Pair, data *collectData) { zsx.WalkIt(data, blocks, nil) } func (data *collectData) VisitBefore(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) VisitAfter(*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) } } |
Changes to internal/box/manager/indexer.go.
︙ | ︙ | |||
148 149 150 151 152 153 154 | return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) { var cData collectData cData.initialize() if mustIndexZettel(zettel.Meta) { | | > | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) { var cData collectData cData.initialize() if mustIndexZettel(zettel.Meta) { zn := parser.ParseZettel(ctx, zettel, "", mgr.rtConfig) collectZettelIndexData(zn.Blocks, &cData) } m := zettel.Meta zi := store.NewZettelIndex(m) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) |
︙ | ︙ |
Changes to internal/collect/collect.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | // Package collect provides functions to collect items from a syntax tree. package collect import ( "iter" | | > | | | | | | | | | | > | | > > | | > > | | | < | | > | > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // Package collect provides functions to collect items from a syntax tree. package collect import ( "iter" "t73f.de/r/sx" "t73f.de/r/zsx" ) type refYielder struct { yield func(*sx.Pair) bool stop bool } // ReferenceSeq returns an iterator of all references mentioned in the given // 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} zsx.WalkIt(&yielder, block, nil) } } // Visit all node to collect data for the summary. func (y *refYielder) VisitBefore(node *sx.Pair, _ *sx.Pair) bool { if y.stop { 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) VisitAfter(*sx.Pair, *sx.Pair) {} |
Changes to internal/collect/collect_test.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 | // Package collect_test provides some unit test for collectors. package collect_test import ( "slices" "testing" | > > > | | | > | < | | | | | | | < | < | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // Package collect_test provides some unit test for collectors. package collect_test import ( "slices" "testing" "t73f.de/r/sx" "t73f.de/r/zsc/sz" "t73f.de/r/zsx" "zettelstore.de/z/internal/collect" ) 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() summary := slices.Collect(collect.ReferenceSeq(nil)) if len(summary) != 0 { t.Error("No references expected, but got:", summary) } 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.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) } 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) } } |
Changes to internal/collect/order.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect | > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect import ( "t73f.de/r/sx" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" ) // 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 lb.List() } 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(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 } // OrderAST of internal links within the given zettel. func OrderAST(bns *ast.BlockSlice) (result []*ast.LinkNode) { for _, bn := range *bns { ln, ok := bn.(*ast.NestedListNode) if !ok { continue } switch ln.Kind { case ast.NestedListOrdered, ast.NestedListUnordered: for _, is := range ln.Items { if ln := firstItemZettelLinkAST(is); ln != nil { result = append(result, ln) } } } } return result } func firstItemZettelLinkAST(is ast.ItemSlice) *ast.LinkNode { for _, in := range is { if pn, ok := in.(*ast.ParaNode); ok { if ln := firstInlineZettelLinkAST(pn.Inlines); ln != nil { return ln } } } return nil } func firstInlineZettelLinkAST(is ast.InlineSlice) (result *ast.LinkNode) { for _, inl := range is { switch in := inl.(type) { case *ast.LinkNode: return in case *ast.EmbedRefNode: result = firstInlineZettelLinkAST(in.Inlines) case *ast.EmbedBLOBNode: result = firstInlineZettelLinkAST(in.Inlines) case *ast.CiteNode: result = firstInlineZettelLinkAST(in.Inlines) case *ast.FootnoteNode: // Ignore references in footnotes continue case *ast.FormatNode: result = firstInlineZettelLinkAST(in.Inlines) default: continue } if result != nil { return result } } |
︙ | ︙ |
Changes to internal/encoder/encoder.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // Package encoder provides a generic interface to encode the abstract syntax // tree into some text form. package encoder import ( "io" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "zettelstore.de/z/internal/ast" ) // 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. | > > | | > > > > > > | | | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | // Package encoder provides a generic interface to encode the abstract syntax // tree into some text form. 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" "zettelstore.de/z/internal/ast/sztrans" ) // 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.Zettel) error // WriteMeta encodes just the metadata. WriteMeta(io.Writer, *meta.Meta) error // WriteSz encodes SZ represented zettel content. WriteSz(io.Writer, *sx.Pair) error // WiteBlocks encodes a block slice, i.e. the zettel content. // // This method is deprecated and will be removed, if all implementations // of WriteSz work correctly. WriteBlocks(io.Writer, *ast.BlockSlice) 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: sztrans.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: sztrans.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: sztrans.NewSzTransformer()} case api.EncoderText: return (*TextEncoder)(nil) case api.EncoderZmk: return (*zmkEncoder)(nil) } return nil } |
︙ | ︙ |
Changes to internal/encoder/encoder_blob_test.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | import ( "testing" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import ( "testing" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "zettelstore.de/z/internal/parser" ) type blobTestCase struct { descr string syntax string blob []byte |
︙ | ︙ | |||
40 41 42 43 44 45 46 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, 0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, }, expect: expectMap{ encoderHTML: `<p><img alt="Minimal PNG" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="></p>`, | | | | > | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, 0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, }, expect: expectMap{ encoderHTML: `<p><img alt="Minimal PNG" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="></p>`, 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'.`, }, }, } 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) node, bs := parser.Parse(inp, m, tc.syntax) checkEncodings(t, testNum, node, false, tc.descr, tc.expect, "???") checkEncodingsAST(t, testNum+1000, bs, false, tc.descr, tc.expect, "???") } } |
Changes to internal/encoder/encoder_block_test.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package encoder_test var tcsBlock = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing", zmk: "", expect: expectMap{ encoderHTML: "", | > > > > > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // under this license. // // 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: "", expect: expectMap{ encoderHTML: "", |
︙ | ︙ | |||
65 66 67 68 69 70 71 | { descr: "Simple Heading", zmk: `=== Top Job`, expect: expectMap{ encoderHTML: "<h2 id=\"top-job\">Top Job</h2>", encoderMD: "# Top Job", encoderSz: `(BLOCK (HEADING 1 () "top-job" "top-job" (TEXT "Top Job")))`, | | | | | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | { descr: "Simple Heading", zmk: `=== Top Job`, expect: expectMap{ encoderHTML: "<h2 id=\"top-job\">Top Job</h2>", encoderMD: "# Top Job", encoderSz: `(BLOCK (HEADING 1 () "top-job" "top-job" (TEXT "Top Job")))`, encoderSHTML: `((h2 ((id . "top-job")) "Top Job"))`, encoderText: `Top Job`, encoderZmk: useZmk, }, }, { descr: "Simple List", zmk: "* A\n* B\n* C", expect: expectMap{ encoderHTML: "<ul><li>A</li><li>B</li><li>C</li></ul>", encoderMD: "* A\n* B\n* 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, }, }, { descr: "Nested List", zmk: "* T1\n*# T2\n* T3\n** T4\n** T5\n* T6", expect: expectMap{ encoderHTML: `<ul><li><p>T1</p><ol><li>T2</li></ol></li><li><p>T3</p><ul><li>T4</li><li>T5</li></ul></li><li><p>T6</p></li></ul>`, encoderMD: "* T1\n 1. T2\n* T3\n * T4\n * T5\n* 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, }, }, { descr: "Sequence of two lists", zmk: "* Item1.1\n* Item1.2\n* Item1.3\n\n* Item2.1\n* Item2.2", expect: expectMap{ encoderHTML: "<ul><li>Item1.1</li><li>Item1.2</li><li>Item1.3</li><li>Item2.1</li><li>Item2.2</li></ul>", encoderMD: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* 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", }, }, { descr: "Simple horizontal rule", |
︙ | ︙ | |||
125 126 127 128 129 130 131 | { descr: "Thematic break with attribute", zmk: `---{lang="zmk"}`, expect: expectMap{ encoderHTML: `<hr lang="zmk">`, encoderMD: "---", encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`, | | | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | { descr: "Thematic break with attribute", zmk: `---{lang="zmk"}`, expect: expectMap{ encoderHTML: `<hr lang="zmk">`, encoderMD: "---", encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`, encoderSHTML: `((hr ((lang . "zmk"))))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "No list after paragraph", zmk: "Text\n*abc", |
︙ | ︙ | |||
148 149 150 151 152 153 154 | }, { descr: "A list after paragraph", zmk: "Text\n# abc", expect: expectMap{ encoderHTML: "<p>Text</p><ol><li>abc</li></ol>", encoderMD: "Text\n\n1. abc", | | | | 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | }, { descr: "A list after paragraph", zmk: "Text\n# abc", expect: expectMap{ encoderHTML: "<p>Text</p><ol><li>abc</li></ol>", encoderMD: "Text\n\n1. abc", encoderSz: `(BLOCK (PARA (TEXT "Text")) (ORDERED () (BLOCK (PARA (TEXT "abc")))))`, encoderSHTML: `((p "Text") (ol (li "abc")))`, encoderText: "Text\nabc", encoderZmk: useZmk, }, }, { descr: "Simple List Quote", zmk: "> ToBeOrNotToBe", expect: expectMap{ encoderHTML: "<blockquote>ToBeOrNotToBe</blockquote>", encoderMD: "> ToBeOrNotToBe", encoderSz: `(BLOCK (QUOTATION () (BLOCK (PARA (TEXT "ToBeOrNotToBe")))))`, encoderSHTML: `((blockquote (@L "ToBeOrNotToBe")))`, encoderText: "ToBeOrNotToBe", encoderZmk: useZmk, }, }, { descr: "Simple Quote Block", |
︙ | ︙ | |||
257 258 259 260 261 262 263 | { descr: "Simple Verbatim Eval", zmk: "~~~\nHello\nWorld\n~~~", expect: expectMap{ encoderHTML: "<pre><code class=\"zs-eval\">Hello\nWorld</code></pre>", encoderMD: "", encoderSz: `(BLOCK (VERBATIM-EVAL () "Hello\nWorld"))`, | | | | 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | { descr: "Simple Verbatim Eval", zmk: "~~~\nHello\nWorld\n~~~", expect: expectMap{ encoderHTML: "<pre><code class=\"zs-eval\">Hello\nWorld</code></pre>", encoderMD: "", encoderSz: `(BLOCK (VERBATIM-EVAL () "Hello\nWorld"))`, encoderSHTML: "((pre (code ((class . \"zs-eval\")) \"Hello\\nWorld\")))", encoderText: "Hello\nWorld", encoderZmk: useZmk, }, }, { descr: "Simple Verbatim Math", zmk: "$$$\nHello\n\\LaTeX\n$$$", expect: expectMap{ encoderHTML: "<pre><code class=\"zs-math\">Hello\n\\LaTeX</code></pre>", encoderMD: "", encoderSz: `(BLOCK (VERBATIM-MATH () "Hello\n\\LaTeX"))`, encoderSHTML: "((pre (code ((class . \"zs-math\")) \"Hello\\n\\\\LaTeX\")))", encoderText: "Hello\n\\LaTeX", encoderZmk: useZmk, }, }, { descr: "Simple Description List", zmk: "; Zettel\n: Paper\n: Note\n; Zettelkasten\n: Slip box", |
︙ | ︙ | |||
316 317 318 319 320 321 322 | }, { descr: "Simple Table", zmk: "|c1|c2|c3\n|d1||d3", expect: expectMap{ encoderHTML: `<table><tbody><tr><td>c1</td><td>c2</td><td>c3</td></tr><tr><td>d1</td><td></td><td>d3</td></tr></tbody></table>`, encoderMD: "", | | | | | | | > > > > > > > > > > > > < < < < | 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 | }, { descr: "Simple Table", zmk: "|c1|c2|c3\n|d1||d3", expect: expectMap{ encoderHTML: `<table><tbody><tr><td>c1</td><td>c2</td><td>c3</td></tr><tr><td>d1</td><td></td><td>d3</td></tr></tbody></table>`, encoderMD: "", 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, }, }, { descr: "Table with alignment and comment", zmk: `|h1>|=h2|h3:| |%--+---+---+ |<c1|c2|:c3| |f1|f2|=f3`, expect: expectMap{ encoderHTML: `<table><thead><tr><th class="right">h1</th><th>h2</th><th class="center">h3</th></tr></thead><tbody><tr><td class="left">c1</td><td>c2</td><td class="center">c3</td></tr><tr><td class="right">f1</td><td>f2</td><td class="center">=f3</td></tr></tbody></table>`, 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")))))`, encoderText: "h1 h2 h3\nc1 c2 c3\nf1 f2 =f3", encoderZmk: /*`|=h1>|=h2|=h3: |<c1|c2|c3 |f1|f2|=f3`,*/ `|=>h1|=h2|=:h3 |<c1|c2|:c3 |>f1|f2|:=f3`, }, }, { descr: "Simple Endnote", zmk: `Text[^Endnote]`, expect: expectMap{ encoderHTML: "<p>Text<sup id=\"fnref:1\"><a class=\"zs-noteref\" href=\"#fn:1\" role=\"doc-noteref\">1</a></sup></p><ol class=\"zs-endnotes\"><li class=\"zs-endnote\" id=\"fn:1\" role=\"doc-endnote\" value=\"1\">Endnote <a class=\"zs-endnote-backref\" href=\"#fnref:1\" role=\"doc-backlink\">\u21a9\ufe0e</a></li></ol>", 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\"))))", encoderText: "Text Endnote", encoderZmk: useZmk, }, }, { descr: "Nested Endnotes", zmk: `Text[^Endnote[^Nested]]`, expect: expectMap{ encoderHTML: "<p>Text<sup id=\"fnref:1\"><a class=\"zs-noteref\" href=\"#fn:1\" role=\"doc-noteref\">1</a></sup></p><ol class=\"zs-endnotes\"><li class=\"zs-endnote\" id=\"fn:1\" role=\"doc-endnote\" value=\"1\">Endnote<sup id=\"fnref:2\"><a class=\"zs-noteref\" href=\"#fn:2\" role=\"doc-noteref\">2</a></sup> <a class=\"zs-endnote-backref\" href=\"#fnref:1\" role=\"doc-backlink\">\u21a9\ufe0e</a></li><li class=\"zs-endnote\" id=\"fn:2\" role=\"doc-endnote\" value=\"2\">Nested <a class=\"zs-endnote-backref\" href=\"#fnref:2\" role=\"doc-backlink\">\u21a9\ufe0e</a></li></ol>", 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\"))))", encoderText: "Text Endnote Nested", encoderZmk: useZmk, }, }, { descr: "Transclusion", zmk: `{{{http://example.com/image}}}{width="100px"}`, expect: expectMap{ encoderHTML: `<p><img class="external" src="http://example.com/image" width="100px"></p>`, encoderMD: "", encoderSz: `(BLOCK (TRANSCLUDE (("width" . "100px")) (EXTERNAL "http://example.com/image")))`, encoderSHTML: `((p (img ((class . "external") (src . "http://example.com/image") (width . "100px")))))`, encoderText: "", encoderZmk: useZmk, }, }, { descr: "A paragraph with a inline comment only should be empty in HTML", zmk: `%% Comment`, expect: expectMap{ // encoderHTML: ``, encoderSz: `(BLOCK (PARA (LITERAL-COMMENT () "Comment")))`, // encoderSHTML: ``, encoderText: "", encoderZmk: useZmk, }, }, { descr: "Zettel with syntax HTML", zmk: "<h1>Hello</h1>\nWorld\n", syntax: meta.ValueSyntaxHTML, expect: expectMap{ encoderHTML: ``, encoderSz: `(BLOCK)`, encoderSHTML: `()`, encoderText: "", encoderZmk: "", }, }, { descr: "", zmk: ``, expect: expectMap{ encoderHTML: ``, encoderSz: `(BLOCK)`, encoderSHTML: `()`, encoderText: "", encoderZmk: useZmk, }, }, } |
Changes to internal/encoder/encoder_inline_test.go.
︙ | ︙ | |||
161 162 163 164 165 166 167 | { descr: "Quotes formatting (german)", zmk: `""quotes""{lang=de}`, expect: expectMap{ encoderHTML: `<p><span lang="de">„quotes“</span></p>`, encoderMD: "„quotes“", encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes"))))`, | | | 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | { descr: "Quotes formatting (german)", zmk: `""quotes""{lang=de}`, expect: expectMap{ encoderHTML: `<p><span lang="de">„quotes“</span></p>`, encoderMD: "„quotes“", encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes"))))`, encoderSHTML: `((p (span ((lang . "de")) (@H "„") "quotes" (@H "“"))))`, encoderText: `quotes`, encoderZmk: `""quotes""{lang="de"}`, }, }, { descr: "Empty quotes (default)", zmk: `""""`, |
︙ | ︙ | |||
185 186 187 188 189 190 191 | { descr: "Empty quotes (unknown)", zmk: `""""{lang=unknown}`, expect: expectMap{ encoderHTML: `<p><span lang="unknown">""</span></p>`, encoderMD: """", encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "unknown")))))`, | | | 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | { descr: "Empty quotes (unknown)", zmk: `""""{lang=unknown}`, expect: expectMap{ encoderHTML: `<p><span lang="unknown">""</span></p>`, encoderMD: """", encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "unknown")))))`, encoderSHTML: `((p (span ((lang . "unknown")) (@H """ """))))`, encoderText: ``, encoderZmk: `""""{lang="unknown"}`, }, }, { descr: "Nested quotes (default)", zmk: `""say: ::""yes, ::""or?""::""::""`, |
︙ | ︙ | |||
305 306 307 308 309 310 311 | { descr: "Math formatting", zmk: `$$\TeX$$`, expect: expectMap{ encoderHTML: `<p><code class="zs-math">\TeX</code></p>`, encoderMD: "\\TeX", encoderSz: `(BLOCK (PARA (LITERAL-MATH () "\\TeX")))`, | | | > > > > > > > > > > > > | 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | { descr: "Math formatting", zmk: `$$\TeX$$`, expect: expectMap{ encoderHTML: `<p><code class="zs-math">\TeX</code></p>`, encoderMD: "\\TeX", encoderSz: `(BLOCK (PARA (LITERAL-MATH () "\\TeX")))`, encoderSHTML: `((p (code ((class . "zs-math")) "\\TeX")))`, encoderText: `\TeX`, encoderZmk: useZmk, }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<p><span lang="fr">« abc »</span></p>`, encoderMD: "« abc »", encoderSz: `(BLOCK (PARA (FORMAT-SPAN (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc")))))`, 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: `<p><ins lang="fr">« abc »</ins></p>`, 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: `<p><span>Stern18</span></p>`, // TODO encoderMD: "", encoderSz: `(BLOCK (PARA (CITE () "Stern18")))`, |
︙ | ︙ | |||
397 398 399 400 401 402 403 | encoderZmk: useZmk, }, }, { descr: "Comment after text and with -->", zmk: `Text%%{-} comment --> end`, expect: expectMap{ | | | | | | 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 | encoderZmk: useZmk, }, }, { descr: "Comment after text and with -->", zmk: `Text%%{-} comment --> end`, expect: expectMap{ encoderHTML: `<p>Text<!-- comment --> end --></p>`, encoderMD: "Text", encoderSz: `(BLOCK (PARA (TEXT "Text") (LITERAL-COMMENT (("-" . "")) "comment --> end")))`, encoderSHTML: `((p "Text" (@@ "comment --> end")))`, encoderText: `Text`, encoderZmk: useZmk, }, }, { descr: "Simple inline endnote", zmk: `[^endnote]`, expect: expectMap{ encoderHTML: `<p><sup id="fnref:1"><a class="zs-noteref" href="#fn:1" role="doc-noteref">1</a></sup></p><ol class="zs-endnotes"><li class="zs-endnote" id="fn:1" role="doc-endnote" value="1">endnote <a class="zs-endnote-backref" href="#fnref:1" role="doc-backlink">↩︎</a></li></ol>`, encoderMD: "", encoderSz: `(BLOCK (PARA (ENDNOTE () (TEXT "endnote"))))`, encoderSHTML: `((p (sup ((id . "fnref:1")) (a ((class . "zs-noteref") (href . "#fn:1") (role . "doc-noteref")) "1"))))`, encoderText: `endnote`, encoderZmk: useZmk, }, }, { descr: "Simple mark", zmk: `[!mark]`, expect: expectMap{ encoderHTML: `<p><a id="mark"></a></p>`, encoderMD: "", encoderSz: `(BLOCK (PARA (MARK "mark" "mark" "mark")))`, encoderSHTML: `((p (a ((id . "mark")))))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Mark with text", zmk: `[!mark|with text]`, expect: expectMap{ encoderHTML: `<p><a id="mark">with text</a></p>`, encoderMD: "with text", encoderSz: `(BLOCK (PARA (MARK "mark" "mark" "mark" (TEXT "with text"))))`, encoderSHTML: `((p (a ((id . "mark")) "with text")))`, encoderText: `with text`, encoderZmk: useZmk, }, }, { descr: "Invalid Link", zmk: `[[link|00000000000000]]`, |
︙ | ︙ | |||
472 473 474 475 476 477 478 | { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ encoderHTML: `<p><a href="abc">abc</a></p>`, encoderMD: "[abc](abc)", encoderSz: `(BLOCK (PARA (LINK () (HOSTED "abc"))))`, | | > > > > > > > > > > > > | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > | 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 | { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ encoderHTML: `<p><a href="abc">abc</a></p>`, encoderMD: "[abc](abc)", encoderSz: `(BLOCK (PARA (LINK () (HOSTED "abc"))))`, encoderSHTML: `((p (a ((href . "abc")) "abc")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Dummy Link with attribute", zmk: `[[abc]]{a="b"}`, expect: expectMap{ encoderHTML: `<p><a a="b" href="abc">abc</a></p>`, encoderMD: "[abc](abc)", encoderSz: `(BLOCK (PARA (LINK (("a" . "b")) (HOSTED "abc"))))`, encoderSHTML: `((p (a ((a . "b") (href . "abc")) "abc")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Simple URL", zmk: `[[https://zettelstore.de]]`, expect: expectMap{ encoderHTML: `<p><a href="https://zettelstore.de" rel="external">https://zettelstore.de</a></p>`, encoderMD: "<https://zettelstore.de>", encoderSz: `(BLOCK (PARA (LINK () (EXTERNAL "https://zettelstore.de"))))`, encoderSHTML: `((p (a ((href . "https://zettelstore.de") (rel . "external")) "https://zettelstore.de")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "URL with Text", zmk: `[[Home|https://zettelstore.de]]`, expect: expectMap{ encoderHTML: `<p><a href="https://zettelstore.de" rel="external">Home</a></p>`, 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")))`, encoderText: `Home`, encoderZmk: useZmk, }, }, { descr: "Simple Zettel ID", zmk: `[[00000000000100]]`, expect: expectMap{ encoderHTML: `<p><a href="00000000000100">00000000000100</a></p>`, encoderMD: "[00000000000100](00000000000100)", encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100"))))`, encoderSHTML: `((p (a ((href . "00000000000100")) "00000000000100")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Zettel ID with Text", zmk: `[[Config|00000000000100]]`, expect: expectMap{ encoderHTML: `<p><a href="00000000000100">Config</a></p>`, encoderMD: "[Config](00000000000100)", encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100") (TEXT "Config"))))`, encoderSHTML: `((p (a ((href . "00000000000100")) "Config")))`, encoderText: `Config`, encoderZmk: useZmk, }, }, { descr: "Simple Zettel ID with fragment", zmk: `[[00000000000100#frag]]`, expect: expectMap{ encoderHTML: `<p><a href="00000000000100#frag">00000000000100#frag</a></p>`, encoderMD: "[00000000000100#frag](00000000000100#frag)", encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100#frag"))))`, encoderSHTML: `((p (a ((href . "00000000000100#frag")) "00000000000100#frag")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Zettel ID with Text and fragment", zmk: `[[Config|00000000000100#frag]]`, expect: expectMap{ encoderHTML: `<p><a href="00000000000100#frag">Config</a></p>`, encoderMD: "[Config](00000000000100#frag)", encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100#frag") (TEXT "Config"))))`, encoderSHTML: `((p (a ((href . "00000000000100#frag")) "Config")))`, encoderText: `Config`, encoderZmk: useZmk, }, }, { descr: "Fragment link to self", zmk: `[[#frag]]`, expect: expectMap{ encoderHTML: `<p><a href="#frag">#frag</a></p>`, encoderMD: "[#frag](#frag)", encoderSz: `(BLOCK (PARA (LINK () (SELF "#frag"))))`, encoderSHTML: `((p (a ((href . "#frag")) "#frag")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Hosted link", zmk: `[[H|/hosted]]`, expect: expectMap{ encoderHTML: `<p><a href="/hosted">H</a></p>`, encoderMD: "[H](/hosted)", encoderSz: `(BLOCK (PARA (LINK () (HOSTED "/hosted") (TEXT "H"))))`, encoderSHTML: `((p (a ((href . "/hosted")) "H")))`, encoderText: `H`, encoderZmk: useZmk, }, }, { descr: "Based link", zmk: `[[B|//based]]`, expect: expectMap{ encoderHTML: `<p><a href="/based">B</a></p>`, encoderMD: "[B](/based)", encoderSz: `(BLOCK (PARA (LINK () (BASED "/based") (TEXT "B"))))`, encoderText: `B`, encoderSHTML: `((p (a ((href . "/based")) "B")))`, encoderZmk: useZmk, }, }, { descr: "Relative link", zmk: `[[R|../relative]]`, expect: expectMap{ encoderHTML: `<p><a href="../relative">R</a></p>`, encoderMD: "[R](../relative)", encoderSz: `(BLOCK (PARA (LINK () (HOSTED "../relative") (TEXT "R"))))`, encoderSHTML: `((p (a ((href . "../relative")) "R")))`, encoderText: `R`, encoderZmk: useZmk, }, }, { descr: "Query link w/o text", zmk: `[[query:title:syntax]]`, expect: expectMap{ encoderHTML: `<p><a href="?q=title%3Asyntax">title:syntax</a></p>`, encoderMD: "", encoderSz: `(BLOCK (PARA (LINK () (QUERY "title:syntax"))))`, encoderSHTML: `((p (a ((href . "?q=title%3Asyntax")) "title:syntax")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Query link with text", zmk: `[[Q|query:title:syntax]]`, expect: expectMap{ encoderHTML: `<p><a href="?q=title%3Asyntax">Q</a></p>`, encoderMD: "Q", encoderSz: `(BLOCK (PARA (LINK () (QUERY "title:syntax") (TEXT "Q"))))`, encoderSHTML: `((p (a ((href . "?q=title%3Asyntax")) "Q")))`, encoderText: `Q`, encoderZmk: useZmk, }, }, { descr: "Dummy Embed", zmk: `{{abc}}`, expect: expectMap{ encoderHTML: `<p><img src="abc"></p>`, encoderMD: "", encoderSz: `(BLOCK (PARA (EMBED () (HOSTED "abc") "")))`, encoderSHTML: `((p (img ((src . "abc")))))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Dummy Embed with attributes", zmk: `{{abc}}{a="b"}`, expect: expectMap{ encoderHTML: `<p><img a="b" src="abc"></p>`, 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: `<p><a href="/url" title="TITL """>text</a></p>`, 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: ``, encoderMD: "", encoderSz: `(BLOCK)`, encoderSHTML: `()`, encoderText: ``, encoderZmk: useZmk, }, }, } |
Changes to internal/encoder/encoder_test.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 | package encoder_test import ( "fmt" "strings" "testing" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "zettelstore.de/z/internal/ast" | > < > | > > > > | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | package encoder_test import ( "fmt" "strings" "testing" "t73f.de/r/sx" "t73f.de/r/sx/sxreader" "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/encoder" "zettelstore.de/z/internal/parser" ) type zmkTestCase struct { descr string zmk string syntax string inline bool expect expectMap } type expectMap map[api.EncodingEnum]string const useZmk = "\000" const ( encoderHTML = api.EncoderHTML encoderMD = api.EncoderMD encoderSz = api.EncoderSz encoderSHTML = api.EncoderSHTML encoderText = api.EncoderText encoderZmk = api.EncoderZmk ) func TestEncoder(t *testing.T) { for i := range tcsInline { tcsInline[i].inline = true } 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)) syntax := tc.syntax if syntax == "" { syntax = meta.ValueSyntaxZmk } node, bs := parser.Parse(inp, nil, syntax) parser.Clean(node, false) parser.CleanAST(&bs, false) checkEncodings(t, testNum, node, tc.inline, tc.descr, tc.expect, tc.zmk) checkEncodingsAST(t, testNum+1000, bs, tc.inline, tc.descr, tc.expect, tc.zmk) checkSz(t, testNum, bs, tc.inline, tc.descr) } } func checkEncodings(t *testing.T, testNum int, node *sx.Pair, isInline bool, descr string, expected expectMap, zmkDefault string) { for enc, exp := range expected { if enc == api.EncoderZmk { continue } encdr := encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}) got, err := encode(encdr, node) if err != nil { prefix := fmt.Sprintf("Test #%d", testNum) if d := descr; d != "" { prefix += "\nReason: " + d } prefix += "\nMode: " + mode(isInline) t.Errorf("%s\nEncoder: %s\nError: %v", prefix, enc, err) continue } if enc == api.EncoderZmk && exp == useZmk { exp = zmkDefault } if got != exp { prefix := fmt.Sprintf("Test #%d", testNum) if d := descr; d != "" { prefix += "\nReason: " + d } prefix += "\nMode: " + mode(isInline) t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got) } } } func checkEncodingsAST(t *testing.T, testNum int, bs ast.BlockSlice, isInline bool, descr string, expected expectMap, zmkDefault string) { for enc, exp := range expected { encdr := encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}) got, err := encodeAST(encdr, bs) if err != nil { prefix := fmt.Sprintf("Test #%d", testNum) if d := descr; d != "" { prefix += "\nReason: " + d } prefix += "\nMode: " + mode(isInline) t.Errorf("%s\nEncoder: %s\nError: %v", prefix, enc, err) |
︙ | ︙ | |||
90 91 92 93 94 95 96 | } } } func checkSz(t *testing.T, testNum int, bs ast.BlockSlice, isInline bool, descr string) { t.Helper() encdr := encoder.Create(encoderSz, nil) | | | < > > > > > | | | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | } } } func checkSz(t *testing.T, testNum int, bs ast.BlockSlice, isInline bool, descr string) { t.Helper() encdr := encoder.Create(encoderSz, nil) exp, err := encodeAST(encdr, bs) if err != nil { t.Error(err) return } val, err := sxreader.MakeReader(strings.NewReader(exp)).Read() if err != nil { t.Error(err) return } if got := val.String(); 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, node *sx.Pair) (string, error) { var sb strings.Builder err := e.WriteSz(&sb, node) return sb.String(), err } func encodeAST(e encoder.Encoder, bs ast.BlockSlice) (string, error) { var sb strings.Builder err := e.WriteBlocks(&sb, &bs) return sb.String(), err } func mode(isInline bool) string { if isInline { return "inline" } return "block" } |
Changes to internal/encoder/htmlenc.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 | "io" "strings" "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "zettelstore.de/z/internal/ast" ) // htmlEncoder contains all data needed for encoding. type htmlEncoder struct { | > > > | | | | | < | | | | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | "io" "strings" "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" "zettelstore.de/z/internal/ast/sztrans" ) // htmlEncoder contains all data needed for encoding. type htmlEncoder struct { tx sztrans.SzTransformer th *shtml.Evaluator lang string textEnc TextEncoder } // WriteZettel encodes a full zettel as HTML5. func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error { env := shtml.MakeEnvironment(he.lang) hm, err := he.th.Evaluate(sztrans.GetMetaSz(zn.InhMeta), &env) if err != nil { return err } var isTitle ast.InlineSlice var htitle *sx.Pair plainTitle, hasTitle := zn.InhMeta.Get(meta.KeyTitle) if hasTitle { szTitle := zsx.MakeInline((zsx.MakeText(sz.NormalizedSpacedText(string(plainTitle))))) htitle, err = he.th.Evaluate(szTitle, &env) if err != nil { return err } } xast := he.tx.GetSz(&zn.BlocksAST) hast, err := he.th.Evaluate(xast, &env) if err != nil { return err } hen := shtml.Endnotes(&env) var head sx.ListBuilder head.AddN( shtml.SymHead, 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) } else { sb.Write(zn.Meta.Zid.Bytes()) } head.Add(sx.MakeList(shtml.SymAttrTitle, sx.MakeString(sb.String()))) var body sx.ListBuilder body.Add(shtml.SymBody) |
︙ | ︙ | |||
92 93 94 95 96 97 98 | ) gen := sxhtml.NewGenerator().SetNewline() return gen.WriteHTML(w, doc) } // WriteMeta encodes meta data as HTML5. | | | | > > > > > > > > > > > > > > > | | < | | < < | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | ) 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) error { env := shtml.MakeEnvironment(he.lang) hm, err := he.th.Evaluate(sztrans.GetMetaSz(m), &env) if err != nil { return err } gen := sxhtml.NewGenerator().SetNewline() return gen.WriteListHTML(w, hm) } // 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(node, &env) if err == nil { gen := sxhtml.NewGenerator() if err = gen.WriteListHTML(w, hobj); err != nil { return err } return gen.WriteHTML(w, shtml.Endnotes(&env)) } return err } // WriteBlocks encodes a block slice. func (he *htmlEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) error { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env) if err == nil { gen := sxhtml.NewGenerator() if err = gen.WriteListHTML(w, hobj); err != nil { return err } return gen.WriteHTML(w, shtml.Endnotes(&env)) } return err } |
Changes to internal/encoder/mdenc.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package encoder // Encodes the abstract syntax tree back into Markdown. import ( "io" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" ) // mdEncoder contains all data needed for encoding. type mdEncoder struct { lang string } // WriteZettel writes the encoded zettel to the writer. | > > > | | | | < | > > > > > > > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | < | | | | | | | | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | package encoder // 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" ) // mdEncoder contains all data needed for encoding. type mdEncoder struct { lang string } // WriteZettel writes the encoded zettel to the writer. func (me *mdEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error { v := newMDVisitorAST(w, me.lang) v.b.WriteMeta(zn.InhMeta) if zn.InhMeta.YamlSep { v.b.WriteString("---\n") } else { v.b.WriteLn() } ast.Walk(&v, &zn.BlocksAST) return v.b.Flush() } // WriteMeta encodes meta data as markdown. 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 defLang string quoteNesting uint } func newMDVisitor(w io.Writer, lang string) mdVisitor { 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) 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) VisitBefore(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, "<mark>", "</mark>") 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) VisitAfter(*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") } 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 { if v.listPrefix == "" { v.writeSpaces(4*l - 4 + v.listInfo[l-1]) } else { v.writeSpaces(4*l - 4) v.b.WriteString(v.listPrefix) } } } 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('[') v.walkList(inlines, alst) v.b.WriteStrings("](", val) _ = v.b.WriteByte(')') } else if isAutoLinkable(refState, val) { _ = v.b.WriteByte('<') v.b.WriteString(val) _ = v.b.WriteByte('>') } else { 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++ v.walkList(inlines, alst) v.quoteNesting-- if withNbsp { v.b.WriteString(" ") } v.b.WriteString(rightQ) } 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() } } // WriteBlocks writes the content of a block slice to the writer. func (me *mdEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) error { v := newMDVisitorAST(w, me.lang) ast.Walk(&v, bs) return v.b.Flush() } // mdVisitorAST writes the abstract syntax tree to an EncWriter. type mdVisitorAST struct { b encWriter listInfo []int listPrefix string langStack shtml.LangStack quoteNesting uint } func newMDVisitorAST(w io.Writer, lang string) mdVisitorAST { return mdVisitorAST{b: newEncWriter(w), langStack: shtml.NewLangStack(lang)} } // pushAttribute adds the current attributes to the visitor. func (v *mdVisitorAST) 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 *mdVisitorAST) popAttributes() { v.langStack.Pop() } // getLanguage returns the current language, func (v *mdVisitorAST) getLanguage() string { return v.langStack.Top() } func (v *mdVisitorAST) getQuotes() (string, string, bool) { qi := shtml.GetQuoteInfo(v.getLanguage()) leftQ, rightQ := qi.GetQuotes(v.quoteNesting) return leftQ, rightQ, qi.GetNBSp() } func (v *mdVisitorAST) 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) |
︙ | ︙ | |||
134 135 136 137 138 139 140 | v.visitLiteral(n) default: return v } return nil } | | | | 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | v.visitLiteral(n) default: return v } return nil } func (v *mdVisitorAST) visitBlockSlice(bs *ast.BlockSlice) { for i, bn := range *bs { if i > 0 { v.b.WriteString("\n\n") } ast.Walk(v, bn) } } func (v *mdVisitorAST) 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++ { |
︙ | ︙ | |||
172 173 174 175 176 177 178 | } v.b.WriteLn() v.writeSpaces(4) i = j - 1 } } | | | 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 | } v.b.WriteLn() v.writeSpaces(4) i = j - 1 } } func (v *mdVisitorAST) visitRegion(rn *ast.RegionNode) { if rn.Kind != ast.RegionQuote { return } v.pushAttributes(rn.Attrs) defer v.popAttributes() first := true |
︙ | ︙ | |||
194 195 196 197 198 199 200 | } first = false v.b.WriteString("> ") ast.Walk(v, &pn.Inlines) } } | | | | | 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 | } first = false v.b.WriteString("> ") ast.Walk(v, &pn.Inlines) } } func (v *mdVisitorAST) 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 *mdVisitorAST) 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 *mdVisitorAST) 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() } |
︙ | ︙ | |||
237 238 239 240 241 242 243 | } } ast.Walk(v, in) } } } | | | 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 | } } ast.Walk(v, in) } } } func (v *mdVisitorAST) writeListQuote(ln *ast.NestedListNode) { v.listInfo = append(v.listInfo, 0) if len(v.listInfo) > 1 { return } prefix := v.listPrefix v.listPrefix = "> " |
︙ | ︙ | |||
265 266 267 268 269 270 271 | ast.Walk(v, in) } } v.listPrefix = prefix } | | | | | | | | | 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 | ast.Walk(v, in) } } v.listPrefix = prefix } func (v *mdVisitorAST) visitBreak(bn *ast.BreakNode) { if bn.Hard { v.b.WriteString("\\\n") } else { v.b.WriteLn() } if l := len(v.listInfo); l > 0 { if v.listPrefix == "" { v.writeSpaces(4*l - 4 + v.listInfo[l-1]) } else { v.writeSpaces(4*l - 4) v.b.WriteString(v.listPrefix) } } } func (v *mdVisitorAST) visitLink(ln *ast.LinkNode) { v.pushAttributes(ln.Attrs) defer v.popAttributes() v.writeReference(ln.Ref, ln.Inlines) } func (v *mdVisitorAST) visitEmbedRef(en *ast.EmbedRefNode) { v.pushAttributes(en.Attrs) defer v.popAttributes() _ = v.b.WriteByte('!') v.writeReference(en.Ref, en.Inlines) } func (v *mdVisitorAST) writeReference(ref *ast.Reference, is ast.InlineSlice) { if ref.State == ast.RefStateQuery { ast.Walk(v, &is) } else if len(is) > 0 { _ = v.b.WriteByte('[') ast.Walk(v, &is) v.b.WriteStrings("](", ref.String()) _ = v.b.WriteByte(')') } else if isAutoLinkableAST(ref) { _ = v.b.WriteByte('<') v.b.WriteString(ref.String()) _ = v.b.WriteByte('>') } else { s := ref.String() v.b.WriteStrings("[", s, "](", s, ")") } } func isAutoLinkableAST(ref *ast.Reference) bool { if ref.State != ast.RefStateExternal || ref.URL == nil { return false } return ref.URL.Scheme != "" } func (v *mdVisitorAST) 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) |
︙ | ︙ | |||
345 346 347 348 349 350 351 | ast.Walk(v, &fn.Inlines) v.b.WriteString("</mark>") default: ast.Walk(v, &fn.Inlines) } } | | | | | 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 | ast.Walk(v, &fn.Inlines) v.b.WriteString("</mark>") default: ast.Walk(v, &fn.Inlines) } } func (v *mdVisitorAST) writeQuote(fn *ast.FormatNode) { leftQ, rightQ, withNbsp := v.getQuotes() v.b.WriteString(leftQ) if withNbsp { v.b.WriteString(" ") } v.quoteNesting++ ast.Walk(v, &fn.Inlines) v.quoteNesting-- if withNbsp { v.b.WriteString(" ") } v.b.WriteString(rightQ) } func (v *mdVisitorAST) 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) } } func (v *mdVisitorAST) writeSpaces(n int) { for range n { v.b.WriteSpace() } } |
Changes to internal/encoder/shtmlenc.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 | "io" "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "zettelstore.de/z/internal/ast" ) // shtmlEncoder contains all data needed for encoding. type shtmlEncoder struct { | > | | | | | > | | | | | > > > > > > > > > > > > | | > | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | "io" "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/ast/sztrans" ) // shtmlEncoder contains all data needed for encoding. type shtmlEncoder struct { tx sztrans.SzTransformer th *shtml.Evaluator lang string } // WriteZettel writes the encoded zettel to the writer. func (enc *shtmlEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error { env := shtml.MakeEnvironment(enc.lang) metaSHTML, err := enc.th.Evaluate(sztrans.GetMetaSz(zn.InhMeta), &env) if err != nil { return err } contentSHTML, err := enc.th.Evaluate(enc.tx.GetSz(&zn.BlocksAST), &env) if err != nil { return err } result := sx.Cons(metaSHTML, contentSHTML) _, err = result.Print(w) return err } // WriteMeta encodes meta data as s-expression. func (enc *shtmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) error { env := shtml.MakeEnvironment(enc.lang) metaSHTML, err := enc.th.Evaluate(sztrans.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 } // WriteBlocks writes a block slice to the writer func (enc *shtmlEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) error { env := shtml.MakeEnvironment(enc.lang) hval, err := enc.th.Evaluate(enc.tx.GetSz(bs), &env) if err != nil { return err } _, err = hval.Print(w) return err } |
Changes to internal/encoder/szenc.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 | import ( "io" "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "zettelstore.de/z/internal/ast" ) // szEncoder contains all data needed for encoding. type szEncoder struct { | > | | | | > | > | > > > > > > | | > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | import ( "io" "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/ast/sztrans" ) // szEncoder contains all data needed for encoding. type szEncoder struct { trans sztrans.SzTransformer } // WriteZettel writes the encoded zettel to the writer. func (enc *szEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error { content := enc.trans.GetSz(&zn.BlocksAST) meta := sztrans.GetMetaSz(zn.InhMeta) _, err := sx.MakeList(meta, content).Print(w) return err } // WriteMeta encodes meta data as s-expression. func (enc *szEncoder) WriteMeta(w io.Writer, m *meta.Meta) error { _, err := sztrans.GetMetaSz(m).Print(w) return err } // WriteSz encodes SZ represented zettel content. func (*szEncoder) WriteSz(w io.Writer, node *sx.Pair) error { _, err := node.Print(w) return err } // WriteBlocks writes a block slice to the writer func (enc *szEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) error { _, err := enc.trans.GetSz(bs).Print(w) return err } |
Deleted internal/encoder/sztransform.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to internal/encoder/textenc.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // textenc encodes the abstract syntax tree into its text. import ( "io" "iter" "t73f.de/r/zsc/domain/meta" "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. | > > | | | | < | | < < | | | < | | | < | > | | | > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | // textenc encodes the abstract syntax tree into its text. 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.Zettel) error { v := newTextVisitorAST(w) _ = te.WriteMeta(&v.b, zn.InhMeta) v.visitBlockSlice(&zn.BlocksAST) return v.b.Flush() } // WriteMeta encodes metadata as text. 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() } return buf.Flush() } func writeTagSet(buf *encWriter, tags iter.Seq[meta.Value]) { first := true for tag := range tags { if !first { 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) error { v := newTextVisitorAST(w) v.visitBlockSlice(bs) return v.b.Flush() } // WriteInlines writes an inline slice to the writer func (*TextEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) error { v := newTextVisitorAST(w) ast.Walk(&v, is) return v.b.Flush() } // 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) 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) VisitBefore(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) VisitAfter(*sx.Pair, *sx.Pair) {} // textVisitorAST writes the abstract syntax tree to an io.Writer. type textVisitorAST struct { b encWriter inlinePos int } func newTextVisitorAST(w io.Writer) textVisitorAST { return textVisitorAST{b: newEncWriter(w)} } func (v *textVisitorAST) 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 |
︙ | ︙ | |||
150 151 152 153 154 155 156 | if n.Kind != ast.LiteralComment { _, _ = v.b.Write(n.Content) } } return v } | | | | | | | | | | | 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | if n.Kind != ast.LiteralComment { _, _ = v.b.Write(n.Content) } } return v } func (v *textVisitorAST) visitVerbatim(vn *ast.VerbatimNode) { if vn.Kind != ast.VerbatimComment { _, _ = v.b.Write(vn.Content) } } func (v *textVisitorAST) 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 *textVisitorAST) 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 *textVisitorAST) 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 *textVisitorAST) writeRow(row ast.TableRow) { for i, cell := range row { v.writePosChar(i, ' ') ast.Walk(v, &cell.Inlines) } } func (v *textVisitorAST) visitBlockSlice(bs *ast.BlockSlice) { for i, bn := range *bs { v.writePosChar(i, '\n') ast.Walk(v, bn) } } func (v *textVisitorAST) visitInlineSlice(is *ast.InlineSlice) { for i, in := range *is { v.inlinePos = i ast.Walk(v, in) } v.inlinePos = 0 } func (v *textVisitorAST) 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 *textVisitorAST) writePosChar(pos int, ch byte) { if pos > 0 { _ = v.b.WriteByte(ch) } } |
Changes to internal/encoder/write.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package encoder import ( | < > > | | < < < | < | | < > < < < < < < < < < < < < < < > > > > > > > > > | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package encoder import ( "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 } // newEncWriter creates a new encWriter func newEncWriter(w io.Writer) encWriter { return encWriter{w: w} } // Write writes the content of p. 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) return l, w.err } // WriteString writes the content of s. func (w *encWriter) WriteString(s string) { if w.err != nil { return } _, w.err = io.WriteString(w.w, s) } // WriteStrings writes the content of sl. func (w *encWriter) WriteStrings(sl ...string) { for _, s := range sl { w.WriteString(s) } } // WriteByte writes the content of b. func (w *encWriter) WriteByte(b byte) error { 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) } // WriteLn writes a new line character. func (w *encWriter) WriteLn() { if w.err == nil { w.err = w.WriteByte('\n') } } // WriteLn writes a space character. 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() error { return w.err } |
Changes to internal/encoder/zmkenc.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // zmkenc encodes the abstract syntax tree back into Zettelmarkup. import ( "fmt" "io" "strings" "t73f.de/r/zero/set" "t73f.de/r/zsc/domain/meta" "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. | > > > | | | | < | | | < | < < < < < | < | | | < < | < | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | < < | | | | | | | | | | | | | | | | | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | // 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/api" "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 (ze *zmkEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error { v := newZmkVisitorAST(w) v.b.WriteMeta(zn.InhMeta) if zn.InhMeta.YamlSep { v.b.WriteString("---\n") } else { v.b.WriteLn() } ast.Walk(&v, &zn.BlocksAST) return v.b.Flush() } // WriteMeta encodes meta data as zmk. 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) 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) VisitBefore(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) default: return false } return true } return false } func (v *zmkVisitor) VisitAfter(*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('|') } v.writeRef(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('|') } v.writeRef(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) writeText(text string) { last := 0 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(text)-1 { s := text[i : i+2] if escapeSeqs.Contains(s) { v.b.WriteString(text[last:i]) for j := range len(s) { v.b.WriteBytes('\\', s[j]) } i++ last = i + 1 continue } } } v.b.WriteString(text[last:]) } func (v *zmkVisitor) writeBreak(isHard bool) { if isHard { v.b.WriteString("\\\n") } else { v.b.WriteLn() } v.writePrefixSpaces() } 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 == "-" { _ = v.b.WriteByte('-') continue } v.b.WriteString(k) if vl := a[k]; len(vl) > 0 { v.b.WriteString("=\"") v.writeEscaped(vl, '"') _ = v.b.WriteByte('"') } } _ = v.b.WriteByte('}') } func (v *zmkVisitor) writeRef(ref *sx.Pair) { refSym, refVal := zsx.GetReference(ref) if sz.SymRefStateBased.IsEqualSymbol(refSym) { _ = v.b.WriteByte('/') } else if sz.SymRefStateQuery.IsEqualSymbol(refSym) { v.b.WriteString(api.QueryPrefix) } v.b.WriteString(refVal) } func (v *zmkVisitor) writeEscaped(s string, toEscape byte) { last := 0 for i := range len(s) { if b := s[i]; b == toEscape || b == '\\' { v.b.WriteString(s[last:i]) v.b.WriteBytes('\\', b) last = i + 1 } } v.b.WriteString(s[last:]) } func (v *zmkVisitor) writePrefixSpaces() { if prefixLen := len(v.prefix); prefixLen > 0 { for i := 0; i <= prefixLen; i++ { v.b.WriteSpace() } } } // WriteBlocks writes the content of a block slice to the writer. func (*zmkEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) error { v := newZmkVisitorAST(w) ast.Walk(&v, bs) return v.b.Flush() } // zmkVisitorAST writes the abstract syntax tree to an io.Writer. type zmkVisitorAST struct { b encWriter prefix []byte inVerse bool } func newZmkVisitorAST(w io.Writer) zmkVisitorAST { return zmkVisitorAST{b: newEncWriter(w)} } func (v *zmkVisitorAST) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSliceAST(n) case *ast.InlineSlice: for _, in := range *n { ast.Walk(v, in) } case *ast.VerbatimNode: v.visitVerbatimAST(n) case *ast.RegionNode: v.visitRegionAST(n) case *ast.HeadingNode: v.visitHeadingAST(n) case *ast.HRuleNode: v.b.WriteString("---") v.visitAttributesAST(n.Attrs) case *ast.NestedListNode: v.visitNestedListAST(n) case *ast.DescriptionListNode: v.visitDescriptionListAST(n) case *ast.TableNode: v.visitTableAST(n) case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") // FIXME n.Inlines v.visitAttributesAST(n.Attrs) case *ast.BLOBNode: v.visitBLOBAST(n) case *ast.TextNode: v.visitTextAST(n) case *ast.BreakNode: v.visitBreakAST(n) case *ast.LinkNode: v.visitLinkAST(n) case *ast.EmbedRefNode: v.visitEmbedRefAST(n) case *ast.EmbedBLOBNode: v.visitEmbedBLOBAST(n) case *ast.CiteNode: v.visitCiteAST(n) case *ast.FootnoteNode: v.b.WriteString("[^") ast.Walk(v, &n.Inlines) _ = v.b.WriteByte(']') v.visitAttributesAST(n.Attrs) case *ast.MarkNode: v.visitMarkAST(n) case *ast.FormatNode: v.visitFormatAST(n) case *ast.LiteralNode: v.visitLiteralAST(n) default: return v } return nil } func (v *zmkVisitorAST) visitBlockSliceAST(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() |
︙ | ︙ | |||
157 158 159 160 161 162 163 | ast.VerbatimComment: "%%%", ast.VerbatimHTML: "@@@", // Attribute is set to {="html"} ast.VerbatimCode: "```", ast.VerbatimEval: "~~~", ast.VerbatimMath: "$$$", } | | | | | | | | | | | 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | ast.VerbatimComment: "%%%", ast.VerbatimHTML: "@@@", // Attribute is set to {="html"} ast.VerbatimCode: "```", ast.VerbatimEval: "~~~", ast.VerbatimMath: "$$$", } func (v *zmkVisitorAST) visitVerbatimAST(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.visitAttributesAST(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 *zmkVisitorAST) visitRegionAST(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.visitAttributesAST(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 *zmkVisitorAST) visitHeadingAST(hn *ast.HeadingNode) { const headingSigns = "========= " v.b.WriteString(headingSigns[len(headingSigns)-hn.Level-3:]) ast.Walk(v, &hn.Inlines) v.visitAttributesAST(hn.Attrs) } var mapNestedListKind = map[ast.NestedListKind]byte{ ast.NestedListOrdered: '#', ast.NestedListUnordered: '*', ast.NestedListQuote: '>', } func (v *zmkVisitorAST) visitNestedListAST(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) } } v.prefix = v.prefix[:len(v.prefix)-1] } func (v *zmkVisitorAST) writePrefixSpaces() { if prefixLen := len(v.prefix); prefixLen > 0 { for i := 0; i <= prefixLen; i++ { v.b.WriteSpace() } } } func (v *zmkVisitorAST) visitDescriptionListAST(dn *ast.DescriptionListNode) { for i, descr := range dn.Descriptions { if i > 0 { v.b.WriteLn() } v.b.WriteString("; ") ast.Walk(v, &descr.Term) |
︙ | ︙ | |||
272 273 274 275 276 277 278 | var alignCode = map[ast.Alignment]string{ ast.AlignDefault: "", ast.AlignLeft: "<", ast.AlignCenter: ":", ast.AlignRight: ">", } | | | | | > | | | 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 | var alignCode = map[ast.Alignment]string{ ast.AlignDefault: "", ast.AlignLeft: "<", ast.AlignCenter: ":", ast.AlignRight: ">", } func (v *zmkVisitorAST) visitTableAST(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 *zmkVisitorAST) 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 *zmkVisitorAST) 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 *zmkVisitorAST) visitBLOBAST(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 var textEnc TextEncoder _ = textEnc.WriteInlines(&sb, &bn.Description) v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", bn.Syntax, "'.") } var escapeSeqs = set.New( "\\", "__", "**", "~~", "^^", ",,", ">>", `""`, "::", "''", "``", "++", "==", "##", ) func (v *zmkVisitorAST) visitTextAST(tn *ast.TextNode) { last := 0 for i := 0; i < len(tn.Text); i++ { if b := tn.Text[i]; b == '\\' { v.b.WriteString(tn.Text[last:i]) v.b.WriteBytes('\\', b) last = i + 1 continue |
︙ | ︙ | |||
350 351 352 353 354 355 356 | continue } } } v.b.WriteString(tn.Text[last:]) } | | | > | > | | | | | 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | continue } } } v.b.WriteString(tn.Text[last:]) } func (v *zmkVisitorAST) visitBreakAST(bn *ast.BreakNode) { if bn.Hard { v.b.WriteString("\\\n") } else { v.b.WriteLn() } v.writePrefixSpaces() } func (v *zmkVisitorAST) visitLinkAST(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(), "]]") v.visitAttributesAST(ln.Attrs) } func (v *zmkVisitorAST) visitEmbedRefAST(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(), "}}") v.visitAttributesAST(en.Attrs) } func (v *zmkVisitorAST) visitEmbedBLOBAST(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 *zmkVisitorAST) visitCiteAST(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.visitAttributesAST(cn.Attrs) } func (v *zmkVisitorAST) visitMarkAST(mn *ast.MarkNode) { v.b.WriteStrings("[!", mn.Mark) if len(mn.Inlines) > 0 { _ = v.b.WriteByte('|') ast.Walk(v, &mn.Inlines) } _ = v.b.WriteByte(']') |
︙ | ︙ | |||
422 423 424 425 426 427 428 | ast.FormatSuper: []byte("^^"), ast.FormatSub: []byte(",,"), ast.FormatQuote: []byte(`""`), ast.FormatMark: []byte("##"), ast.FormatSpan: []byte("::"), } | | | | | | | | | | | > | | 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 | ast.FormatSuper: []byte("^^"), ast.FormatSub: []byte(",,"), ast.FormatQuote: []byte(`""`), ast.FormatMark: []byte("##"), ast.FormatSpan: []byte("::"), } func (v *zmkVisitorAST) visitFormatAST(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.visitAttributesAST(fn.Attrs) } func (v *zmkVisitorAST) visitLiteralAST(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.visitAttributesAST(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.visitAttributesAST(ln.Attrs) v.b.WriteSpace() _, _ = v.b.Write(ln.Content) default: panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind)) } } func (v *zmkVisitorAST) writeLiteral(code byte, a zsx.Attributes, content []byte) { v.b.WriteBytes(code, code) v.writeEscaped(string(content), code) v.b.WriteBytes(code, code) v.visitAttributesAST(a) } // visitAttributesAST write HTML attributes func (v *zmkVisitorAST) visitAttributesAST(a zsx.Attributes) { if a.IsEmpty() { return } _ = v.b.WriteByte('{') for i, k := range a.Keys() { if i > 0 { v.b.WriteSpace() } if k == "-" { _ = v.b.WriteByte('-') continue } v.b.WriteString(k) if vl := a[k]; len(vl) > 0 { v.b.WriteString("=\"") v.writeEscaped(vl, '"') _ = v.b.WriteByte('"') } } _ = v.b.WriteByte('}') } func (v *zmkVisitorAST) writeEscaped(s string, toEscape byte) { last := 0 for i := range len(s) { if b := s[i]; b == toEscape || b == '\\' { v.b.WriteString(s[last:i]) v.b.WriteBytes('\\', b) last = i + 1 } |
︙ | ︙ |
Changes to internal/evaluator/evaluator.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package evaluator interprets and evaluates the AST. package evaluator import ( | < < | | | | > | | | | < < < < < < < < < < < < < < < < < < < < < | | | > > | | < | < | | > | > > > > > > > > > > > > > | | > | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > | > | | | | < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | | | | | | | | < < | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | > > > > | > > > > > > > > > | | | | | | | | | | | | | | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 | // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package evaluator interprets and evaluates the AST. package evaluator import ( "context" "errors" "fmt" "path" "slices" "strconv" "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/ast/sztrans" "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" ) // Port contains all methods to retrieve zettel (or part of it) to evaluate a zettel. type Port interface { GetZettel(context.Context, id.Zid) (zettel.Zettel, error) 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.Zettel) { switch zn.Syntax { case meta.ValueSyntaxNone: // AST is empty, evaluate to a description list of metadata. zn.Blocks = evaluateMetadata(zn.Meta) case meta.ValueSyntaxSxn: zn.Blocks = evaluateSxn(zn.Blocks) default: zn.BlocksAST = EvaluateBlock(ctx, port, rtConfig, zn.Blocks) return } if blk, err := sztrans.GetBlockSlice(zn.Blocks); err == nil { zn.BlocksAST = blk } } // 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, block *sx.Pair) ast.BlockSlice { e := evaluator{ ctx: ctx, port: port, rtConfig: rtConfig, transcludeMax: rtConfig.GetMaxTransclusions(), transcludeCount: 0, costMap: map[id.Zid]transcludeCost{}, embedMap: map[string]*sx.Pair{}, embedMapAST: map[string]ast.InlineSlice{}, marker: &ast.Zettel{}, } obj := zsx.Walk(&e, block, nil) evalBlock, isPair := sx.GetPair(obj) if !isPair { panic(fmt.Sprintf("not a pair after evaluate: %T/%v", obj, obj)) } bns, err := sztrans.GetBlockSlice(evalBlock) if err != nil { panic(err) } // Now evaluate everything that was not evaluated by SZ-walker. ast.Walk(&e, &bns) parser.CleanAST(&bns, true) return bns } type evaluator struct { ctx context.Context port Port rtConfig config.Config transcludeMax int transcludeCount int costMap map[id.Zid]transcludeCost marker *ast.Zettel embedMap map[string]*sx.Pair embedMapAST map[string]ast.InlineSlice } 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: case zsx.SymVerbatimEval: return e.evalVerbatimEval(node) } } return node } 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) 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 mustParseZid(ref *sx.Pair, refVal string) id.Zid { zid, err := id.Parse(refVal) if err != nil { refState, _ := zsx.GetReference(ref) panic(fmt.Sprintf("%v: %q (state %v) -> %v", err, refVal, refState, ref)) } return zid } // AST-based code, deprecated. func (e *evaluator) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: e.visitBlockSliceAST(n) case *ast.InlineSlice: e.visitInlineSliceAST(n) default: return e } return nil } func (e *evaluator) visitBlockSliceAST(bs *ast.BlockSlice) { for i := 0; i < len(*bs); i++ { bn := (*bs)[i] ast.Walk(e, bn) switch n := bn.(type) { case *ast.VerbatimNode: if n.Kind == ast.VerbatimZettel { i += transcludeNodeAST(bs, i, e.evalVerbatimZettelNodeAST(n)) } case *ast.TranscludeNode: i += transcludeNodeAST(bs, i, e.evalTransclusionNodeAST(n)) } } } func transcludeNodeAST(bln *ast.BlockSlice, i int, bn ast.BlockNode) int { if ln, ok := bn.(*ast.BlockSlice); ok { *bln = replaceWithBlockNodesAST(*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 replaceWithBlockNodesAST(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) evalVerbatimZettelNodeAST(vn *ast.VerbatimNode) ast.BlockNode { m := meta.New(id.Invalid) m.Set(meta.KeySyntax, getSyntaxAST(vn.Attrs, meta.ValueSyntaxText)) zettel := zettel.Zettel{ Meta: m, Content: zettel.NewContent(vn.Content), } e.transcludeCount++ zn := e.evaluateEmbeddedZettelAST(zettel) return &zn.BlocksAST } func getSyntaxAST(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) } } return defSyntax } func (e *evaluator) evalTransclusionNodeAST(tn *ast.TranscludeNode) ast.BlockNode { ref := tn.Ref // To prevent e.embedCount from counting if errText := e.checkMaxTransclusionsAST(ref); errText != nil { return makeBlockNodeAST(errText) } switch ref.State { case ast.RefStateZettel: // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return makeBlockNodeAST(createInlineErrorTextAST(ref, "Invalid or broken transclusion reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNodeAST(createInlineErrorTextAST(ref, "Self transclusion reference")) case ast.RefStateFound, ast.RefStateExternal: return tn case ast.RefStateHosted, ast.RefStateBased: if n := createEmbeddedNodeLocalAST(ref); n != nil { n.Attrs = tn.Attrs return makeBlockNodeAST(n) } return tn case ast.RefStateQuery: e.transcludeCount++ return e.evalQueryTransclusionAST(tn.Ref.Value) default: return makeBlockNodeAST(createInlineErrorTextAST(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 makeBlockNodeAST(createInlineErrorTextAST(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 makeBlockNodeAST(createInlineErrorTextAST(ref, "Unable to get zettel")) } setMetadataFromAttributesAST(zettel.Meta, tn.Attrs) ec := e.transcludeCount e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec} zn = e.evaluateEmbeddedZettelAST(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) evalQueryTransclusionAST(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 makeBlockNodeAST(createInlineErrorTextAST(nil, "Unable to search zettel")) } result := QueryActionAST(e.ctx, q, ml) if result != nil { ast.Walk(e, result) } return result } func (e *evaluator) checkMaxTransclusionsAST(ref *ast.Reference) ast.InlineNode { if maxTrans := e.transcludeMax; e.transcludeCount > maxTrans { e.transcludeCount = maxTrans + 1 return createInlineErrorTextAST(ref, "Too many transclusions (must be at most "+strconv.Itoa(maxTrans)+ ", see runtime configuration key max-transclusions)") } return nil } func makeBlockNodeAST(in ast.InlineNode) ast.BlockNode { return ast.CreateParaNode(in) } func setMetadataFromAttributesAST(m *meta.Meta, a zsx.Attributes) { for aKey, aVal := range a { if meta.KeyIsValid(aKey) { m.Set(aKey, meta.Value(aVal)) } } } func (e *evaluator) visitInlineSliceAST(is *ast.InlineSlice) { for i := 0; i < len(*is); i++ { in := (*is)[i] ast.Walk(e, in) switch n := in.(type) { case *ast.EmbedRefNode: i += embedNodeAST(is, i, e.evalEmbedRefNodeAST(n)) } } } func embedNodeAST(is *ast.InlineSlice, i int, in ast.InlineNode) int { if ln, ok := in.(*ast.InlineSlice); ok { *is = replaceWithInlineNodesAST(*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 replaceWithInlineNodesAST(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) evalEmbedRefNodeAST(en *ast.EmbedRefNode) ast.InlineNode { ref := en.Ref // To prevent e.embedCount from counting if errText := e.checkMaxTransclusionsAST(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 createInlineErrorImageAST(en) case ast.RefStateSelf: e.transcludeCount++ return createInlineErrorTextAST(ref, "Self embed reference") case ast.RefStateFound, ast.RefStateExternal: return en case ast.RefStateHosted, ast.RefStateBased: if n := createEmbeddedNodeLocalAST(ref); n != nil { n.Attrs = en.Attrs n.Inlines = en.Inlines return n } return en default: return createInlineErrorTextAST(ref, "Illegal inline state"+strconv.Itoa(int(ref.State))) } zid := mustParseZidAST(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 createInlineErrorImageAST(en) } if syntax := string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)); parser.IsImageFormat(syntax) { e.updateImageRefNodeAST(en, zettel.Meta, syntax) return en } else if !parser.IsASTParser(syntax) { // Not embeddable. e.transcludeCount++ return createInlineErrorTextAST(ref, "Not embeddable (syntax="+syntax+")") } cost, ok := e.costMap[zid] zn := cost.zn if zn == e.marker { e.transcludeCount++ return createInlineErrorTextAST(ref, "Recursive transclusion") } if !ok { ec := e.transcludeCount e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec} zn = e.evaluateEmbeddedZettelAST(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.embedMapAST[ref.Value] if !ok { // Search for text to be embedded. result = findInlineSliceAST(&zn.BlocksAST, ref.URL.Fragment) e.embedMapAST[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 mustParseZidAST(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) updateImageRefNodeAST(en *ast.EmbedRefNode, m *meta.Meta, syntax string) { en.Syntax = syntax if len(en.Inlines) == 0 { is := parseDescriptionAST(m) if len(is) > 0 { ast.Walk(e, &is) if len(is) > 0 { en.Inlines = is } } } } func parseDescriptionAST(m *meta.Meta) ast.InlineSlice { // Non-AST function in package parser. if m == nil { return nil } if summary, found := m.Get(meta.KeySummary); found { return ast.InlineSlice{&ast.TextNode{Text: sz.NormalizedSpacedText(string(summary))}} } if title, found := m.Get(meta.KeyTitle); found { return ast.InlineSlice{&ast.TextNode{Text: sz.NormalizedSpacedText(string(title))}} } return ast.InlineSlice{&ast.TextNode{Text: "Zettel without title/summary: " + m.Zid.String()}} } func createInlineErrorImageAST(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 createInlineErrorTextAST(ref *ast.Reference, message string) ast.InlineNode { text := message 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 createEmbeddedNodeLocalAST(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) evaluateEmbeddedZettelAST(zettel zettel.Zettel) *ast.Zettel { zn := parser.ParseZettel(e.ctx, zettel, string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), e.rtConfig) ast.Walk(e, &zn.BlocksAST) return zn } func findInlineSliceAST(bs *ast.BlockSlice, fragment string) ast.InlineSlice { if fragment == "" { return firstInlinesToEmbedAST(*bs) } fs := fragmentSearcherAST{fragment: fragment} ast.Walk(&fs, bs) return fs.result } func firstInlinesToEmbedAST(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 fragmentSearcherAST struct { fragment string result ast.InlineSlice } func (fs *fragmentSearcherAST) Visit(node ast.Node) ast.Visitor { if len(fs.result) > 0 { return nil } switch n := node.(type) { case *ast.BlockSlice: fs.visitBlockSliceAST(n) case *ast.InlineSlice: fs.visitInlineSliceAST(n) default: return fs } return nil } func (fs *fragmentSearcherAST) visitBlockSliceAST(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 *fragmentSearcherAST) visitInlineSliceAST(is *ast.InlineSlice) { for i, in := range *is { if mn, ok := in.(*ast.MarkNode); ok && mn.Fragment == fs.fragment { ris := skipBreakeNodesAST((*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 skipBreakeNodesAST(ins ast.InlineSlice) ast.InlineSlice { for i, in := range ins { switch in.(type) { case *ast.BreakNode: default: return ins[i:] } } return nil } |
Changes to internal/evaluator/list.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | "bytes" "context" "math" "slices" "strconv" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/query" ) | > > > > > > > > > | | | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | "bytes" "context" "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/ast/sztrans" "zettelstore.de/z/internal/query" ) // QueryActionAST transforms a list of metadata according to query actions into an AST nested list. func QueryActionAST(ctx context.Context, q *query.Query, ml []*meta.Meta) ast.BlockNode { bn, _ := QueryAction(ctx, q, ml) return sztrans.MustGetBlock(bn) } // 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: zsx.SymListUnordered, minVal: -1, maxVal: -1, } actions := q.Actions() if len(actions) == 0 { return ap.createBlockNodeMeta("") } acts := make([]string, 0, len(actions)) for _, act := range actions { if strings.HasPrefix(act, api.NumberedAction[0:1]) { 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 continue } |
︙ | ︙ | |||
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | case meta.TypeTagSet: return ap.createBlockNodeTagSet(key) } 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 } type actionPara struct { ctx context.Context q *query.Query ml []*meta.Meta | > | | | > > < > | | | | > < | < < < | | < > > | < > | | | < | < < < | | | < > | | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | case meta.TypeTagSet: return ap.createBlockNodeTagSet(key) } 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 } type actionPara struct { ctx context.Context q *query.Query ml []*meta.Meta kind *sx.Symbol minVal int maxVal 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 } ccs.SortByName() var items sx.ListBuilder count := 0 for _, cat := range ccs { buf.WriteString(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 zsx.MakeList(ap.kind, nil, items.List()), count } 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) countClassAttrs := ap.calcFontSizes(ccs) ccs.SortByName() var tags sx.ListBuilder count := 0 for i, cat := range ccs { if i > 0 { tags.Add(zsx.MakeText(" ")) } buf.WriteString(string(cat.Name)) 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 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 { minVal = ccs[len(ccs)-1].Count } |
︙ | ︙ | |||
177 178 179 180 181 182 183 | } return temp } } return ccs } | | | > | | < | | < | | | < | | < | | > < | < < < | | > | | | | | > < | < < < | 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | } return temp } } return ccs } 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) } } if len(arr) == 0 { return nil, 0 } ccs := arr.Counted() ccs.SortByName() var buf bytes.Buffer bufLen := ap.prepareSimpleQuery(&buf) 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) buf.WriteString(api.ActionSeparator) buf.WriteString(string(cat.Name)) q2 := buf.String() buf.Truncate(bufLen) 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 } var items sx.ListBuilder count := 0 for _, m := range ap.ml { if key != "" { if _, found := m.Get(key); !found { continue } } 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 } ccs := meta.CreateArrangement(ap.ml, key).Counted() |
︙ | ︙ | |||
281 282 283 284 285 286 287 | } return buf.Len() } const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css const fontSizes64 = float64(fontSizes) | | | < | | | 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | } 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]*sx.Pair { var fsAttrs [fontSizes]*sx.Pair for i := range fontSizes { 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]++ } countList := make([]int, 0, len(countMap)) for count := range countMap { countList = append(countList, count) } slices.Sort(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] curSize++ } |
︙ | ︙ |
Changes to internal/evaluator/metadata.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package evaluator import ( "t73f.de/r/zsc/domain/meta" | > > > > | | | > | < < < < < | < | < < < > > | | | < < < | > | | | | > > | < | < < | > | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package evaluator import ( "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 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.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | //----------------------------------------------------------------------------- // 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 } |
Changes to internal/parser/blob.go.
︙ | ︙ | |||
57 58 59 60 61 62 63 | }) } func parseBlob(inp *input.Input, m *meta.Meta, syntax string) *sx.Pair { if p := Get(syntax); p != nil { syntax = p.Name } | | | 57 58 59 60 61 62 63 64 65 | }) } func parseBlob(inp *input.Input, m *meta.Meta, syntax string) *sx.Pair { if p := Get(syntax); p != nil { syntax = p.Name } return zsx.MakeBlock(zsx.MakeBLOB(nil, syntax, inp.Src, ParseDescription(m))) } |
Changes to internal/parser/cleaner.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 | // cleaner provides functions to clean up the parsed AST. import ( "strconv" "strings" zerostrings "t73f.de/r/zero/strings" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/encoder" ) | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | < | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | // cleaner provides functions to clean up the parsed AST. 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 the given SZ syntax tree. func Clean(node *sx.Pair, allowHTML bool) { v1 := cleanPhase1{ids: idsNode{}, allowHTML: allowHTML} zsx.WalkIt(&v1, node, nil) if v1.hasMark { v2 := cleanPhase2{ids: v1.ids} zsx.WalkIt(&v2, node, nil) } } type cleanPhase1 struct { ids idsNode allowHTML bool hasMark bool // Mark nodes will be cleaned in phase 2 only } func (v *cleanPhase1) VisitBefore(node *sx.Pair, _ *sx.Pair) bool { if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol { switch sym { case zsx.SymBlock: if !v.allowHTML { curr, next := node, node.Tail() for next != nil { sy, ok := sx.GetSymbol(next.Head().Car()) if !ok || sy != zsx.SymVerbatimHTML { curr = next next = next.Tail() } else { next = next.Tail() curr.SetCdr(next) } } } case zsx.SymMark: v.hasMark = true } } return false } func (v *cleanPhase1) VisitAfter(node *sx.Pair, _ *sx.Pair) { 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 { return } slugText := zerostrings.Slugify(sb.String()) slugNode.SetCar(sx.MakeString(slugText)) fragmentNode.SetCar(sx.MakeString(v.ids.addIdentifier(slugText, node))) } } } type cleanPhase2 struct { ids idsNode } func (v *cleanPhase2) VisitBefore(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) VisitAfter(*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 := ids[newID]; !ok2 || n2 == node { ids[newID] = node return newID } } } ids[id] = node return id } // CleanAST cleans the given block list. func CleanAST(bs *ast.BlockSlice, allowHTML bool) { cv := cleanASTVisitor{ allowHTML: allowHTML, hasMark: false, doMark: false, } ast.Walk(&cv, bs) if cv.hasMark { cv.doMark = true ast.Walk(&cv, bs) } } type cleanASTVisitor struct { ids map[string]ast.Node allowHTML bool hasMark bool doMark bool } func (cv *cleanASTVisitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: if !cv.allowHTML { cv.visitBlockSlice(n) return nil } case *ast.InlineSlice: |
︙ | ︙ | |||
65 66 67 68 69 70 71 | case *ast.MarkNode: cv.visitMark(n) return nil } return cv } | | | 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | case *ast.MarkNode: cv.visitMark(n) return nil } return cv } func (cv *cleanASTVisitor) visitBlockSlice(bs *ast.BlockSlice) { if bs == nil { return } if len(*bs) == 0 { *bs = nil return } |
︙ | ︙ | |||
96 97 98 99 100 101 102 | } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] } | | | > | < | | | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] } func (cv *cleanASTVisitor) 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 *cleanASTVisitor) visitHeading(hn *ast.HeadingNode) { if cv.doMark || hn == nil || len(hn.Inlines) == 0 { return } if hn.Slug == "" { var sb strings.Builder var textEnc encoder.TextEncoder if err := textEnc.WriteInlines(&sb, &hn.Inlines); err != nil { return } hn.Slug = zerostrings.Slugify(sb.String()) } if hn.Slug != "" { hn.Fragment = cv.addIdentifier(hn.Slug, hn) } } func (cv *cleanASTVisitor) 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 *cleanASTVisitor) 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 { prefix := id + "-" for count := 1; ; count++ { |
︙ | ︙ |
Added internal/parser/cleaner_test.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | //----------------------------------------------------------------------------- // 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 allowHTML bool 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: "remove-html-0", src: "(BLOCK (VERBATIM-HTML () \"<h1>Heading</h1>\"))", exp: "(BLOCK)"}, {name: "remove-html-0-1", src: "(BLOCK (VERBATIM-HTML () \"<h1>Heading</h1>\") (PARA (TEXT \"ABC\")))", exp: "(BLOCK (PARA (TEXT \"ABC\")))"}, {name: "remove-html-1-0", src: "(BLOCK (PARA (TEXT \"ABC\")) (VERBATIM-HTML () \"<h1>Heading</h1>\"))", exp: "(BLOCK (PARA (TEXT \"ABC\")))"}, {name: "remove-html-1-2", src: "(BLOCK (PARA (TEXT \"ABC\")) (VERBATIM-HTML () \"<h1>Heading</h1>\") (VERBATIM-HTML () \"<h2>Head</h2>\"))", exp: "(BLOCK (PARA (TEXT \"ABC\")))"}, {name: "allow HTML", allowHTML: true, src: "(BLOCK (VERBATIM-HTML () \"<h1>Heading</h1>\"))", exp: "(BLOCK (VERBATIM-HTML () \"<h1>Heading</h1>\"))"}, {name: "allow-html-1-2", allowHTML: true, src: "(BLOCK (PARA (TEXT \"ABC\")) (VERBATIM-HTML () \"<h1>Heading</h1>\") (VERBATIM-HTML () \"<h2>Head</h2>\"))", exp: "(BLOCK (PARA (TEXT \"ABC\")) (VERBATIM-HTML () \"<h1>Heading</h1>\") (VERBATIM-HTML () \"<h2>Head</h2>\"))"}, } 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, tc.allowHTML) if got := node.String(); got != tc.exp { t.Errorf("\nexpected: %q\n but got: %q", tc.exp, got) } }) } } |
Changes to internal/parser/draw.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "strconv" "t73f.de/r/sx" "t73f.de/r/webs/aasvg" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "t73f.de/r/zsx/input" | < < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "strconv" "t73f.de/r/sx" "t73f.de/r/webs/aasvg" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "t73f.de/r/zsx/input" ) func init() { register(&Info{ Name: meta.ValueSyntaxDraw, AltNames: []string{}, IsASTParser: true, |
︙ | ︙ | |||
59 60 61 62 63 64 65 | if err != nil { return zsx.MakeBlock(zsx.MakeParaList(canvasErrMsg(err))) } svg := aasvg.CanvasToSVG(canvas, string(font), int(scaleX), int(scaleY)) if len(svg) == 0 { return zsx.MakeBlock(zsx.MakeParaList(noSVGErrMsg())) } | | | > | | | | | | | | | | > > | | < | | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | if err != nil { return zsx.MakeBlock(zsx.MakeParaList(canvasErrMsg(err))) } 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, meta.ValueSyntaxSVG, svg, ParseDescription(m))) } // ParseDrawBlock parses the content of an eval verbatim node into an SVG image BLOB. func ParseDrawBlock(attrs *sx.Pair, content []byte) *sx.Pair { a := zsx.GetAttributes(attrs) font := defaultFont if val, found := a.Get("font"); found { font = val } scaleX := getScaleAST(a, "x-scale", defaultScaleX) scaleY := getScaleAST(a, "y-scale", defaultScaleY) canvas, err := aasvg.NewCanvas(content) if err != nil { 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 zsx.MakePara(zsx.MakeText("NO IMAGE")) } return zsx.MakeBLOB( nil, meta.ValueSyntaxSVG, svg, nil, // TODO: look for attribute "summary" / "title" for a description. ) } 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 } } return defVal } |
︙ | ︙ |
Changes to internal/parser/draw_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | import ( "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | < | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import ( "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "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) }) } |
Changes to internal/parser/none.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //----------------------------------------------------------------------------- package parser import ( "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" ) // none provides a none-parser, e.g. for zettel with just metadata. func init() { register(&Info{ Name: meta.ValueSyntaxNone, AltNames: []string{}, IsASTParser: false, IsTextFormat: false, IsImageFormat: false, | > | > > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //----------------------------------------------------------------------------- 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. func init() { register(&Info{ Name: meta.ValueSyntaxNone, AltNames: []string{}, IsASTParser: false, IsTextFormat: false, IsImageFormat: false, Parse: func(inp *input.Input, _ *meta.Meta, _ string) *sx.Pair { return sz.ParseNoneBlocks(inp) }, }) } |
Changes to internal/parser/parser.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import ( "context" "fmt" "log" "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "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" | > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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" |
︙ | ︙ | |||
93 94 95 96 97 98 99 | pi, ok := registry[syntax] if !ok { return false } return pi.IsImageFormat } | | | < | | < < < < < < < < < < < < < < < | | | < < < < | | > | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | pi, ok := registry[syntax] if !ok { return false } return pi.IsImageFormat } // 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) (*sx.Pair, ast.BlockSlice) { if obj := Get(syntax).Parse(inp, m, syntax); obj != nil { bs, err := sztrans.GetBlockSlice(obj) if err == nil { return obj, bs } log.Printf("sztrans error: %v, for %v\n", err, obj) } return nil, nil } // 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(sz.NormalizedSpacedText(string(summary))), sx.Nil()) } if title, found := m.Get(meta.KeyTitle); found { 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.Zettel { m := zettel.Meta inhMeta := m if rtConfig != nil { inhMeta = rtConfig.AddDefaultValues(ctx, inhMeta) } if syntax == "" { syntax = string(inhMeta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)) } parseMeta := inhMeta if syntax == meta.ValueSyntaxNone { parseMeta = m } rootNode, bs := Parse(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax) return &ast.Zettel{ Meta: m, Content: zettel.Content, Zid: m.Zid, InhMeta: inhMeta, Blocks: rootNode, BlocksAST: bs, Syntax: syntax, } } |
Changes to internal/parser/plain.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import ( "bytes" "t73f.de/r/sx" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "t73f.de/r/zsx/input" ) func init() { register(&Info{ Name: meta.ValueSyntaxTxt, AltNames: []string{meta.ValueSyntaxPlain, meta.ValueSyntaxText}, IsASTParser: false, IsTextFormat: true, IsImageFormat: false, Parse: parsePlain, }) register(&Info{ Name: meta.ValueSyntaxHTML, AltNames: []string{}, IsASTParser: false, IsTextFormat: true, IsImageFormat: false, | > | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | import ( "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() { register(&Info{ Name: meta.ValueSyntaxTxt, AltNames: []string{meta.ValueSyntaxPlain, meta.ValueSyntaxText}, IsASTParser: false, IsTextFormat: true, IsImageFormat: false, Parse: parsePlain, }) register(&Info{ Name: meta.ValueSyntaxHTML, AltNames: []string{}, IsASTParser: false, IsTextFormat: true, IsImageFormat: false, Parse: parsePlain, }) register(&Info{ Name: meta.ValueSyntaxCSS, AltNames: []string{}, IsASTParser: false, IsTextFormat: true, IsImageFormat: false, |
︙ | ︙ | |||
65 66 67 68 69 70 71 | IsTextFormat: true, IsImageFormat: false, Parse: parsePlainSxn, }) } func parsePlain(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair { | | < < < < < < < < < < | | | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | IsTextFormat: true, IsImageFormat: false, Parse: parsePlainSxn, }) } func parsePlain(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair { return sz.ParsePlainBlocks(inp, syntax) } func parsePlainSVG(inp *input.Input, _ *meta.Meta, syntax string) *sx.Pair { is := parseSVGInlines(inp, syntax) if is == 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.MakeEmbedBLOBuncode(nil, syntax, svgSrc, nil), sx.Nil()) } func scanSVG(inp *input.Input) string { inp.SkipSpace() pos := inp.Pos if !inp.Accept("<svg") { return "" |
︙ | ︙ |
Changes to internal/parser/plain_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | import ( "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | | < | > > > | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | import ( "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "zettelstore.de/z/internal/ast/sztrans" "zettelstore.de/z/internal/parser" ) func TestParseSVG(t *testing.T) { testCases := []struct { name string src string exp string }{ {"common", " <svg bla", "(BLOCK (PARA (EMBED-BLOB () \"svg\" \"<svg bla\")))"}, {"inkscape", "<svg\nbla", "(BLOCK (PARA (EMBED-BLOB () \"svg\" \"<svg\\nbla\")))"}, {"selfmade", "<svg>", "(BLOCK (PARA (EMBED-BLOB () \"svg\" \"<svg>\")))"}, {"error", "<svgbla", "(BLOCK)"}, {"error-", "<svg-bla", "(BLOCK)"}, {"error#", "<svg2bla", "(BLOCK)"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { inp := input.NewInput([]byte(tc.src)) node, bs := parser.Parse(inp, nil, meta.ValueSyntaxSVG) if got := node.String(); tc.exp != got { t.Errorf("\nexp: %q\ngot: %q", tc.exp, got) } trans := sztrans.NewSzTransformer() lst := trans.GetSz(&bs) if got := lst.String(); tc.exp != got { t.Errorf("\nexp: %q\ngot: %q", tc.exp, got) } }) } } |
Changes to internal/parser/zettelmark.go.
︙ | ︙ | |||
26 27 28 29 30 31 32 | register(&Info{ Name: meta.ValueSyntaxZmk, AltNames: nil, IsASTParser: true, IsTextFormat: true, IsImageFormat: false, Parse: func(inp *input.Input, _ *meta.Meta, _ string) *sx.Pair { | | | | | 26 27 28 29 30 31 32 33 34 35 36 37 38 | register(&Info{ Name: meta.ValueSyntaxZmk, AltNames: nil, IsASTParser: true, IsTextFormat: true, IsImageFormat: false, Parse: func(inp *input.Input, _ *meta.Meta, _ string) *sx.Pair { var zmkParser zmk.Parser zmkParser.Initialize(inp) return zmkParser.Parse() }, }) } |
Deleted internal/parser/zettelmark_fuzz_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/parser/zettelmark_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to internal/usecase/evaluate.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/parser" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/zettel" | > > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/sx" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/parser" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/zettel" |
︙ | ︙ | |||
40 41 42 43 44 45 46 | rtConfig: rtConfig, ucGetZettel: ucGetZettel, ucQuery: ucQuery, } } // Run executes the use case. | | | | | | < | < | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | rtConfig: rtConfig, ucGetZettel: ucGetZettel, ucQuery: ucQuery, } } // Run executes the use case. func (uc *Evaluate) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.Zettel, error) { zettel, err := uc.ucGetZettel.Run(ctx, zid) if err != nil { return nil, err } return uc.RunZettel(ctx, zettel, syntax), nil } // RunZettel executes the use case for a given zettel. func (uc *Evaluate) RunZettel(ctx context.Context, zettel zettel.Zettel, syntax string) *ast.Zettel { zn := parser.ParseZettel(ctx, zettel, syntax, uc.rtConfig) evaluator.EvaluateZettel(ctx, uc, uc.rtConfig, zn) return zn } // RunBlockNode executes the use case for a metadata list, formatted as a block. func (uc *Evaluate) RunBlockNode(ctx context.Context, block *sx.Pair) ast.BlockSlice { if block == nil { return nil } return evaluator.EvaluateBlock(ctx, uc, uc.rtConfig, zsx.MakeBlock(block)) } // GetZettel retrieves the full zettel of a given zettel identifier. func (uc *Evaluate) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { return uc.ucGetZettel.Run(ctx, zid) } // QueryMeta returns a list of metadata that comply to the given selection criteria. func (uc *Evaluate) QueryMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) { return uc.ucQuery.Run(ctx, q) } |
Changes to internal/usecase/get_references.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- package usecase import ( "iter" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/domain/meta" | > > > < | > | > | | | | | | | | | | > > | > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | //----------------------------------------------------------------------------- package usecase import ( "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" "zettelstore.de/z/internal/collect" ) // GetReferences is the usecase to retrieve references that occur in a zettel. type GetReferences struct{} // NewGetReferences creates a new usecase object. func NewGetReferences() GetReferences { return GetReferences{} } // RunByState returns all references of a zettel, sparated by their state: // local, external, query. No zettel references are returned. func (uc GetReferences) RunByState(block *sx.Pair) (local, ext, query *sx.Pair) { var lbLoc, lbQueries, lbExt sx.ListBuilder for ref := range collect.ReferenceSeq(block) { sym, _ := zsx.GetReference(ref) switch sym { case zsx.SymRefStateHosted, sz.SymRefStateBased: lbLoc.Add(ref) case zsx.SymRefStateExternal: lbExt.Add(ref) case sz.SymRefStateQuery: lbQueries.Add(ref) } } return lbLoc.List(), lbExt.List(), lbQueries.List() } // RunByExternal returns an iterator of all external references of a zettel. func (uc GetReferences) RunByExternal(blocks *sx.Pair) iter.Seq[*sx.Pair] { return zeroiter.FilterSeq( collect.ReferenceSeq(blocks), func(ref *sx.Pair) bool { sym, _ := zsx.GetReference(ref) return zsx.SymRefStateExternal.IsEqualSymbol(sym) }) } // RunByMeta returns all URLs that are stored in the metadata. func (uc GetReferences) RunByMeta(m *meta.Meta) iter.Seq[string] { return func(yield func(string) bool) { for key, val := range m.All() { if meta.Type(key) == meta.TypeURL && !yield(string(val)) { |
︙ | ︙ |
Changes to internal/usecase/parse_zettel.go.
︙ | ︙ | |||
31 32 33 34 35 36 37 | // NewParseZettel creates a new use case. func NewParseZettel(rtConfig config.Config, getZettel GetZettel) ParseZettel { return ParseZettel{rtConfig: rtConfig, getZettel: getZettel} } // Run executes the use case. | | | > > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // NewParseZettel creates a new use case. func NewParseZettel(rtConfig config.Config, getZettel GetZettel) ParseZettel { return ParseZettel{rtConfig: rtConfig, getZettel: getZettel} } // Run executes the use case. func (uc ParseZettel) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.Zettel, error) { zettel, err := uc.getZettel.Run(ctx, zid) if err != nil { return nil, err } z := parser.ParseZettel(ctx, zettel, syntax, uc.rtConfig) parser.Clean(z.Blocks, uc.rtConfig.GetHTMLInsecurity().AllowHTML(syntax)) return z, nil } |
Changes to internal/usecase/query.go.
︙ | ︙ | |||
128 129 130 131 132 133 134 | func (uc *Query) processItemsDirective(ctx context.Context, _ *query.ItemsSpec, metaSeq []*meta.Meta) []*meta.Meta { result := make([]*meta.Meta, 0, len(metaSeq)) for _, m := range metaSeq { zn, err := uc.ucEvaluate.Run(ctx, m.Zid, string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax))) if err != nil { continue } | | | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | func (uc *Query) processItemsDirective(ctx context.Context, _ *query.ItemsSpec, metaSeq []*meta.Meta) []*meta.Meta { result := make([]*meta.Meta, 0, len(metaSeq)) for _, m := range metaSeq { zn, err := uc.ucEvaluate.Run(ctx, m.Zid, string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax))) if err != nil { continue } for _, ln := range collect.OrderAST(&zn.BlocksAST) { ref := ln.Ref if !ref.IsZettel() { continue } if collectedZid, err2 := id.Parse(ref.URL.Path); err2 == nil { if z, err3 := uc.port.GetZettel(ctx, collectedZid); err3 == nil { |
︙ | ︙ | |||
200 201 202 203 204 205 206 | func (uc *Query) filterCandidates(ctx context.Context, candidates []*meta.Meta, words []string) []*meta.Meta { result := make([]*meta.Meta, 0, len(candidates)) for _, cand := range candidates { zettel, err := uc.port.GetZettel(ctx, cand.Zid) if err != nil { continue } | | | | | | | | | | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | func (uc *Query) filterCandidates(ctx context.Context, candidates []*meta.Meta, words []string) []*meta.Meta { result := make([]*meta.Meta, 0, len(candidates)) for _, cand := range candidates { zettel, err := uc.port.GetZettel(ctx, cand.Zid) if err != nil { continue } v := unlinkedVisitorAST{ words: words, found: false, } v.text = v.joinWords(words) syntax := string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)) if !parser.IsASTParser(syntax) { continue } zn := uc.ucEvaluate.RunZettel(ctx, zettel, syntax) ast.Walk(&v, &zn.BlocksAST) if v.found { result = append(result, cand) } } return result } func (*unlinkedVisitorAST) joinWords(words []string) string { return " " + strings.ToLower(strings.Join(words, " ")) + " " } type unlinkedVisitorAST struct { words []string text string found bool } func (v *unlinkedVisitorAST) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.InlineSlice: v.checkWordsAST(n) return nil case *ast.HeadingNode: return nil case *ast.LinkNode, *ast.EmbedRefNode, *ast.EmbedBLOBNode, *ast.CiteNode: return nil } return v } func (v *unlinkedVisitorAST) checkWordsAST(is *ast.InlineSlice) { if len(*is) < 2*len(v.words)-1 { return } for _, text := range v.splitInlineTextListAST(is) { if strings.Contains(text, v.text) { v.found = true } } } func (v *unlinkedVisitorAST) splitInlineTextListAST(is *ast.InlineSlice) []string { var result []string var curList []string for _, in := range *is { switch n := in.(type) { case *ast.TextNode: curList = append(curList, zerostrings.MakeWords(n.Text)...) default: |
︙ | ︙ |
Changes to internal/web/adapter/api/get_references.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 25 | "iter" "net/http" "t73f.de/r/sx" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" | > < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "iter" "net/http" "t73f.de/r/sx" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsx" "zettelstore.de/z/internal/usecase" "zettelstore.de/z/internal/web/content" ) // MakeGetReferencesHandler creates a new HTTP handler to return various lists // of zettel references. func (a *API) MakeGetReferencesHandler( |
︙ | ︙ | |||
49 50 51 52 53 54 55 | var seq iter.Seq[string] q := r.URL.Query() switch getPart(q, partZettel) { case partZettel: seq = zeroiter.CatSeq( ucGetReferences.RunByMeta(zn.InhMeta), | | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | var seq iter.Seq[string] q := r.URL.Query() switch getPart(q, partZettel) { case partZettel: seq = zeroiter.CatSeq( ucGetReferences.RunByMeta(zn.InhMeta), getExternalURLs(zn.Blocks, ucGetReferences), ) case partMeta: seq = ucGetReferences.RunByMeta(zn.InhMeta) case partContent: seq = getExternalURLs(zn.Blocks, ucGetReferences) } enc, _ := getEncoding(r, q) if enc == api.EncoderData { var lb sx.ListBuilder lb.Collect(zeroiter.MapSeq(seq, func(s string) sx.Object { return sx.MakeString(s) })) if err = a.writeObject(w, zid, lb.List()); err != nil { |
︙ | ︙ | |||
78 79 80 81 82 83 84 | } if err = writeBuffer(w, &buf, content.PlainText); err != nil { a.logger.Error("write plain data", "err", err, "zid", zid) } }) } | | | | > > > | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | } if err = writeBuffer(w, &buf, content.PlainText); err != nil { a.logger.Error("write plain data", "err", err, "zid", zid) } }) } func getExternalURLs(blocks *sx.Pair, ucGetReferences usecase.GetReferences) iter.Seq[string] { return zeroiter.MapSeq( ucGetReferences.RunByExternal(blocks), func(ref *sx.Pair) string { _, val := zsx.GetReference(ref) return val }, ) } |
Changes to internal/web/adapter/api/get_zettel.go.
︙ | ︙ | |||
53 54 55 56 57 58 59 | case api.EncoderPlain: a.writePlainData(ctx, w, zid, part, getZettel) case api.EncoderData: a.writeSzData(ctx, w, zid, part, getZettel) default: | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | case api.EncoderPlain: a.writePlainData(ctx, w, zid, part, getZettel) case api.EncoderData: a.writeSzData(ctx, w, zid, part, getZettel) default: var zn *ast.Zettel if q.Has(api.QueryKeyParseOnly) { zn, err = parseZettel.Run(ctx, zid, q.Get(meta.KeySyntax)) } else { zn, err = evaluate.Run(ctx, zid, q.Get(meta.KeySyntax)) } if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ | |||
138 139 140 141 142 143 144 | if err = a.writeObject(w, zid, obj); err != nil { a.logger.Error("write sx data", "err", err, "zid", zid) } } func (a *API) writeEncodedZettelPart( ctx context.Context, | | | | | > > | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | if err = a.writeObject(w, zid, obj); err != nil { a.logger.Error("write sx data", "err", err, "zid", zid) } } func (a *API) writeEncodedZettelPart( ctx context.Context, w http.ResponseWriter, zn *ast.Zettel, enc api.EncodingEnum, encStr string, part partType, ) { encdr := encoder.Create( enc, &encoder.CreateParameter{ Lang: a.rtConfig.Get(ctx, zn.InhMeta, meta.KeyLang), }) if encdr == nil { adapter.BadRequest(w, fmt.Sprintf("Zettel %q not available in encoding %q", zn.Meta.Zid, encStr)) return } var err error var buf bytes.Buffer switch part { case partZettel: err = encdr.WriteZettel(&buf, zn) case partMeta: err = encdr.WriteMeta(&buf, zn.InhMeta) case partContent: err = encdr.WriteBlocks(&buf, &zn.BlocksAST) case partSz: // TEMP err = encdr.WriteSz(&buf, zn.Blocks) } if err != nil { a.logger.Error("Unable to store data in buffer", "err", err, "zid", zn.Zid) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if buf.Len() == 0 { |
︙ | ︙ |
Changes to internal/web/adapter/api/request.go.
︙ | ︙ | |||
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | type partType int const ( _ partType = iota partMeta partContent partZettel ) var partMap = map[string]partType{ api.PartMeta: partMeta, api.PartContent: partContent, api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } return defPart | > > | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | type partType int const ( _ partType = iota partMeta partContent partZettel partSz // TEMP: SZ encoded content ) var partMap = map[string]partType{ api.PartMeta: partMeta, api.PartContent: partContent, api.PartZettel: partZettel, "sz": partSz, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } return defPart |
︙ | ︙ |
Changes to internal/web/adapter/webui/create_zettel.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 26 | "net/http" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" | > < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "net/http" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/sz" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/encoder" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/usecase" "zettelstore.de/z/internal/web/adapter" "zettelstore.de/z/internal/zettel" |
︙ | ︙ | |||
61 62 63 64 65 66 67 | roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "", roleData, syntaxData) case actionFolge: wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "", roleData, syntaxData) case actionNew: | | | | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "", roleData, syntaxData) case actionFolge: wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "", roleData, syntaxData) case actionNew: title := sz.NormalizedSpacedText(origZettel.Meta.GetTitle()) newTitle := sz.NormalizedSpacedText(q.Get(meta.KeyTitle)) wui.renderZettelForm(ctx, w, createZettel.PrepareNew(origZettel, newTitle), title, "", roleData, syntaxData) case actionSequel: wui.renderZettelForm(ctx, w, createZettel.PrepareSequel(origZettel), "Sequel Zettel", "", roleData, syntaxData) } }) } |
︙ | ︙ | |||
172 173 174 175 176 177 178 | wui.reportError(ctx, w, err) return } entries, _ := evaluator.QueryAction(ctx, q, metaSeq) bns := evaluate.RunBlockNode(ctx, entries) enc := encoder.Create(api.EncoderZmk, nil) var zmkContent bytes.Buffer | | < | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | wui.reportError(ctx, w, err) return } entries, _ := evaluator.QueryAction(ctx, q, metaSeq) bns := evaluate.RunBlockNode(ctx, entries) enc := encoder.Create(api.EncoderZmk, nil) var zmkContent bytes.Buffer if err = enc.WriteBlocks(&zmkContent, &bns); err != nil { wui.reportError(ctx, w, err) return } m := meta.New(id.Invalid) m.Set(meta.KeyTitle, meta.Value(q.Human())) m.Set(meta.KeySyntax, meta.ValueSyntaxZmk) |
︙ | ︙ |
Changes to internal/web/adapter/webui/get_info.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 27 | "strings" "t73f.de/r/sx" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" | > > < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "strings" "t73f.de/r/sx" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/api" "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/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/encoder" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/usecase" ) |
︙ | ︙ | |||
63 64 65 66 67 68 69 | getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel) var lbMetadata sx.ListBuilder for key, val := range zn.Meta.Computed() { sxval := wui.writeHTMLMetaValue(key, val, getTextTitle) lbMetadata.Add(sx.Cons(sx.MakeString(key), sxval)) } | | | | | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel) var lbMetadata sx.ListBuilder for key, val := range zn.Meta.Computed() { sxval := wui.writeHTMLMetaValue(key, val, getTextTitle) lbMetadata.Add(sx.Cons(sx.MakeString(key), sxval)) } locLinks, extLinks, queryLinks := wui.getLocalExtQueryLinks(ucGetReferences, zn.Blocks) title := sz.NormalizedSpacedText(zn.InhMeta.GetTitle()) phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { phrase = title } unlinkedMeta, err := ucQuery.Run(ctx, createUnlinkedQuery(zid, phrase)) if err != nil { wui.reportError(ctx, w, err) return } enc := wui.getSimpleHTMLEncoder(wui.getConfig(ctx, zn.InhMeta, meta.KeyLang)) entries, _ := evaluator.QueryAction(ctx, nil, unlinkedMeta) bns := ucEvaluate.RunBlockNode(ctx, entries) unlinkedContent, _, err := enc.BlocksSxnAST(&bns) if err != nil { wui.reportError(ctx, w, err) return } encTexts := encodingTexts() shadowLinks := getShadowLinks(ctx, zid, zn.InhMeta.GetDefault(meta.KeyBoxNumber, ""), ucGetAllZettel) |
︙ | ︙ | |||
111 112 113 114 115 116 117 | } if err != nil { wui.reportError(ctx, w, err) } }) } | | | | > | | > | | > | | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) getLocalExtQueryLinks(ucGetReferences usecase.GetReferences, blocks *sx.Pair) (locLinks, extLinks, queries *sx.Pair) { locRefs, extRefs, queryRefs := ucGetReferences.RunByState(blocks) var lbLoc, lbQueries, lbExt sx.ListBuilder for ref := range locRefs.Pairs() { _, value := zsx.GetReference(ref.Head()) lbLoc.Add(sx.MakeString(value)) } for ref := range extRefs.Pairs() { _, value := zsx.GetReference(ref.Head()) lbExt.Add(sx.MakeString(value)) } for ref := range queryRefs.Pairs() { _, value := zsx.GetReference(ref.Head()) lbQueries.Add( sx.Cons( sx.MakeString(value), sx.MakeString(wui.NewURLBuilder('h').AppendQuery(value).String()))) } return lbLoc.List(), lbExt.List(), lbQueries.List() } func createUnlinkedQuery(zid id.Zid, phrase string) *query.Query { var sb strings.Builder sb.Write(zid.Bytes()) |
︙ | ︙ | |||
161 162 163 164 165 166 167 168 | return encTexts } var apiParts = []string{api.PartZettel, api.PartMeta, api.PartContent} func (wui *WebUI) infoAPIMatrix(zid id.Zid, parseOnly bool, encTexts []string) *sx.Pair { matrix := sx.Nil() u := wui.NewURLBuilder('z').SetZid(zid) | > > > > | | | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | return encTexts } var apiParts = []string{api.PartZettel, api.PartMeta, api.PartContent} func (wui *WebUI) infoAPIMatrix(zid id.Zid, parseOnly bool, encTexts []string) *sx.Pair { matrix := sx.Nil() apiPartsAndSz := apiParts if parseOnly { apiPartsAndSz = append(apiPartsAndSz, "sz") // TEMP } u := wui.NewURLBuilder('z').SetZid(zid) for ip := len(apiPartsAndSz) - 1; ip >= 0; ip-- { part := apiPartsAndSz[ip] row := sx.Nil() for je := len(encTexts) - 1; je >= 0; je-- { enc := encTexts[je] if parseOnly { u.AppendKVQuery(api.QueryKeyParseOnly, "") } u.AppendKVQuery(api.QueryKeyPart, part) |
︙ | ︙ |
Changes to internal/web/adapter/webui/get_zettel.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 27 | "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" | > < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsc/sz" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/usecase" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". |
︙ | ︙ | |||
52 53 54 55 56 57 58 | wui.reportError(ctx, w, err) return } zettelLang := wui.getConfig(ctx, zn.InhMeta, meta.KeyLang) enc := wui.getSimpleHTMLEncoder(zettelLang) metaObj := enc.MetaSxn(zn.InhMeta) | | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | wui.reportError(ctx, w, err) return } zettelLang := wui.getConfig(ctx, zn.InhMeta, meta.KeyLang) enc := wui.getSimpleHTMLEncoder(zettelLang) metaObj := enc.MetaSxn(zn.InhMeta) content, endnotes, err := enc.BlocksSxnAST(&zn.BlocksAST) if err != nil { wui.reportError(ctx, w, err) return } user := user.GetCurrentUser(ctx) getTextTitle := wui.makeGetTextTitle(ctx, getZettel) title := sz.NormalizedSpacedText(zn.InhMeta.GetTitle()) env, rb := wui.createRenderEnvironment(ctx, "zettel", zettelLang, title, user) rb.bindSymbol(symMetaHeader, metaObj) rb.bindString("heading", sx.MakeString(title)) if role, found := zn.InhMeta.Get(meta.KeyRole); found && role != "" { rb.bindString( "role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery( |
︙ | ︙ |
Changes to internal/web/adapter/webui/htmlgen.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | import ( "maps" "net/url" "slices" "strings" "t73f.de/r/sx" | < | < | | | | | | < | | | | > | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | import ( "maps" "net/url" "slices" "strings" "t73f.de/r/sx" "t73f.de/r/zero/set" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "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" "zettelstore.de/z/internal/ast/sztrans" ) // Builder allows to build new URLs for the web service. type urlBuilder interface { NewURLBuilder(key byte) *api.URLBuilder } type htmlGenerator struct { tx sztrans.SzTransformer th *shtml.Evaluator lang string symAt *sx.Symbol } func (wui *WebUI) createGenerator(builder urlBuilder, lang string) *htmlGenerator { th := shtml.NewEvaluator(1) findA := func(obj sx.Object) (assoc, rest *sx.Pair) { pair, isTag := sx.GetPair(obj) if !isTag || !shtml.SymA.IsEqual(pair.Car()) { return nil, nil } rest = pair.Tail() if rest == nil { return nil, nil } if attr, isAssoc := sx.GetPair(rest.Car()); isAssoc { if _, isAttr := sx.GetPair(attr.Car()); isAttr { return attr, rest.Tail() } } return nil, nil } rebindWrap(th, zsx.SymLink, func(args sx.Vector, env *shtml.Environment, prevFn shtml.EvalFn) sx.Object { refSym, _ := shtml.GetReference(args[1], env) obj := prevFn(args, env) assoc, rest := findA(obj) if assoc == nil { return obj } if zsx.SymRefStateExternal.IsEqual(refSym) { a := zsx.GetAttributes(assoc) a = a.Set("target", "_blank") a = a.Add("rel", "external").Add("rel", "noreferrer") return rest.Cons(shtml.EvaluateAttributes(a)).Cons(shtml.SymA) } hrefP := assoc.Assoc(shtml.SymAttrHref) if hrefP == nil { return obj |
︙ | ︙ | |||
95 96 97 98 99 100 101 | if err == nil { u = u.SetZid(zid) } if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) | | | | | > > > > | | | | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | if err == nil { u = u.SetZid(zid) } if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) return rest.Cons(assoc).Cons(shtml.SymA) case sz.SymRefStateQuery: ur, err := url.Parse(href.GetValue()) if err != nil { return obj } urlQuery := ur.Query() if !urlQuery.Has(api.QueryKeyQuery) { return obj } u := builder.NewURLBuilder('h') if q := urlQuery.Get(api.QueryKeyQuery); q != "" { u = u.AppendQuery(q) } assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) return rest.Cons(assoc).Cons(shtml.SymA) case sz.SymRefStateBased: u := builder.NewURLBuilder('/') assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()+href.GetValue()[1:]))) return rest.Cons(assoc).Cons(shtml.SymA) } return obj }) rebind(th, zsx.SymEmbed, func(obj sx.Object) sx.Object { pair, isPair := sx.GetPair(obj) if !isPair || !shtml.SymIMG.IsEqual(pair.Car()) { return obj } attr, isPair := sx.GetPair(pair.Tail().Car()) if !isPair { return obj } _, isAttr := sx.GetPair(attr.Car()) if !isAttr { return obj } srcP := attr.Assoc(shtml.SymAttrSrc) if srcP == nil { return obj } src, isString := sx.GetString(srcP.Cdr()) if !isString { return obj } zid, err := id.Parse(src.GetValue()) if err != nil { return obj } u := builder.NewURLBuilder('z').SetZid(zid) imgAttr := attr.Tail().Cons(sx.Cons(shtml.SymAttrSrc, sx.MakeString(u.String()))) return pair.Tail().Tail().Cons(imgAttr).Cons(shtml.SymIMG) }) return &htmlGenerator{ tx: sztrans.NewSzTransformer(), th: th, lang: lang, } } func rebind(ev *shtml.Evaluator, sym *sx.Symbol, fn func(sx.Object) sx.Object) { prevFn := ev.ResolveBinding(sym) |
︙ | ︙ | |||
185 186 187 188 189 190 191 | var mapMetaKey = map[string]string{ meta.KeyCopyright: "copyright", meta.KeyLicense: "license", } func (g *htmlGenerator) MetaSxn(m *meta.Meta) *sx.Pair { | | | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | var mapMetaKey = map[string]string{ meta.KeyCopyright: "copyright", meta.KeyLicense: "license", } func (g *htmlGenerator) MetaSxn(m *meta.Meta) *sx.Pair { tm := sztrans.GetMetaSz(m) env := shtml.MakeEnvironment(g.lang) hm, err := g.th.Evaluate(tm, &env) if err != nil { return nil } ignore := set.New(meta.KeyTitle, meta.KeyLang) |
︙ | ︙ | |||
258 259 260 261 262 263 264 | metaTags := sb.String() if len(metaTags) == 0 { return nil } return g.th.EvaluateMeta(zsx.Attributes{"name": "keywords", "content": metaTags}) } | | | < | > > > | < | > > > > | | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | metaTags := sb.String() if len(metaTags) == 0 { return nil } return g.th.EvaluateMeta(zsx.Attributes{"name": "keywords", "content": metaTags}) } func (g *htmlGenerator) BlocksSxn(block *sx.Pair) (content, endnotes *sx.Pair, _ error) { if block == nil || block.Tail() == nil { return nil, nil, nil } env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(block, &env) if err != nil { return nil, nil, err } return sh, shtml.Endnotes(&env), nil } func (g *htmlGenerator) BlocksSxnAST(bs *ast.BlockSlice) (content, endnotes *sx.Pair, _ error) { if bs == nil || len(*bs) == 0 { return nil, nil, nil } sx := g.tx.GetSz(bs) return g.BlocksSxn(sx) } func (g *htmlGenerator) szToSxHTML(node *sx.Pair) *sx.Pair { env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(node, &env) if err != nil { return nil } return sh } |
Changes to internal/web/adapter/webui/htmlmeta.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 27 | "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" | > < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsc/sz" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/usecase" ) func (wui *WebUI) writeHTMLMetaValue( key string, value meta.Value, getTextTitle getTextTitleFunc, |
︙ | ︙ | |||
48 49 50 51 52 53 54 | case meta.TypeString: return sx.MakeString(string(value)) case meta.TypeTagSet: return wui.transformTagSet(key, value.AsSlice()) case meta.TypeTimestamp: if ts, ok := value.AsTime(); ok { return sx.MakeList( | | < | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | case meta.TypeString: return sx.MakeString(string(value)) case meta.TypeTagSet: return wui.transformTagSet(key, value.AsSlice()) case meta.TypeTimestamp: if ts, ok := value.AsTime(); ok { return sx.MakeList( sxhtml.MakeSymbol("time"), sx.MakeList( sx.Cons(sxhtml.MakeSymbol("datetime"), sx.MakeString(ts.Format("2006-01-02T15:04:05"))), ), sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(ts.Format("2006-01-02 15:04:05"))), ) } return sx.Nil() case meta.TypeURL: return wui.url2html(sx.MakeString(string(value))) |
︙ | ︙ | |||
80 81 82 83 84 85 86 | switch { case found > 0: ub := wui.NewURLBuilder('h').SetZid(zid) attrs := sx.Nil() if title != "" { attrs = attrs.Cons(sx.Cons(shtml.SymAttrTitle, sx.MakeString(title))) } | | | | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | switch { case found > 0: ub := wui.NewURLBuilder('h').SetZid(zid) attrs := sx.Nil() if title != "" { attrs = attrs.Cons(sx.Cons(shtml.SymAttrTitle, sx.MakeString(title))) } attrs = attrs.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(ub.String()))) return sx.Nil().Cons(sx.MakeString(zid.String())).Cons(attrs).Cons(shtml.SymA) case found == 0: return sx.MakeList(sxhtml.MakeSymbol("s"), text) default: // case found < 0: return text } } var space = sx.MakeString(" ") |
︙ | ︙ | |||
140 141 142 143 144 145 146 | } return buildHref(ub, text) } func buildHref(ub *api.URLBuilder, text string) *sx.Pair { return sx.MakeList( shtml.SymA, | < < | < | | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | } return buildHref(ub, text) } func buildHref(ub *api.URLBuilder, text string) *sx.Pair { return sx.MakeList( shtml.SymA, sx.MakeList(sx.Cons(shtml.SymAttrHref, sx.MakeString(ub.String()))), sx.MakeString(text), ) } type getTextTitleFunc func(id.Zid) (string, int) func (wui *WebUI) makeGetTextTitle(ctx context.Context, getZettel usecase.GetZettel) getTextTitleFunc { return func(zid id.Zid) (string, int) { z, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { if errors.Is(err, &box.ErrNotAllowed{}) { return "", -1 } return "", 0 } return sz.NormalizedSpacedText(z.Meta.GetTitle()), 1 } } |
Changes to internal/web/adapter/webui/lists.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "net/http" "net/url" "slices" "strconv" "strings" "t73f.de/r/sx" | < > < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "net/http" "net/url" "slices" "strconv" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsx" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/usecase" "zettelstore.de/z/internal/web/adapter" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of zettel as HTML. |
︙ | ︙ | |||
70 71 72 73 74 75 76 | userLang := wui.getUserLang(ctx) var content, endnotes *sx.Pair numEntries := 0 if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq); bn != nil { enc := wui.getSimpleHTMLEncoder(userLang) | | | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | userLang := wui.getUserLang(ctx) var content, endnotes *sx.Pair numEntries := 0 if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq); bn != nil { enc := wui.getSimpleHTMLEncoder(userLang) content, endnotes, err = enc.BlocksSxn(zsx.MakeBlock(bn)) if err != nil { wui.reportError(ctx, w, err) return } numEntries = cnt } |
︙ | ︙ | |||
171 172 173 174 175 176 177 | } return withZettel, withoutZettel } func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair { link := sx.MakeList( shtml.SymA, | < < | < | 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | } return withZettel, withoutZettel } func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair { link := sx.MakeList( shtml.SymA, sx.MakeList(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))), sx.MakeString(name), ) if sxZtl != nil { sxZtl = sxZtl.Cons(sx.MakeString(", ")) } return sxZtl.Cons(link) } |
︙ | ︙ |
Changes to internal/web/adapter/webui/template.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 34 35 36 37 38 39 40 | "t73f.de/r/sx/sxeval" "t73f.de/r/sx/sxreader" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/collect" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/logging" | > > | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | "t73f.de/r/sx/sxeval" "t73f.de/r/sx/sxreader" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "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" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/collect" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/logging" |
︙ | ︙ | |||
135 136 137 138 139 140 141 | func (wui *WebUI) url2html(text sx.String) sx.Object { if u, errURL := url.Parse(text.GetValue()); errURL == nil { if us := u.String(); us != "" { return sx.MakeList( shtml.SymA, sx.MakeList( | < | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | func (wui *WebUI) url2html(text sx.String) sx.Object { if u, errURL := url.Parse(text.GetValue()); errURL == nil { if us := u.String(); us != "" { return sx.MakeList( shtml.SymA, sx.MakeList( sx.Cons(shtml.SymAttrHref, sx.MakeString(us)), sx.Cons(shtml.SymAttrTarget, sx.MakeString("_blank")), sx.Cons(shtml.SymAttrRel, sx.MakeString("external noreferrer")), ), text) } } |
︙ | ︙ | |||
343 344 345 346 347 348 349 | } func (wui *WebUI) bindQueryURL(rb *renderBinder, strZid, symName, directive string) { rb.bindString(symName, sx.MakeString(wui.NewURLBuilder('h').AppendQuery(strZid+" "+directive+" "+api.DirectedDirective).String())) } func (wui *WebUI) buildListsMenuSxn(ctx context.Context, lang string) *sx.Pair { | | | | | | > > | | | | | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 | } func (wui *WebUI) bindQueryURL(rb *renderBinder, strZid, symName, directive string) { rb.bindString(symName, sx.MakeString(wui.NewURLBuilder('h').AppendQuery(strZid+" "+directive+" "+api.DirectedDirective).String())) } func (wui *WebUI) buildListsMenuSxn(ctx context.Context, lang string) *sx.Pair { var zn *ast.Zettel if menuZid, err := id.Parse(wui.getConfig(ctx, nil, config.KeyListsMenuZettel)); err == nil { if zn, err = wui.evalZettel.Run(ctx, menuZid, ""); err != nil { zn = nil } } if zn == nil { ctx = box.NoEnrichContext(ctx) ztl, err := wui.box.GetZettel(ctx, id.ZidTOCListsMenu) if err != nil { return nil } zn = wui.evalZettel.RunZettel(ctx, ztl, "") } htmlgen := wui.getSimpleHTMLEncoder(lang) var lb sx.ListBuilder for ln := range collect.Order(zn.Blocks).Pairs() { lb.Add(htmlgen.szToSxHTML(ln.Head())) } return lb.List() } func (wui *WebUI) fetchNewTemplatesSxn(ctx context.Context, user *meta.Meta) *sx.Pair { if !wui.canCreate(ctx, user) { return nil } ctx = box.NoEnrichContext(ctx) menu, err := wui.box.GetZettel(ctx, id.ZidTOCNewTemplate) if err != nil { return nil } var lb sx.ListBuilder zn := parser.ParseZettel(ctx, menu, "", wui.rtConfig) for ln := range collect.Order(zn.Blocks).Pairs() { _, ref, _ := zsx.GetLink(ln.Head()) sym, val := zsx.GetReference(ref) if !sz.SymRefStateZettel.IsEqualSymbol(sym) { continue } zid, err2 := id.Parse(val) if err2 != nil { continue } z, err2 := wui.box.GetZettel(ctx, zid) if err2 != nil { continue } if !wui.policy.CanRead(user, z.Meta) { continue } text := sx.MakeString(sz.NormalizedSpacedText(z.Meta.GetTitle())) link := sx.MakeString(wui.NewURLBuilder('c').SetZid(zid). AppendKVQuery(queryKeyAction, valueActionNew).String()) lb.Add(sx.Cons(text, link)) } return lb.List() } func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair { if footerZid, err := id.Parse(wui.getConfig(ctx, nil, config.KeyFooterZettel)); err == nil { if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil { htmlEnc := wui.getSimpleHTMLEncoder(wui.getConfig(ctx, zn.InhMeta, meta.KeyLang)).SetUnique("footer-") if content, endnotes, err3 := htmlEnc.BlocksSxnAST(&zn.BlocksAST); err3 == nil { if content != nil && endnotes != nil { content.LastPair().SetCdr(sx.Cons(endnotes, nil)) } return content } } } |
︙ | ︙ | |||
480 481 482 483 484 485 486 | if err != nil { return err } wui.logger.Debug("render", "page", pageObj) gen := sxhtml.NewGenerator().SetNewline() var sb bytes.Buffer | | < | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 | if err != nil { return err } wui.logger.Debug("render", "page", pageObj) gen := sxhtml.NewGenerator().SetNewline() var sb bytes.Buffer if err = gen.WriteHTML(&sb, pageObj); err != nil { return err } wui.prepareAndWriteHeader(w, code) if _, err = w.Write(sb.Bytes()); err != nil { wui.logger.Error("Unable to write HTML via template", "err", err) } return nil // No error reporting, since we do not know what happended during write to client. |
︙ | ︙ |
Changes to internal/web/adapter/webui/webui.go.
︙ | ︙ | |||
189 190 191 192 193 194 195 | return wui.policy.CanRefresh(user) } func (wui *WebUI) getSimpleHTMLEncoder(lang string) *htmlGenerator { return wui.createGenerator(wui, lang) } | < < < | | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | return wui.policy.CanRefresh(user) } func (wui *WebUI) getSimpleHTMLEncoder(lang string) *htmlGenerator { return wui.createGenerator(wui, lang) } // NewURLBuilder creates a new URL builder object with the given key. func (wui *WebUI) NewURLBuilder(key byte) *api.URLBuilder { return wui.ab.NewURLBuilder(key) } func (wui *WebUI) clearToken(ctx context.Context, w http.ResponseWriter) context.Context { return wui.ab.ClearToken(ctx, w) } func (wui *WebUI) setToken(w http.ResponseWriter, token []byte) { wui.ab.SetToken(w, token, wui.tokenLifetime) } func (wui *WebUI) prepareAndWriteHeader(w http.ResponseWriter, statusCode int) { h := adapter.PrepareHeader(w, "text/html; charset=utf-8") h.Set("Content-Security-Policy", "default-src 'self'; img-src * data:; style-src 'self' 'unsafe-inline'") h.Set("Permissions-Policy", "payment=(), interest-cohort=()") h.Set("Referrer-Policy", "same-origin") h.Set("X-Content-Type-Options", "nosniff") if !wui.debug { h.Set("X-Frame-Options", "sameorigin") } w.WriteHeader(statusCode) } |
Changes to internal/web/server/http.go.
︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | ) type webServer struct { log *slog.Logger baseURL string httpServer httpServer router httpRouter persistentCookie bool secureCookie bool } // ConfigData contains the data needed to configure a server. type ConfigData struct { Log *slog.Logger | > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | ) type webServer struct { log *slog.Logger baseURL string httpServer httpServer router httpRouter cop *http.CrossOriginProtection persistentCookie bool secureCookie bool } // ConfigData contains the data needed to configure a server. type ConfigData struct { Log *slog.Logger |
︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | } // New creates a new web server. func New(sd ConfigData) Server { srv := webServer{ log: sd.Log, baseURL: sd.BaseURL, persistentCookie: sd.PersistentCookie, secureCookie: sd.SecureCookie, } rd := routerData{ log: sd.Log, urlPrefix: sd.URLPrefix, maxRequestSize: sd.MaxRequestSize, auth: sd.Auth, loopbackIdent: sd.LoopbackIdent, loopbackZid: sd.LoopbackZid, profiling: sd.Profiling, } srv.router.initializeRouter(rd) mwReqID := reqid.Config{WithContext: true} mwLogReq := logging.ReqConfig{ Logger: sd.Log, Level: slog.LevelDebug, | > | | > > > | > > > < < < | | | | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | } // New creates a new web server. func New(sd ConfigData) Server { srv := webServer{ log: sd.Log, baseURL: sd.BaseURL, cop: http.NewCrossOriginProtection(), persistentCookie: sd.PersistentCookie, secureCookie: sd.SecureCookie, } rd := routerData{ log: sd.Log, urlPrefix: sd.URLPrefix, maxRequestSize: sd.MaxRequestSize, auth: sd.Auth, loopbackIdent: sd.LoopbackIdent, loopbackZid: sd.LoopbackZid, profiling: sd.Profiling, } srv.router.initializeRouter(rd) mwReqID := reqid.Config{WithContext: true} mwLogReq := logging.ReqConfig{ Logger: sd.Log, Level: slog.LevelDebug, Message: "ServeHTTP", WithRequestID: true, WithRemote: true, WithHeaders: true} mwLogResp := logging.RespConfig{Logger: sd.Log, Level: slog.LevelDebug, Message: "/ServeHTTP", WithRequestID: true} mw := middleware.NewChain(mwReqID.Build(), mwLogReq.Build(), mwLogResp.Build()) srv.httpServer.initializeHTTPServer(sd.ListenAddr, middleware.Apply(mw, &srv.router)) return &srv } func (srv *webServer) Handle(pattern string, handler http.Handler) { srv.router.Handle(pattern, handler) } func (srv *webServer) AddListRoute(isAPI bool, key byte, method Method, handler http.Handler) { if !isAPI { handler = srv.cop.Handler(handler) } srv.router.addListRoute(key, method, handler) } func (srv *webServer) AddZettelRoute(isAPI bool, key byte, method Method, handler http.Handler) { if !isAPI { handler = srv.cop.Handler(handler) } srv.router.addZettelRoute(key, method, handler) } func (srv *webServer) SetUserRetriever(ur UserRetriever) { srv.router.ur = ur } func (srv *webServer) NewURLBuilder(key byte) *api.URLBuilder { return api.NewURLBuilder(srv.router.urlPrefix, key) } func (srv *webServer) NewURLBuilderAbs(key byte) *api.URLBuilder { return api.NewURLBuilder(srv.baseURL, key) } const sessionName = "zsession" func (srv *webServer) SetToken(w http.ResponseWriter, token []byte, d time.Duration) { cookie := http.Cookie{ Name: sessionName, Value: string(token), Path: srv.router.urlPrefix, Secure: srv.secureCookie, HttpOnly: true, SameSite: http.SameSiteLaxMode, } if srv.persistentCookie && d > 0 { cookie.Expires = time.Now().Add(d).Add(30 * time.Second).UTC() } srv.log.Debug("SetToken", "token", cookie.Value) if v := cookie.String(); v != "" { w.Header().Add("Set-Cookie", v) w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`) w.Header().Add("Vary", "Cookie") } } |
︙ | ︙ |
Changes to internal/web/server/server.go.
︙ | ︙ | |||
41 42 43 44 45 46 47 | MethodDelete methodLAST // must always be the last one ) // Router allows to state routes for various URL paths. type Router interface { Handle(pattern string, handler http.Handler) | | | < | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | MethodDelete methodLAST // must always be the last one ) // Router allows to state routes for various URL paths. type Router interface { Handle(pattern string, handler http.Handler) AddListRoute(isAPI bool, key byte, method Method, handler http.Handler) AddZettelRoute(isAPI bool, key byte, method Method, handler http.Handler) SetUserRetriever(ur UserRetriever) } // Builder allows to build new URLs for the web service. type Builder interface { NewURLBuilder(key byte) *api.URLBuilder NewURLBuilderAbs(key byte) *api.URLBuilder } // Auth is the authencation interface. type Auth interface { // SetToken sends the token to the client. |
︙ | ︙ |
Changes to tests/markdown_test.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 | "bytes" "encoding/json" "fmt" "os" "strings" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "zettelstore.de/z/internal/ast" | > < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "bytes" "encoding/json" "fmt" "os" "strings" "testing" "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/encoder" "zettelstore.de/z/internal/parser" ) type markdownTestCase struct { Markdown string `json:"markdown"` HTML string `json:"html"` |
︙ | ︙ | |||
63 64 65 66 67 68 69 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { | | | > | > | | | > > > > > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | > > > > > | | < | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { node, ast := createMDBlockSlice(tc.Markdown) testAllEncodings(t, tc, node) testAllEncodingsAST(t, tc, &ast) testZmkEncoding(t, tc, node) testZmkEncodingAST(t, tc, &ast) } } func createMDBlockSlice(markdown string) (*sx.Pair, ast.BlockSlice) { return parser.Parse(input.NewInput([]byte(markdown)), nil, meta.ValueSyntaxMarkdown) } func testAllEncodings(t *testing.T, tc markdownTestCase, node *sx.Pair) { var sb strings.Builder testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(*testing.T) { _ = encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}).WriteSz(&sb, node) sb.Reset() }) } } func testAllEncodingsAST(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var sb strings.Builder testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(*testing.T) { _ = encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}).WriteBlocks(&sb, ast) sb.Reset() }) } } func testZmkEncoding(t *testing.T, tc markdownTestCase, node *sx.Pair) { zmkEncoder := encoder.Create(api.EncoderZmk, nil) var buf bytes.Buffer testID := tc.Example*100 + 1 t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) { buf.Reset() _ = zmkEncoder.WriteSz(&buf, node) // gotFirst := buf.String() testID = tc.Example*100 + 2 secondNode, _ := parser.Parse(input.NewInput(buf.Bytes()), nil, meta.ValueSyntaxZmk) buf.Reset() _ = zmkEncoder.WriteSz(&buf, secondNode) gotSecond := buf.String() // if gotFirst != gotSecond { // st.Errorf("\nCMD: %q\n1st: %q\n2nd: %q", tc.Markdown, gotFirst, gotSecond) // } testID = tc.Example*100 + 3 thirdNode, _ := parser.Parse(input.NewInput(buf.Bytes()), nil, meta.ValueSyntaxZmk) buf.Reset() _ = zmkEncoder.WriteSz(&buf, thirdNode) gotThird := buf.String() if gotSecond != gotThird { st.Errorf("\n1st: %q\n2nd: %q", gotSecond, gotThird) } }) } func testZmkEncodingAST(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { zmkEncoder := encoder.Create(api.EncoderZmk, nil) var buf bytes.Buffer testID := tc.Example*100 + 1 t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) { buf.Reset() _ = zmkEncoder.WriteBlocks(&buf, ast) // gotFirst := buf.String() testID = tc.Example*100 + 2 _, secondAst := parser.Parse(input.NewInput(buf.Bytes()), nil, meta.ValueSyntaxZmk) buf.Reset() _ = zmkEncoder.WriteBlocks(&buf, &secondAst) gotSecond := buf.String() // if gotFirst != gotSecond { // st.Errorf("\nCMD: %q\n1st: %q\n2nd: %q", tc.Markdown, gotFirst, gotSecond) // } testID = tc.Example*100 + 3 _, thirdAst := parser.Parse(input.NewInput(buf.Bytes()), nil, meta.ValueSyntaxZmk) buf.Reset() _ = zmkEncoder.WriteBlocks(&buf, &thirdAst) gotThird := buf.String() if gotSecond != gotThird { st.Errorf("\n1st: %q\n2nd: %q", gotSecond, gotThird) } }) } func TestAdditionalMarkdown(t *testing.T) { testcases := []struct { md string exp string }{ {`abc<br>def`, "abc``<br>``{=\"html\"}def"}, } zmkEncoder := encoder.Create(api.EncoderZmk, nil) var sb strings.Builder for i, tc := range testcases { node, ast := createMDBlockSlice(tc.md) sb.Reset() _ = zmkEncoder.WriteSz(&sb, node) if got := sb.String(); got != tc.exp { t.Errorf("%d: %q -> %q, but got %q", i, tc.md, tc.exp, got) } sb.Reset() _ = zmkEncoder.WriteBlocks(&sb, &ast) if got := sb.String(); got != tc.exp { t.Errorf("%d: %q -> %q, but got %q", i, tc.md, tc.exp, got) } } } |
Changes to tests/naughtystrings_test.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "os" "path/filepath" "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "os" "path/filepath" "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "zettelstore.de/z/internal/encoder" "zettelstore.de/z/internal/parser" _ "zettelstore.de/z/cmd" ) // Test all parser / encoder with a list of "naughty strings", i.e. unusual strings |
︙ | ︙ | |||
81 82 83 84 85 86 87 | } encs := getAllEncoder() if len(encs) == 0 { t.Fatal("no encoder found") } for _, s := range blns { for _, pinfo := range pinfos { | | > > > | < | 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | } encs := getAllEncoder() if len(encs) == 0 { t.Fatal("no encoder found") } for _, s := range blns { for _, pinfo := range pinfos { node, bs := parser.Parse(input.NewInput([]byte(s)), &meta.Meta{}, pinfo.Name) for _, enc := range encs { if err = enc.WriteSz(io.Discard, node); err != nil { t.Error(err) } if err = enc.WriteBlocks(io.Discard, &bs); err != nil { t.Error(err) } } } } } |
Changes to tests/regression_test.go.
︙ | ︙ | |||
121 122 123 124 125 126 127 | u, err := url.Parse(p.Location()) if err != nil { panic("Unable to parse URL '" + p.Location() + "': " + err.Error()) } return u.Path[len(root):] } | | | | 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | u, err := url.Parse(p.Location()) if err != nil { panic("Unable to parse URL '" + p.Location() + "': " + err.Error()) } return u.Path[len(root):] } func checkMetaFile(t *testing.T, resultName string, zn *ast.Zettel, enc api.EncodingEnum) { t.Helper() if enc := encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}); enc != nil { var sf strings.Builder _ = enc.WriteMeta(&sf, zn.Meta) checkFileContent(t, resultName, sf.String()) return } panic(fmt.Sprintf("Unknown writer encoding %q", enc)) } func checkMetaBox(t *testing.T, p box.ManagedBox, wd, boxName string) { |
︙ | ︙ |
Changes to www/changes.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Change Log</title> <a id="0_23"></a> <h2>Changes for Version 0.23.0 (pending)</h2> <a id="0_22"></a> <h2>Changes for Version 0.22.0 (2025-07-07)</h2> * Sx builtin <code>(bind-lookup ...)</code> is replaced with <code>(resolve-symbol ...)</code>. If you maintain your own Sx code to customize Zettelstore behaviour, you must update your code; otherwise it will break. If your code use <code>(ROLE-DEFAULT-action ...)</code> | > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <title>Change Log</title> <a id="0_23"></a> <h2>Changes for Version 0.23.0 (pending)</h2> * SZ encoding of lists (ordered, unordered, and quotations) has been simplified by allowing only block elements. Previously, inline elements were also permitted to signal a compact list. However, this behavior is only relevant for HTML generation. Therefore, the detection of compact lists is now performed exclusively during HTML generation. (breaking) * SZ encoding of BLOBs has be changed from <code>(BLOB attrs inlines syntax data)</code> to <code>(BLOG attrs syntax data inline ...)</code>. (breaking) <a id="0_22"></a> <h2>Changes for Version 0.22.0 (2025-07-07)</h2> * Sx builtin <code>(bind-lookup ...)</code> is replaced with <code>(resolve-symbol ...)</code>. If you maintain your own Sx code to customize Zettelstore behaviour, you must update your code; otherwise it will break. If your code use <code>(ROLE-DEFAULT-action ...)</code> |
︙ | ︙ |