","lang":"en","license":"CC BY-SA 4.0"},"content":"The endpoint to work with a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000}}.\n\nFor example, ...
-```
-
-Pretty-printed, this results in:
-```
-{
- "id": "00001012053300",
- "meta": {
- "back": "00001012000000 00001012053200 00001012054400",
- "backward": "00001012000000 00001012053200 00001012054400 00001012920000",
- "box-number": "1",
- "forward": "00001010040100 00001012050200 00001012920000 00001012920800",
- "modified": "20210726190012",
- "published": "20210726190012",
- "role": "manual",
- "syntax": "zmk",
- "tags": "#api #manual #zettelstore",
- "title": "API: Retrieve metadata and content of an existing zettel"
- },
- "encoding": "",
- "content": "The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' (...)
- "rights": 62
-}
-```
-
-The following keys of the JSON object are used:
-; ''"id"''
-: The zettel identifier of the zettel you requested.
-; ''"meta"''
-: References an embedded JSON object with only string values.
- The name/value pairs of this objects are interpreted as the metadata of the new zettel.
- Please consider the [[list of supported metadata keys|00001006020000]] (and their value types).
-; ''"encoding"''
-: States how the content is encoded.
- Currently, only two values are allowed: the empty string (''""'') that specifies an empty encoding, and the string ''"base64"'' that specifies the [[standard Base64 encoding|https://www.rfc-editor.org/rfc/rfc4648.txt]].
- Other values will result in a HTTP response status code ''400''.
-; ''"content"''
-: Is a string value that contains the content of the zettel to be created.
- Typically, text content is not encoded, and binary content is encoded via Base64.
-; ''"rights"''
-: An integer number that describes the [[access rights|00001012921200]] for the zettel.
-
-=== Plain zettel
-[!plain]Additionally, you can retrieve the plain zettel, without using JSON.
-Just change the [[endpoint|00001012920000]] to ''/z/{ID}''
+...
+````
+
Optionally, you may provide which parts of the zettel you are requesting.
In this case, add an additional query parameter ''part=PART''.
Valid values for [[''PART''|00001012920800]] are ""zettel"", ""[[meta|00001012053400]]"", and ""content"" (the default value).
-````sh
-# curl 'http://127.0.0.1:23123/z/00001012053300'
-The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
-
-For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
-If successful, the output is a JSON object:
-```sh
-...
-````
````sh
# curl 'http://127.0.0.1:23123/z/00001012053300?part=zettel'
title: API: Retrieve metadata and content of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
+The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint
...
````
+=== Data output
+
+Alternatively, you may retrieve the zettel as a parseable object / a [[symbolic expression|00001012930500]] by providing the query parameter ''enc=data'':
+
+```sh
+# curl 'http://127.0.0.1:23123/z/00001012053300?enc=data&part=zettel'
+(zettel (meta (back "00001006000000 00001012000000 00001012053200 00001012054400") (backward "00001006000000 00001012000000 00001012053200 00001012054400 00001012920000") (box-number "1") (created "20211004093206") (forward "00001006020000 00001006050000 00001010040100 00001012050200 00001012053400 00001012920000 00001012920800 00001012921200 00001012930500") (modified "20230703174152") (published "20230703174152") (role "manual") (syntax "zmk") (tags "#api #manual #zettelstore") (title "API: Retrieve metadata and content of an existing zettel")) (rights 62) (encoding "") (content "The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].\n\nFor example, ...
+```
+
+If you print the result a little bit nicer, you will see its structure:
+```
+(zettel (meta (back "00001006000000 00001012000000 00001012053200 00001012054400")
+ (backward "00001006000000 00001012000000 00001012053200 00001012054400 00001012920000")
+ (box-number "1")
+ (created "20211004093206")
+ (forward "00001006020000 00001006050000 00001010040100 00001012050200 00001012053400 00001012920000 00001012920800 00001012921200 00001012930500")
+ (modified "20230703174152")
+ (published "20230703174152")
+ (role "manual")
+ (syntax "zmk")
+ (tags "#api #manual #zettelstore")
+ (title "API: Retrieve metadata and content of an existing zettel"))
+ (rights 62)
+ (encoding "")
+ (content "The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].\n\nFor example, ...
+```
+
+* The result is a list, starting with the symbol ''zettel''.
+* Then, some key/value pairs are following, also nested.
+* Nested in ''meta'' are the metadata, each as a key/value pair.
+* ''rights'' specifies the [[access rights|00001012921200]] the user has for this zettel.
+* ''"encoding"'' states how the content is encoded.
+ Currently, only two values are allowed: the empty string (''""'') that specifies an empty encoding, and the string ''"base64"'' that specifies the [[standard Base64 encoding|https://www.rfc-editor.org/rfc/rfc4648.txt]].
+* The zettel contents is stored as a value of the key ''content''.
+ Typically, text content is not encoded, and binary content is encoded via Base64.
+
=== HTTP Status codes
; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object / plain zettel data.
+: Retrieval was successful, the body contains an appropriate data value.
; ''204''
: Request was valid, but there is no data to be returned.
Most likely, you specified the query parameter ''part=content'', but the zettel does not contain any content.
; ''400''
: Request was not valid.
Index: docs/manual/00001012053400.zettel
==================================================================
--- docs/manual/00001012053400.zettel
+++ docs/manual/00001012053400.zettel
@@ -2,65 +2,61 @@
title: API: Retrieve metadata of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210726174524
-modified: 20220917175233
-
-The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/m/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
-
-For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
-If successful, the output is a JSON object:
-```sh
-# curl http://127.0.0.1:23123/m/00001012053400
-{"meta":{"back":"00001012000000 00001012053300","backward":"00001012000000 00001012053300 00001012920000","box-number":"1","forward":"00001010040100 00001012050200 00001012920000 00001012920800","modified":"20211004111240","published":"20211004111240","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Retrieve metadata of an existing zettel"},"rights":62}
-```
-
-Pretty-printed, this results in:
-```
-{
- "meta": {
- "back": "00001012000000 00001012053300",
- "backward": "00001012000000 00001012053300 00001012920000",
- "box-number": "1",
- "forward": "00001010040100 00001012050200 00001012920000 00001012920800",
- "modified": "20211004111240",
- "published": "20211004111240",
- "role": "manual",
- "syntax": "zmk",
- "tags": "#api #manual #zettelstore",
- "title": "API: Retrieve metadata of an existing zettel"
- },
- "rights": 62
-}
-```
-The following keys of the JSON object are used:
-; ''"meta"''
-: References an embedded JSON object with only string values.
- The name/value pairs of this objects are interpreted as the metadata of the new zettel.
- Please consider the [[list of supported metadata keys|00001006020000]] (and their value types).
-; ''"rights"''
-: An integer number that describes the [[access rights|00001012921200]] for the zettel.
-
-[!plain]Additionally, you can retrieve the plain metadata of a zettel, without using JSON.
-Just change the [[endpoint|00001012920000]] to ''/z/{ID}?part=meta''
+modified: 20230807170155
+
+The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]][^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
+
+To retrieve the plain metadata of a zettel, use the query parameter ''part=meta''
````sh
# curl 'http://127.0.0.1:23123/z/00001012053400?part=meta'
title: API: Retrieve metadata of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
````
+=== Data output
+
+Alternatively, you may retrieve the zettel as a parseable object / a [[symbolic expression|00001012930500]] by providing the query parameter ''enc=data'':
+
+```sh
+# curl 'http://127.0.0.1:23123/z/00001012053400?part=meta&enc=data'
+(list (meta (title "API: Retrieve metadata of an existing zettel") (role "manual") (tags "#api #manual #zettelstore") (syntax "zmk") (back "00001012000000 00001012053300") (backward "00001012000000 00001012053300") (box-number "1") (created "20210726174524") (forward "00001006020000 00001006050000 00001010040100 00001012050200 00001012920000 00001012921200") (modified "20230703174515") (published "20230703174515")) (rights 62))
+```
+
+Pretty-printed, this results in:
+```
+(list (meta (title "API: Retrieve metadata of an existing zettel")
+ (role "manual")
+ (tags "#api #manual #zettelstore")
+ (syntax "zmk")
+ (back "00001012000000 00001012053300")
+ (backward "00001012000000 00001012053300")
+ (box-number "1")
+ (created "20210726174524")
+ (forward "00001006020000 00001006050000 00001010040100 00001012050200 00001012920000 00001012921200")
+ (modified "20230703174515")
+ (published "20230703174515"))
+ (rights 62))
+```
+
+* The result is a list, starting with the symbol ''list''.
+* Then, some key/value pairs are following, also nested.
+* Nested in ''meta'' are the metadata, each as a key/value pair.
+* ''rights'' specifies the [[access rights|00001012921200]] the user has for this zettel.
+
=== HTTP Status codes
; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
+: Retrieval was successful, the body contains an appropriate data value.
; ''400''
: Request was not valid.
There are several reasons for this.
Maybe the zettel identifier did not consist of exactly 14 digits.
; ''403''
: You are not allowed to retrieve data of the given zettel.
; ''404''
: Zettel not found.
You probably used a zettel identifier that is not used in the Zettelstore.
Index: docs/manual/00001012053500.zettel
==================================================================
--- docs/manual/00001012053500.zettel
+++ docs/manual/00001012053500.zettel
@@ -2,70 +2,59 @@
title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210726174524
-modified: 20220908162843
-
-The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/v/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
-
-For example, to retrieve some evaluated data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
-If successful, the output is a JSON object:
-```sh
-# curl http://127.0.0.1:23123/v/00001012053500
-{"meta":{"title":[{"t":"Text","s":"API:"},{"t":"Space"},{"t":"Text","s":"Retrieve"},{"t":"Space"},{"t":"Text","s":"evaluated"},{"t":"Space"},{"t":"Text","s":"metadata"},{"t":"Space"},{"t":"Text","s":"and"},{"t":"Space"},{"t":"Text","s":"content"},{"t":"Space"},{"t":"Text","s":"of"},{"t":"Space"},{"t":"Text","s":"an"},{"t":"Space"},{"t":"Text","s":"existing"},{"t":"Space"},{"t":"Text","s":"zettel"},{"t":"Space"},{"t":"Text","s":"in"},{"t":"Space"}, ...
+modified: 20230807170112
+
+The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
+
+For example, to retrieve some evaluated data about this zettel you are currently viewing in [[Sz encoding|00001012920516]], just send a HTTP GET request to the endpoint ''/z/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''enc=sz''.
+If successful, the output is a symbolic expression value:
+```sh
+# curl 'http://127.0.0.1:23123/z/00001012053500?enc=sz'
+((PARA (TEXT "The") (SPACE) (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (SPACE) (TEXT "to") (SPACE) (TEXT "work") (SPACE) (TEXT "with") (SPACE) (TEXT "evaluated") (SPACE) (TEXT "metadata") (SPACE) (TEXT "and") (SPACE) (TEXT "content") (SPACE) (TEXT "of") (SPACE) (TEXT "a") (SPACE) (TEXT "specific") (SPACE) (TEXT "zettel") (SPACE) (TEXT "is") (SPACE) (LITERAL-INPUT () "/z/{ID}") (TEXT ",") (SPACE) (TEXT "where") (SPACE) (LITERAL-INPUT () "{ID}") ...
```
-To select another encoding, you can provide a query parameter ''enc=ENCODING''.
-The default value for [[''ENCODING''|00001012920500]] is ""[[zjson|00001012920503]]"".
-Others are ""[[html|00001012920510]]"", ""[[text|00001012920519]]"", and some more.
+To select another encoding, you must provide the query parameter ''enc=ENCODING''.
+Others are ""[[html|00001012920510]]"", ""[[text|00001012920519]]"", and some [[more|00001012920500]].
+In addition, you may provide a query parameter ''part=PART'' to select the relevant [[part|00001012920800]] of a zettel.
```sh
-# curl 'http://127.0.0.1:23123/v/00001012053500?enc=html'
-
-
+# curl 'http://127.0.0.1:23123/z/00001012053500?enc=html&part=zettel'
+
API: Retrieve evaluated metadata and content of an existing zettel in various encodings
+
-
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
-The endpoint to work with evaluated metadata and content of a specific zettel is /v/{ID}, where {ID} is a placeholder for the zettel identifier.
+API: Retrieve evaluated metadata and content of an existing zettel in various encodings
+The endpoint to work with evaluated metadata and content of a specific zettel is /z/{ID},
...
```
-You also can use the query parameter ''part=PART'' to specify which [[parts|00001012920800]] of a zettel must be encoded.
-In this case, its default value is ''content''.
-```sh
-# curl 'http://127.0.0.1:23123/v/00001012053500?enc=html&part=meta'
-
-
-
-
-
-
-
-
-
-
-
-```
-
=== HTTP Status codes
; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
+: Retrieval was successful, the body contains an appropriate data value.
; ''400''
: Request was not valid.
There are several reasons for this.
Maybe the zettel identifier did not consist of exactly 14 digits or ''enc'' / ''part'' contained illegal values.
; ''403''
: You are not allowed to retrieve data of the given zettel.
; ''404''
: Zettel not found.
You probably used a zettel identifier that is not used in the Zettelstore.
Index: docs/manual/00001012053600.zettel
==================================================================
--- docs/manual/00001012053600.zettel
+++ docs/manual/00001012053600.zettel
@@ -2,34 +2,34 @@
title: API: Retrieve parsed metadata and content of an existing zettel in various encodings
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20220908163514
+modified: 20230807170019
-The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/p/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
+The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
A __parsed__ zettel is basically an [[unevaluated|00001012053500]] zettel: the zettel is read and analyzed, but its content is not __evaluated__.
By using this endpoint, you are able to retrieve the structure of a zettel before it is evaluated.
-For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
-If successful, the output is a JSON object:
+For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''parseonly'' (and other appropriate query parameter).
+For example:
```sh
-# curl http://127.0.0.1:23123/p/00001012053600
-[{"t":"Para","i":[{"t":"Text","s":"The"},{"t":"Space"},{"t":"Link","q":"zettel","s":"00001012920000","i":[{"t":"Text","s":"endpoint"}]},{"t":"Space"},{"t":"Text","s":"to"},{"t":"Space"},{"t":"Text","s":"work"},{"t":"Space"},{"t":"Text","s":"with"},{"t":"Space"}, ...
+# curl 'http://127.0.0.1:23123/z/00001012053600?enc=sz&parseonly'
+((PARA (TEXT "The") (SPACE) (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (SPACE) (TEXT "to") (SPACE) (TEXT "work") (SPACE) (TEXT "with") (SPACE) ...
```
Similar to [[retrieving an encoded zettel|00001012053500]], you can specify an [[encoding|00001012920500]] and state which [[part|00001012920800]] of a zettel you are interested in.
The same default values applies to this endpoint.
=== HTTP Status codes
; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
+: Retrieval was successful, the body contains an appropriate data value.
; ''400''
: Request was not valid.
There are several reasons for this.
Maybe the zettel identifier did not consist of exactly 14 digits or ''enc'' / ''part'' contained illegal values.
; ''403''
: You are not allowed to retrieve data of the given zettel.
; ''404''
: Zettel not found.
You probably used a zettel identifier that is not used in the Zettelstore.
DELETED docs/manual/00001012053800.zettel
Index: docs/manual/00001012053800.zettel
==================================================================
--- docs/manual/00001012053800.zettel
+++ docs/manual/00001012053800.zettel
@@ -1,79 +0,0 @@
-id: 00001012053800
-title: API: Retrieve context of an existing zettel
-role: manual
-tags: #api #manual #zettelstore
-syntax: zmk
-modified: 20220202112607
-
-The context of an origin zettel consists of those zettel that are somehow connected to the origin zettel.
-Direct connections of an origin zettel to other zettel are visible via [[metadata values|00001006020000]], such as ''backward'', ''forward'' or other values with type [[identifier|00001006032000]] or [[set of identifier|00001006032500]].
-
-The context is defined by a __direction__, a __depth__, and a __limit__:
-* Direction: connections are directed.
- For example, the metadata value of ''backward'' lists all zettel that link to the current zettel, while ''forward'' list all zettel to which the current zettel links to.
- When you are only interested in one direction, set the parameter ''dir'' either to the value ""backward"" or ""forward"".
- All other values, including a missing value, is interpreted as ""both"".
-* Depth: a direct connection has depth 1, an indirect connection is the length of the shortest path between two zettel.
- You should limit the depth by using the parameter ''depth''.
- Its default value is ""5"".
- A value of ""0"" does disable any depth check.
-* Limit: to set an upper bound for the returned context, you should use the parameter ''limit''.
- Its default value is ""200"".
- A value of ""0"" disables does not limit the number of elements returned.
-
-The search for the context of a zettel stops at the [[home zettel|00001004020000#home-zettel]].
-This zettel is connected to all other zettel.
-If it is included, the context would become too big and therefore unusable.
-
-To retrieve the context of an existing zettel, use the [[endpoint|00001012920000]] ''/x/{ID}''[^Mnemonic: conte**X**t].
-
-````
-# curl 'http://127.0.0.1:23123/x/00001012053800?limit=3&dir=forward&depth=2'
-{"id": "00001012053800","meta": {...},"rights":62,"list":[{"id": "00001012921000","meta": {...},"rights":62},{"id": "00001012920800","meta": {...},"rights":62},{"id": "00010000000000","meta": {...},"rights":62}]}
-````
-Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
-````json
-{
- "id": "00001012053800",
- "meta": {...},
- "rights": 62,
- "list": [
- {
- "id": "00001012921000",
- "meta": {...},
- "rights":62
- },
- {
- "id": "00001012920800",
- "meta": {...},
- "rights":62
- },
- {
- "id": "00010000000000",
- "meta": {...},
- "rights":62
- }
- ]
-}
-````
-=== Keys
-The following top-level JSON keys are returned:
-; ''id''
-: The [[zettel identifier|00001006050000]] for which the context was requested.
-; ''meta'':
-: The metadata of the zettel, encoded as a JSON object.
-; ''rights''
-: An integer number that describes the [[access rights|00001012921200]] for the given zettel.
-; ''list''
-: A list of JSON objects with keys ''id'', ''meta'', and ''rights'' that contains the zettel of the context.
-
-=== HTTP Status codes
-; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
-; ''400''
-: Request was not valid.
-; ''403''
-: You are not allowed to retrieve data of the given zettel.
-; ''404''
-: Zettel not found.
- You probably used a zettel identifier that is not used in the Zettelstore.
DELETED docs/manual/00001012053900.zettel
Index: docs/manual/00001012053900.zettel
==================================================================
--- docs/manual/00001012053900.zettel
+++ docs/manual/00001012053900.zettel
@@ -1,80 +0,0 @@
-id: 00001012053900
-title: API: Retrieve unlinked references to an existing zettel
-role: manual
-tags: #api #manual #zettelstore
-syntax: zmk
-created: 20211119133357
-modified: 20220913152019
-
-The value of a personal Zettelstore is determined in part by explicit connections between related zettel.
-If the number of zettel grow, some of these connections are missing.
-There are various reasons for this.
-Maybe, you forgot that a zettel exists.
-Or you add a zettel later, but forgot that previous zettel already mention its title.
-
-__Unlinked references__ are phrases in a zettel that mention the title of another, currently unlinked zettel.
-
-To retrieve unlinked references to an existing zettel, use the [[endpoint|00001012920000]] ''/u/{ID}''.
-
-````
-# curl 'http://127.0.0.1:23123/u/00001007000000'
-{"id": "00001007000000","meta": {...},"rights":62,"list": [{"id": "00001012070500","meta": {...},"rights":62},...{"id": "00001006020000","meta": {...},"rights":62}]}
-````
-Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
-````json
-{
- "id": "00001007000000",
- "meta": {...},
- "rights": 62,
- "list": [
- {
- "id": "00001012070500",
- "meta": {...},
- "rights": 62
- },
- ...
- {
- "id": "00001006020000",
- "meta": {...},
- "rights": 62
- }
- ]
-}
-````
-
-This call searches within all zettel whether the title of the specified zettel occurs there.
-The other zettel must not link to the specified zettel.
-The title must not occur within a link (e.g. to another zettel), in a [[heading|00001007030300]], in a [[citation|00001007040340]], and must have a uniform formatting.
-The match must be exact, but is case-insensitive.
-
-If the title of the specified zettel contains some extra character that probably reduce the number of found unlinked references,
-you can specify the title phase to be searched for as a query parameter ''phrase'':
-
-````
-# curl 'http://127.0.0.1:23123/u/00001007000000?phrase=markdown'
-{"id": "00001007000000","meta": {...},"list": [{"id": "00001008010000","meta": {...},"rights":62},{"id": "00001004020000","meta": {...},"rights":62}]}
-````
-
-%%TODO: In addition, you are allowed to limit the search by a [[query expression|00001012051840]], which may search for zettel content.
-
-=== Keys
-The following top-level JSON keys are returned:
-; ''id''
-: The [[zettel identifier|00001006050000]] for which the unlinked references were requested.
-; ''meta'':
-: The metadata of the zettel, encoded as a JSON object.
-; ''rights''
-: An integer number that describes the [[access rights|00001012921200]] for the given zettel.
-; ''list''
-: A list of JSON objects with keys ''id'', ''meta'', and ''rights'' that describe zettel with unlinked references.
-
-=== HTTP Status codes
-; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
-; ''400''
-: Request was not valid.
-; ''403''
-: You are not allowed to retrieve data of the given zettel.
-; ''404''
-: Zettel not found.
- You probably used a zettel identifier that is not used in the Zettelstore.
DELETED docs/manual/00001012054000.zettel
Index: docs/manual/00001012054000.zettel
==================================================================
--- docs/manual/00001012054000.zettel
+++ docs/manual/00001012054000.zettel
@@ -1,84 +0,0 @@
-id: 00001012054000
-title: API: Retrieve zettel order within an existing zettel
-role: manual
-tags: #api #manual #zettelstore
-syntax: zmk
-modified: 20220202112451
-
-Some zettel act as a ""table of contents"" for other zettel.
-The [[initial zettel|00001000000000]] of this manual is one example, the [[general API description|00001012000000]] is another.
-Every zettel with a certain internal structure can act as the ""table of contents"" for others.
-
-What is a ""table of contents""?
-Basically, it is just a list of references to other zettel.
-
-To retrieve the ""table of contents"", the software looks at first level [[list items|00001007030200]].
-If an item contains a valid reference to a zettel, this reference will be interpreted as an item in the table of contents.
-
-This applies only to first level list items (ordered or unordered list), but not to deeper levels.
-Only the first reference to a valid zettel is collected for the table of contents.
-Following references to zettel within such an list item are ignored.
-
-To retrieve the zettel order of an existing zettel, use the [[endpoint|00001012920000]] ''/o/{ID}''.
-
-````
-# curl http://127.0.0.1:23123/o/00001000000000
-{"id":"00001000000000","meta":{...},"rights":62,"list":[{"id":"00001001000000","meta":{...},"rights":62},{"id":"00001002000000","meta":{...},"rights":62},{"id":"00001003000000","meta":{...},"rights":62},{"id":"00001004000000","meta":{...},"rights":62},...,{"id":"00001014000000","meta":{...},"rights":62}]}
-````
-Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
-````json
-{
- "id": "00001000000000",
- "meta": {...},
- "rights": 62,
- "list": [
- {
- "id": "00001001000000",
- "meta": {...},
- "rights": 62
- },
- {
- "id": "00001002000000",
- "meta": {...},
- "rights": 62
- },
- {
- "id": "00001003000000",
- "meta": {...},
- "rights": 62
- },
- {
- "id": "00001004000000",
- "meta": {...},
- "rights": 62
- },
- ...
- {
- "id": "00001014000000",
- "meta": {...},
- "rights": 62
- }
- ]
-}
-````
-
-The following top-level JSON keys are returned:
-; ''id''
-: The [[zettel identifier|00001006050000]] for which the references were requested.
-; ''meta'':
-: The metadata of the zettel, encoded as a JSON object.
-; ''rights''
-: An integer number that describes the [[access rights|00001012921200]] for the given zettel.
-; ''list''
-: A list of JSON objects with keys ''id'', ''meta'', and ''rights'' that describe other zettel in the defined order.
-
-=== HTTP Status codes
-; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
-; ''400''
-: Request was not valid.
-; ''403''
-: You are not allowed to retrieve data of the given zettel.
-; ''404''
-: Zettel not found.
- You probably used a zettel identifier that is not used in the Zettelstore.
Index: docs/manual/00001012054200.zettel
==================================================================
--- docs/manual/00001012054200.zettel
+++ docs/manual/00001012054200.zettel
@@ -1,33 +1,33 @@
id: 00001012054200
title: API: Update a zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20211124180943
+created: 20210713150005
+modified: 20230807165948
Updating metadata and content of a zettel is technically quite similar to [[creating a new zettel|00001012053200]].
In both cases you must provide the data for the new or updated zettel in the body of the HTTP request.
One difference is the endpoint.
-The [[endpoint|00001012920000]] to update a zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
-You must send a HTTP PUT request to that endpoint:
-
-```
-# curl -X PUT --data '{}' http://127.0.0.1:23123/j/00001012054200
-```
-This will put some empty content and metadata to the zettel you are currently reading.
-As usual, some metadata will be calculated if it is empty.
-
-The body of the HTTP response is empty, if the request was successful.
-
-[!plain]Alternatively, you can use the [[endpoint|00001012920000]] ''/z/{ID}'' to update a zettel.
-In this case, the zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line.
-This is the same format as used by storing zettel within a [[directory box|00001006010000]].
-```
-# curl -X POST --data $'title: Updated Note\n\nUpdated content.' http://127.0.0.1:23123/z/00001012054200
-```
+The [[endpoint|00001012920000]] to update a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
+You must send a HTTP PUT request to that endpoint.
+
+The zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line.
+This is the same format as used by storing zettel within a [[directory box|00001006010000]].
+
+```
+# curl -X POST --data 'title: Updated Note\n\nUpdated content.' http://127.0.0.1:23123/z/00001012054200
+```
+
+=== Data input
+Alternatively, you may encode the zettel as a parseable object / a [[symbolic expression|00001012930500]] by providing the query parameter ''enc=data''.
+The encoding is the same as the data output encoding when you [[retrieve a zettel|00001012053300#data-output]].
+
+The encoding for [[access rights|00001012921200]] must be given, but is ignored.
+You may encode computed or property [[metadata keys|00001006020000]], but these are also ignored.
=== HTTP Status codes
; ''204''
: Update was successful, there is no body in the response.
; ''400''
Index: docs/manual/00001012054400.zettel
==================================================================
--- docs/manual/00001012054400.zettel
+++ docs/manual/00001012054400.zettel
@@ -1,33 +1,31 @@
id: 00001012054400
title: API: Rename a zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20211124181324
+created: 20210713150005
+modified: 20221219154659
Renaming a zettel is effectively just specifying a new identifier for the zettel.
Since more than one [[box|00001004011200]] might contain a zettel with the old identifier, the rename operation must success in every relevant box to be overall successful.
If the rename operation fails in one box, Zettelstore tries to rollback previous successful operations.
As a consequence, you cannot rename a zettel when its identifier is used in a read-only box.
This applies to all [[predefined zettel|00001005090000]], for example.
-The [[endpoint|00001012920000]] to rename a zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
+The [[endpoint|00001012920000]] to rename a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
You must send a HTTP MOVE request to this endpoint, and you must specify the new zettel identifier as an URL, placed under the HTTP request header key ''Destination''.
```
-# curl -X MOVE -H "Destination: 10000000000001" http://127.0.0.1:23123/j/00001000000000
+# curl -X MOVE -H "Destination: 10000000000001" http://127.0.0.1:23123/z/00001000000000
```
Only the last 14 characters of the value of ''Destination'' are taken into account and those must form an unused [[zettel identifier|00001006050000]].
If the value contains less than 14 characters that do not form an unused zettel identifier, the response will contain a HTTP status code ''400''.
All other characters, besides those 14 digits, are effectively ignored.
However, the value should form a valid URL that could be used later to [[read the content|00001012053300]] of the freshly renamed zettel.
-[!plain]Alternatively, you can also use the [[endpoint|00001012920000]] ''/z/{ID}''.
-Both endpoints behave identical.
-
=== HTTP Status codes
; ''204''
: Rename was successful, there is no body in the response.
; ''400''
: Request was not valid.
Index: docs/manual/00001012054600.zettel
==================================================================
--- docs/manual/00001012054600.zettel
+++ docs/manual/00001012054600.zettel
@@ -1,29 +1,27 @@
id: 00001012054600
title: API: Delete a zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20211124181041
+created: 20210713150005
+modified: 20221219154608
Deleting a zettel within the Zettelstore is executed on the first [[box|00001004011200]] that contains that zettel.
Zettel with the same identifier, but in subsequent boxes remain.
If the first box containing the zettel is read-only, deleting that zettel will fail, as well for a Zettelstore in [[read-only mode|00001004010000#read-only-mode]] or if [[authentication is enabled|00001010040100]] and the user has no [[access right|00001010070600]] to do so.
-The [[endpoint|00001012920000]] to delete a zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
+The [[endpoint|00001012920000]] to delete a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
You must send a HTTP DELETE request to this endpoint:
```
-# curl -X DELETE http://127.0.0.1:23123/j/00001000000000
+# curl -X DELETE http://127.0.0.1:23123/z/00001000000000
```
-[!plain]Alternatively, you can also use the [[endpoint|00001012920000]] ''/z/{ID}''.
-Both endpoints behave identical.
-
=== HTTP Status codes
; ''204''
: Delete was successful, there is no body in the response.
; ''403''
: You are not allowed to delete the given zettel.
Maybe you do not have enough access rights, or either the box or Zettelstore itself operate in read-only mode.
; ''404''
: Zettel not found.
You probably specified a zettel identifier that is not used in the Zettelstore.
Index: docs/manual/00001012070500.zettel
==================================================================
--- docs/manual/00001012070500.zettel
+++ docs/manual/00001012070500.zettel
@@ -1,27 +1,29 @@
id: 00001012070500
title: Retrieve administrative data
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20220805174216
+created: 00010101000000
+modified: 20230701160903
The [[endpoint|00001012920000]] ''/x'' allows you to retrieve some (administrative) data.
Currently, you can only request Zettelstore version data.
````
# curl 'http://127.0.0.1:23123/x'
-{"major":0,"minor":4,"patch":0,"info":"dev","hash":"cb121cc980-dirty"}
+(0 13 0 "dev" "f781dc384b-dirty")
````
-Zettelstore conforms somehow to the Standard [[Semantic Versioning|https://semver.org/]].
+* Zettelstore conforms somehow to the Standard [[Semantic Versioning|https://semver.org/]].
-The names ""major"", ""minor"", and ""patch"" are described in this standard.
+ The first three digits contain the major, minor, and patch version as described in this standard.
+* The first string contains additional information, e.g. ""dev"" for a development version, or ""preview"" for a preview version.
+* The second string contains data to identify the version from a developers perspective.
-The name ""info"" contains sometimes some additional information, e.g. ""dev"" for a development version, or ""preview"" for a preview version.
-
-The name ""hash"" contains some data to identify the version from a developers perspective.
+If any of the three digits has the value -1, its semantic value is unknown.
+Similar, the two string might be empty.
=== HTTP Status codes
; ''200''
-: Retrieval was successful, the body contains an appropriate JSON object.
+: Retrieval was successful, the body contains an appropriate object.
Index: docs/manual/00001012920000.zettel
==================================================================
--- docs/manual/00001012920000.zettel
+++ docs/manual/00001012920000.zettel
@@ -2,11 +2,11 @@
title: Endpoints used by the API
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20210126175322
-modified: 20220923101703
+modified: 20230731162343
All API endpoints conform to the pattern ''[PREFIX]LETTER[/ZETTEL-ID]'', where:
; ''PREFIX''
: is the URL prefix (default: ""/""), configured via the ''url-prefix'' [[startup configuration|00001004010000]],
; ''LETTER''
@@ -17,26 +17,16 @@
The following letters are currently in use:
|= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]] | Mnemonic
| ''a'' | POST: [[client authentication|00001012050200]] | | **A**uthenticate
| | PUT: [[renew access token|00001012050400]] |
-| ''j'' | GET: [[query zettel list|00001012051400]] (alias of ''/q'') | GET: [[retrieve zettel AS JSON|00001012053300]] | **J**SON
-| | POST: [[create new zettel|00001012053200]] | PUT: [[update a zettel|00001012054200]]
-| | | DELETE: [[delete the zettel|00001012054600]]
-| | | MOVE: [[rename the zettel|00001012054400]]
-| ''m'' | | GET: [[retrieve metadata|00001012053400]] | **M**etadata
-| ''o'' | | GET: [[list zettel order|00001012054000]] | **O**rder
-| ''p'' | | GET: [[retrieve parsed zettel|00001012053600]]| **P**arsed
-| ''q'' | GET: [[query zettel list|00001012051400]] | | **Q**uery
-| ''u'' | | GET [[unlinked references|00001012053900]] | **U**nlinked
-| ''v'' | | GET: [[retrieve evaluated zettel|00001012053500]] | E**v**aluated
-| ''x'' | GET: [[retrieve administrative data|00001012070500]] | GET: [[list zettel context|00001012053800]] | Conte**x**t
+| ''x'' | GET: [[retrieve administrative data|00001012070500]] | | E**x**ecute
| | POST: [[execute command|00001012080100]]
-| ''z'' | GET: [[list zettel|00001012051200#plain]] | GET: [[retrieve zettel|00001012053300#plain]] | **Z**ettel
-| | POST: [[create new zettel|00001012053200#plain]] | PUT: [[update a zettel|00001012054200#plain]]
-| | | DELETE: [[delete zettel|00001012054600#plain]]
-| | | MOVE: [[rename zettel|00001012054400#plain]]
+| ''z'' | GET: [[list zettel|00001012051200]]/[[query zettel|00001012051400]] | GET: [[retrieve zettel|00001012053300]] | **Z**ettel
+| | POST: [[create new zettel|00001012053200]] | PUT: [[update zettel|00001012054200]]
+| | | DELETE: [[delete zettel|00001012054600]]
+| | | MOVE: [[rename zettel|00001012054400]]
The full URL will contain either the ""http"" oder ""https"" scheme, a host name, and an optional port number.
The API examples will assume the ""http"" schema, the local host ""127.0.0.1"", the default port ""23123"", and the default empty ''PREFIX'' ""/"".
Therefore, all URLs in the API documentation will begin with ""http://127.0.0.1:23123/"".
Index: docs/manual/00001012920500.zettel
==================================================================
--- docs/manual/00001012920500.zettel
+++ docs/manual/00001012920500.zettel
@@ -1,14 +1,17 @@
id: 00001012920500
-title: Encodings available via the [[API|00001012000000]]
+title: Encodings available via the API
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
-modified: 20220423131535
+created: 20210126175322
+modified: 20230403123653
A zettel representation can be encoded in various formats for further processing.
+These will be retrieved via the [[API|00001012000000]].
* [[html|00001012920510]]
-* [[sexpr|00001012920516]]
+* [[md|00001012920513]]
+* [[shtml|00001012920525]]
+* [[sz|00001012920516]]
* [[text|00001012920519]]
-* [[zjson|00001012920503]] (default)
* [[zmk|00001012920522]]
DELETED docs/manual/00001012920503.zettel
Index: docs/manual/00001012920503.zettel
==================================================================
--- docs/manual/00001012920503.zettel
+++ docs/manual/00001012920503.zettel
@@ -1,36 +0,0 @@
-id: 00001012920503
-title: ZJSON Encoding
-role: manual
-tags: #api #manual #reference #zettelstore
-syntax: zmk
-created: 20210126175322
-modified: 20220908163450
-
-A zettel representation that allows to process the syntactic structure of a zettel.
-It is a JSON-based encoding format, but different to the structures returned by [[endpoint|00001012920000]] ''/j/{ID}''.
-
-For an example, take a look at the ZJSON encoding of this page, which is available via the ""Info"" sub-page of this zettel:
-
-* [[//v/00001012920503?enc=zjson&part=zettel]],
-* [[//v/00001012920503?enc=zjson&part=meta]],
-* [[//v/00001012920503?enc=zjson&part=content]].
-
-If transferred via HTTP, the content type will be ''application/json''.
-
-A full zettel encoding results in a JSON object with two keys: ''"meta"'' and ''"content"''.
-Both values are the same as if you have requested just the appropriate [[part|00001012920800]].
-
-=== Encoding of metadata
-Metadata encoding results in a JSON object, where each metadata key is mapped to the same JSON object name.
-The associated value is itself a JSON object with two names.
-The first name ``""`` references the [[metadata key type|00001006030000]].
-Depending on the key type, the other name denotes the value of the metadata element.
-The meaning of these names is [[well defined|00001012920582]], as well as the [[mapping of key types to used object names|00001012920584]].
-
-=== Encoding of zettel content
-The content encoding results in a JSON array of objects, where each objects represents a Zettelmarkup element.
-
-Every [!zettelmarkup|Zettelmarkup] element is encoded as a JSON object.
-These objects always contain the empty name ''""'' with a string value describing the type of Zettelmarkup element.
-Depending on the type, other one letter names denotes the details of the element.
-The meaning of these names is [[well defined|00001012920588]].
ADDED docs/manual/00001012920513.zettel
Index: docs/manual/00001012920513.zettel
==================================================================
--- docs/manual/00001012920513.zettel
+++ docs/manual/00001012920513.zettel
@@ -0,0 +1,30 @@
+id: 00001012920513
+title: Markdown Encoding
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20221107183011
+modified: 20221107185130
+
+A zettel representation that tries to recreate a [[Markdown|00001008010500]] representation of the zettel.
+Useful if you want to convert [[other markup languages|00001008000000]] to Markdown (e.g. [[Zettelmarkup|00001007000000]]).
+
+If transferred via HTTP, the content type will be ''text/markdown''.
+
+Please note that many elements of Zettelmarkup cannot be encoded in Markdown / CommonMark.
+Examples are:
+* [[Description lists|00001007030100]]
+* [[Verse blocks|00001007030700]]
+* [[Region blocks|00001007030800]]
+* [[Comment blocks|00001007030900]] (and inline comments)
+* [[Evaluation blocks|00001007031300]]
+* [[Math-mode blocks|00001007031400]]
+* [[Tables|00001007031000]]
+* Most [[text formatting|00001007040100]] elements (except emphasis and quotation)
+* Most [[literal-like formatting|00001007040200]] (except literal text / code spans)
+Some elements are restricted, e.g. [[quotation lists|00001007030200]] are only supported as a top-level element.
+
+Restricted and unsupported elements are not encoded.
+They will not appear on the encoded output.
+
+Maybe in the future, ignored elements may be shown as an HTML-like comment.
Index: docs/manual/00001012920516.zettel
==================================================================
--- docs/manual/00001012920516.zettel
+++ docs/manual/00001012920516.zettel
@@ -1,45 +1,20 @@
id: 00001012920516
-title: Sexpr Encoding
+title: Sz Encoding
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20220422181104
-modified: 20220908163427
-
-A zettel representation that is a [[s-expression|https://en.wikipedia.org/wiki/S-expression]] (also known as symbolic expression).
-
-It is an alternative to the [[ZJSON encoding|00001012920503]].
-Both encodings are (relatively) easy to parse and contain all relevant information of a zettel, metadata and content.
-
-For example, take a look at the Sexpr encoding of this page, which is available via the ""Info"" sub-page of this zettel:
-
-* [[//v/00001012920516?enc=sexpr&part=zettel]],
-* [[//v/00001012920516?enc=sexpr&part=meta]],
-* [[//v/00001012920516?enc=sexpr&part=content]].
+modified: 20230403161458
+
+A zettel representation that is a [[s-expression|00001012930000]] (also known as symbolic expression).
+
+It is (relatively) easy to parse and contain all relevant information of a zettel, metadata and content.
+For example, take a look at the Sz encoding of this page, which is available via the ""Info"" sub-page of this zettel:
+
+* [[//z/00001012920516?enc=sz&part=zettel]],
+* [[//z/00001012920516?enc=sz&part=meta]],
+* [[//z/00001012920516?enc=sz&part=content]].
+
+Some zettel describe the [[Sz encoding|00001012931000]] in a more detailed way.
If transferred via HTTP, the content type will be ''text/plain''.
-
-=== Syntax of s-expressions
-There are only two types of elements: atoms and lists.
-
-A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029).
-A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms.
-
-There are three syntactic forms for an atom: numbers, symbols and strings.
-
-A number is a non-empty sequence of digits (""0"" ... ""9"").
-The smallest number is ``0``, there are no negative numbers.
-
-A symbol is a non-empty sequence of printable characters, except left or right parenthesis.
-Unicode characters of the following categories contains printable characters in the above sense: letter (L), number (N), punctuation (P), symbol (S).
-Symbols are case-insensitive, i.e. ""''ZETTEL''"" and ""''zettel''"" denote the same symbol.
-
-A string starts with a quotation mark (""''"''"", U+0022), contains a possibly empty sequence of Unicode characters, and ends with a quotation mark.
-To allow a string to contain a quotations mark, it must be prefixed by one backslash (""''\\''"", U+005C).
-To allow a string to contain a backslash, it also must be prefixed by one backslash.
-Unicode characters with a code less than U+FF are encoded by by the sequence ""''\\xNM''"", where ''NM'' is the hex encoding of the character.
-Unicode characters with a code less than U+FFFF are encoded by by the sequence ""''\\uNMOP''"", where ''NMOP'' is the hex encoding of the character.
-Unicode characters with a code less than U+FFFFFF are encoded by by the sequence ""''\\UNMOPQR''"", where ''NMOPQR'' is the hex encoding of the character.
-In addition, the sequence ""''\\t''"" encodes a horizontal tab (U+0009), the sequence ""''\\n''"" encodes a line feed (U+000A).
-
-Atoms are separated by Unicode characters of category separator (Z).
ADDED docs/manual/00001012920525.zettel
Index: docs/manual/00001012920525.zettel
==================================================================
--- docs/manual/00001012920525.zettel
+++ docs/manual/00001012920525.zettel
@@ -0,0 +1,36 @@
+id: 00001012920525
+title: SHTML Encoding
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230316181044
+modified: 20230403150657
+
+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:
+
+* [[//z/00001012920525?enc=shtml&part=zettel]],
+* [[//z/00001012920525?enc=shtml&part=meta]],
+* [[//z/00001012920525?enc=shtml&part=content]].
+
+If transferred via HTTP, the content type will be ''text/plain''.
+
+Internally, if a zettel should be transformed into HTML, the zettel is translated into the [[Sz encoding|00001012920516]], which is transformed into this SHTML encoding to produce the [[HTML encoding|00001012920510]].
+
+=== Syntax of SHTML
+There are only two types of elements: atoms and lists, similar to the Sz encoding.
+
+A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029).
+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 to 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`` 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).
DELETED docs/manual/00001012920582.zettel
Index: docs/manual/00001012920582.zettel
==================================================================
--- docs/manual/00001012920582.zettel
+++ docs/manual/00001012920582.zettel
@@ -1,14 +0,0 @@
-id: 00001012920582
-title: ZJSON Encoding: List of Valid Metadata Value Objects Names
-role: manual
-tags: #api #manual #reference #zettelstore
-syntax: zmk
-modified: 20220223184324
-
-Every Metadata value element is mapped to a JSON object with some well defined names / keys.
-
-|=Name | JSON Value | Meaning
-| ''"\"'' | string | The type of the Zettelmarkup element.
-| ''"i"'' | array | A sequence of [[inline-structured|00001007040000]] elements.
-| ''"s"'' | string | The first / major string value of an element.
-| ''"y"'' | array | A set of string values.
DELETED docs/manual/00001012920584.zettel
Index: docs/manual/00001012920584.zettel
==================================================================
--- docs/manual/00001012920584.zettel
+++ docs/manual/00001012920584.zettel
@@ -1,28 +0,0 @@
-id: 00001012920584
-title: ZJSON Encoding: Mapping of Metadata Key Types to Object Names
-role: manual
-tags: #api #manual #reference #zettelstore
-syntax: zmk
-modified: 20220304114135
-
-Every [[Metadata key|00001006030000]] is mapped to an [[object name|00001012920582]] where its value is encoded.
-
-|=Type | JSON Object Name | Remark
-| [[Credential|00001006031000]] | ''"s"'' | A string with the decrypted credential.
-| [[EString|00001006031500]] | ''"s"'' | A possibly empty string.
-| [[Identifier|00001006032000]] | ''"s"'' | A string containing a [[zettel identifier|00001006050000]].
-| [[IdentifierSet|00001006032500]] | ''"y"'' | An array of strings containing [[zettel identifier|00001006050000]].
-| [[Number|00001006033000]] | ''"s"'' | A string containing a numeric value.
-| [[String|00001006033500]] | ''"s"'' | A non-empty string.
-| [[TagSet|00001006034000]] | ''"y"'' | An array of string containing zettel tags.
-| [[Timestamp|00001006034500]] | ''"s"'' | A string containing a timestamp in the format YYYYMMDDHHmmSS.
-| [[URL|00001006035000]] | ''"s"'' | A string containing an URL.
-| [[Word|00001006035500]] | ''"s"'' | A string containing a word (no space characters)
-| [[WordSet|00001006036000]] | ''"y"'' | An array of strings containing words.
-| [[Zettelmarkup|00001006036500]] | ''"i"'' | A sequence of [[inline-structured|00001007040000]] elements.
-
-Please note, that metadata is weakly typed.
-Every metadata key expects a certain type.
-But the user is free to enter something different.
-For example, even if the metadata type is ""number"", its value could still be ""abc"".
-However, the mapping itself is always valid.
DELETED docs/manual/00001012920588.zettel
Index: docs/manual/00001012920588.zettel
==================================================================
--- docs/manual/00001012920588.zettel
+++ docs/manual/00001012920588.zettel
@@ -1,24 +0,0 @@
-id: 00001012920588
-title: ZJSON Encoding: List of Valid Zettelmarkup Element Objects Names
-role: manual
-tags: #api #manual #reference #zettelstore
-syntax: zmk
-modified: 20220301102447
-
-Every [[Zettelmarkup|00001007000000]] element is mapped to a JSON object with some well defined names / keys.
-
-|=Name | JSON Value | Meaning
-| ''"\"'' | string | The type of the Zettelmarkup element.
-| ''"a"'' | object | Additional attributes of the element.
-| ''"b"'' | array | A sequence of [[block-structured|00001007030000]] elements.
-| ''"c"'' | array | A sequence of a sequence of (sub-) list elements or [[inline-structured|00001007040000]] elements. Used for nested lists.
-| ''"d"'' | array | A sequence of description list elements, where each element is an object of a definition term and a list of descriptions.
-| ''"e"'' | array | A sequence of descriptions: a JSON array of simple description, which is itself a JSON array of block structured elements.
-| ''"i"'' | array | A sequence of [[inline-structured|00001007040000]] elements.
-| ''"j"'' | object | An objects describing a BLOB element.
-| ''"n"'' | number | A numeric value, e.g. for specifying the [[heading|00001007030300]] level.
-| ''"o"'' | string | A base64 encoded binary value. Used in some BLOB elements.
-| ''"p"'' | array | A sequence of two elements: a sequence of [[table|00001007031000]] header value, followed by a sequence of sequence of table row values.
-| ''"q"'' | string | A second string value, if ''""s""'' is already used.
-| ''"s"'' | string | The first / major string value of an element.
-| ''"v"'' | string | A third string value, if ''""q""'' is already used.
Index: docs/manual/00001012921000.zettel
==================================================================
--- docs/manual/00001012921000.zettel
+++ docs/manual/00001012921000.zettel
@@ -1,14 +1,23 @@
id: 00001012921000
-title: API: JSON structure of an access token
+title: API: Structure of an access token
+role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
-role: manual
+created: 20210126175322
+modified: 20230807165915
If the [[authentication process|00001012050200]] was successful, an access token with some additional data is returned.
The same is true, if the access token was [[renewed|00001012050400]].
-The response is structured as an JSON object, with the following named values:
+The response is structured as a [[symbolic expression|00001012930000]] list, with the following elements:
-|=Name|Description
-|''access_token''|The access token itself, as string value, which is a [[JSON Web Token|https://tools.ietf.org/html/rfc7519]] (JWT, RFC 7915)
-|''token_type''|The type of the token, always set to ''"Bearer"'', as described in [[RFC 6750|https://tools.ietf.org/html/rfc6750]]
-|''expires_in''|An integer that gives a hint about the lifetime / endurance of the token, measured in seconds
+# The type of the token, always set to ''"Bearer"'', as described in [[RFC 6750|https://tools.ietf.org/html/rfc6750]]
+# The token itself, which is technically the string representation of a [[symbolic expression|00001012930500]] containing relevant data, plus a check sum.
+#* The symbolic expression has the form ''(KIND USERNAME NOW EXPIRE Z-ID)''
+#* ''KIND'' is ''0'' for an API access, ''1'' if it created for the Web user interface.
+#* ''USERNAME'' is the user name of the user.
+#* ''NOW'' is a timestamp of the current time.
+#* ''EXPIRE'' is the timestamp when the access token expires.
+#* ''Z-ID'' is the zettel identifier of the user zettel.
+ The symbolic expression is encoded via ""base64"".
+ Based on this encoding, a checksum is calculated, also encoded via ""base64"".
+ Both encoded values are concatenated, with a period (''"."'') as a delimiter.
Index: docs/manual/00001012921200.zettel
==================================================================
--- docs/manual/00001012921200.zettel
+++ docs/manual/00001012921200.zettel
@@ -1,14 +1,15 @@
id: 00001012921200
title: API: Encoding of Zettel Access Rights
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
-modified: 20220201171959
+created: 20220201173115
+modified: 20230807164817
-Various API calls return a JSON key ''"rights"'' that encodes the access rights the user currently has.
-It is an integer number between 0 and 62.[^Not all values in this range are used.]
+Various API calls return a symbolic expression list ''(rights N)'', with ''N'' as a number, that encodes the access rights the user currently has.
+''N'' is an integer number between 0 and 62.[^Not all values in this range are used.]
The value ""0"" signals that something went wrong internally while determining the access rights.
A value of ""1"" says, that the current user has no access right for the given zettel.
In most cases, this value will not occur, because only zettel are presented, which are at least readable by the current user.
ADDED docs/manual/00001012930000.zettel
Index: docs/manual/00001012930000.zettel
==================================================================
--- docs/manual/00001012930000.zettel
+++ docs/manual/00001012930000.zettel
@@ -0,0 +1,26 @@
+id: 00001012930000
+title: Symbolic Expression
+role: manual
+tags: #manual #reference #zettelstore
+syntax: zmk
+created: 20230403145644
+modified: 20230403154010
+
+A symbolic expression (also called __s-expression__) is a notation of a list-based tree.
+Inner nodes are lists of arbitrary length, outer nodes are primitive values (also called __atoms__) or the empty list.
+
+A symbolic expression is either
+* a primitive value (__atom__), or
+* a list of the form __(E,,1,, E,,2,, … E,,n,,)__, where __E,,i,,__ is itself a symbolic expression, separated by space characters.
+
+An atom is a number, a string, or a symbol.
+
+Symbolic expressions are used in programming languages like LISP or Scheme, where they denote both data structures and the program itself.
+This allows a LISP or Scheme program to process LISP or Scheme programs.
+That property is also used within Zettelstore.
+
+Symbolic expressions are relatively easy to read, to parse, and to process.
+
+=== See also
+* [[Syntax|00001012930500]] of symbolic expressions in the Zettelstore
+* [[S-expression @ Wikipedia|https://en.wikipedia.org/wiki/S-expression]]
ADDED docs/manual/00001012930500.zettel
Index: docs/manual/00001012930500.zettel
==================================================================
--- docs/manual/00001012930500.zettel
+++ docs/manual/00001012930500.zettel
@@ -0,0 +1,77 @@
+id: 00001012930500
+title: Syntax of Symbolic Expressions
+role: manual
+tags: #manual #reference #zettelstore
+syntax: zmk
+created: 20230403151127
+modified: 20230703174218
+
+=== Syntax of lists
+A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029).
+A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms.
+
+Internally, lists are composed of __cells__.
+A cell allows to store two values.
+The first value references the first element of a list.
+The second value references the rest of the list, or is the special value __nil__ if there is no rest of the list.
+
+However, it is possible to store an atom as the second value of the last cell.
+In this case, before the last element of a list of at least two elements, a full stop character (""''.''"", U+002E) signals such a cell as the last two elements.
+This allows a more space economic storage of data.
+
+A __proper__ list, which contains __nil__ as the second value of the last element might be pictured as follows:
+
+~~~draw
++---+---+ +---+---+ +---+---+
+| V | N +-->| V | N +--> -->| V | |
++-+-+---+ +-+-+---+ +-+-+---+
+ | | |
+ v v v
++-------+ +-------+ +-------+
+| Elem1 | | Elem2 | | ElemN |
++-------+ +-------+ +-------+
+~~~
+
+''V'' is a placeholder for a value, ''N'' is the reference to the next cell (also known as the rest / tail of the list).
+Above list will be represented as an symbolic expression as ''(Elem1 Elem2 ... ElemN)''
+
+An improper list will have a non-__nil__ reference to an atom as the very last element
+
+~~~draw
++---+---+ +---+---+ +---+---+
+| V | N +-->| V | N +--> -->| V | V |
++-+-+---+ +-+-+---+ +-+-+-+-+
+ | | | |
+ v v v v
++-------+ +-------+ +-------+ +------+
+| Elem1 | | Elem2 | | ElemN | | Atom |
++-------+ +-------+ +-------+ +------+
+~~~
+
+Above improper list will be represented as an symbolic expression as ''(Elem1 Elem2 ... ElemN . Atom)''
+
+
+=== Syntax of numbers (atom)
+A number is a non-empty sequence of digits (""0"" ... ""9"").
+The smallest number is ``0``, there are no negative numbers.
+
+=== Syntax of symbols (atom)
+A symbol is a non-empty sequence of printable characters, except left or right parenthesis.
+Unicode characters of the following categories contains printable characters in the above sense: letter (L), number (N), punctuation (P), symbol (S).
+Symbols are case-sensitive, i.e. ""''ZETTEL''"" and ""''zettel''"" denote different symbols.
+
+=== Syntax of string (atom)
+
+A string starts with a quotation mark (""''"''"", U+0022), contains a possibly empty sequence of Unicode characters, and ends with a quotation mark.
+To allow a string to contain a quotations mark, it must be prefixed by one backslash (""''\\''"", U+005C).
+To allow a string to contain a backslash, it also must be prefixed by one backslash.
+Unicode characters with a code less than U+FF are encoded by by the sequence ""''\\xNM''"", where ''NM'' is the hex encoding of the character.
+Unicode characters with a code less than U+FFFF are encoded by by the sequence ""''\\uNMOP''"", where ''NMOP'' is the hex encoding of the character.
+Unicode characters with a code less than U+FFFFFF are encoded by by the sequence ""''\\UNMOPQR''"", where ''NMOPQR'' is the hex encoding of the character.
+In addition, the sequence ""''\\t''"" encodes a horizontal tab (U+0009), the sequence ""''\\n''"" encodes a line feed (U+000A).
+
+=== See also
+* Currently, Zettelstore uses [[sx|https://zettelstore.de/sx]] (""Symbolic eXPression Framework"") to implement symbolic expression.
+ The project page might contain additional information about the full syntax.
+
+ Zettelstore only uses lists, numbers, string, and symbols to represent zettel.
ADDED docs/manual/00001012931000.zettel
Index: docs/manual/00001012931000.zettel
==================================================================
--- docs/manual/00001012931000.zettel
+++ docs/manual/00001012931000.zettel
@@ -0,0 +1,81 @@
+id: 00001012931000
+title: Encoding of Sz
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230403153903
+modified: 20230405133249
+
+Zettel in a [[Sz encoding|00001012920516]] are represented as a [[symbolic expression|00001012930000]].
+To process these symbolic expressions, you need to know, how a specific part of a zettel is represented by a symbolic expression.
+
+Basically, each part of a zettel is represented as a list, often a nested list.
+The first element of that list is always an unique symbol, which denotes that part.
+The meaning / semantic of all other elements depend on that symbol.
+
+=== Zettel
+A full zettel is represented by a list of two elements.
+The first elements represents the metadata, the second element represents the zettel content.
+
+:::syntax
+__Zettel__ **=** ''('' [[__Metadata__|#metadata]] [[__Content__|#content]] '')''.
+:::
+
+=== Metadata
+
+Metadata is represented by a list, where the first element is the symbol ''META''.
+Following elements represent each metadatum[^""Metadatum"" is used as the singular form of metadata.] of a zettel in standard order.
+
+Standard order is: [[Title|00001006020000#title]], [[Role|00001006020000#role]], [[Tags|00001006020000#tags]], [[Syntax|00001006020000#syntax]], all other [[keys|00001006020000]] in alphabetic order.
+
+:::syntax
+__Metadata__ **=** ''(META'' [[__Metadatum__|00001012931200]] __Metadatum__ … __Metadatum__ '')''.
+:::
+=== Content
+
+Zettel content is represented by a block.
+:::syntax
+__Content__ **=** [[__Block__|#block]].
+:::
+
+==== Block
+A block is represented by a list with the symbol ''BLOCK'' as the first element.
+All following elements represent a nested [[block-structured element|00001007030000]].
+
+:::syntax
+[!block|__Block__] **=** ''(BLOCK'' [[__BlockElement__|00001012931400]] __BlockElement__ … __BlockElement__ '')''.
+:::
+
+==== Inline
+Both block-structured elements and some metadata values may contain [[inline-structured elements|00001007040000]].
+Similar, inline-structured elements are represented as follows:
+
+:::syntax
+__Inline__ **=** ''(INLINE'' [[__InlineElement__|00001012931600]] __InlineElement__ … __InlineElement__ '')''.
+:::
+
+==== Attribute
+[[Attributes|00001007050000]] may be specified for both block- and inline- structured elements.
+Attributes are represented by the following schema.
+Please note, the the symbol ''quote'' is lower-case by intention.
+
+:::syntax
+__Attribute__ **=** ''('' **[** ''quote'' ''('' [[__AttributeKeyValue__|00001012931800]] __AttributeKeyValue__ … __AttributeKeyValue__ '')'' **]** ')'.
+:::
+
+Either, there are no attributes.
+These are specified by the empty list ''()''.
+Or there are attributes.
+In this case, the first element of the list must be the symbol ''quote'': ''(quote'' ''('' A,,1,, A,,2,, … A,,n,, '')'''')''.
+
+=== Other
+A list with ''UNKNOWN'' as its first element signals an internal error during transforming a zettel into the Sz encoding.
+It may be ignored, or it may produce an error.
+
+:::syntax
+__Unknown__ **=** ''(UNKNOWN'' Object … '')''.
+:::
+
+The list may only contain the symbol ''UNKNOWN'', or additionally an unlimited amount of other objects.
+
+Similar, any symbol with the pattern ''**xyz:NOT-FOUND**'', where ''xyz'' is any string, signals an internal error.
ADDED docs/manual/00001012931200.zettel
Index: docs/manual/00001012931200.zettel
==================================================================
--- docs/manual/00001012931200.zettel
+++ docs/manual/00001012931200.zettel
@@ -0,0 +1,41 @@
+id: 00001012931200
+title: Encoding of Sz Metadata
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230403161618
+modified: 20230405121932
+
+A single metadata (""metadatum"") is represented by a triple: a symbol representing the type, a symbol representing the key, and either a string or a list that represent the value.
+
+The key symbol must be ""quoted"", i.e. for the key ""title"": ''(quote title)''.
+This property may be relaxed in future versions of the Zettelstore.
+
+The symbol depends on the [[metadata key type|00001006030000]].
+The value also depends somehow on the key type: a set of values is represented as a list, all other values are represented by a string, even if it is a number.
+
+The following table maps key types to symbols and to the type of the value representation.
+
+|=Key Type<| Symbol<| Value<
+| [[Credential|00001006031000]] | ''CREDENTIAL'' | string
+| [[EString|00001006031500]] | ''EMPTY-STRING'' | string
+| [[Identifier|00001006032000]] | ''ZID'' | string
+| [[IdentifierSet|00001006032500]] | ''ZID-SET'' | list
+| [[Number|00001006033000]] | ''NUMBER'' | string
+| [[String|00001006033500]] | ''STRING'' | string
+| [[TagSet|00001006034000]] | ''TAG-SET'' | list
+| [[Timestamp|00001006034500]] | ''TIMESTAMP'' | string
+| [[URL|00001006035000]] | ''URL'' | string
+| [[Word|00001006035500]] | ''WORD'' | string
+| [[WordSet|00001006036000]] | ''WORD-SET'' | list
+| [[Zettelmarkup|00001006036500]] | ''ZETTELMARKUP'' | string
+
+If the value is represented as a list, its first element is the symbol ''list'', and all other elements are strings with the appropriate values.
+
+:::syntax
+__ListValue__ **=** ''(list'' String,,1,, String,,2,, … String,,n,, '')''.
+:::
+
+Examples:
+* The title of this zettel is represented as: ''(EMPTY-STRING (quote title) "Encoding of Sz Metadata")''
+* The tags of this zettel are represented as: ''(TAG-SET (quote tags) (list "#api" "#manual" "#reference" "#zettelstore"))''
ADDED docs/manual/00001012931400.zettel
Index: docs/manual/00001012931400.zettel
==================================================================
--- docs/manual/00001012931400.zettel
+++ docs/manual/00001012931400.zettel
@@ -0,0 +1,186 @@
+id: 00001012931400
+title: Encoding of Sz Block Elements
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230403161803
+modified: 20230405132916
+
+=== ''PARA''
+:::syntax
+__Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] … '')''.
+:::
+A paragraph is just a list of inline elements.
+
+=== ''HEADING''
+:::syntax
+__Heading__ **=** ''(HEADING'' Number [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, [[__InlineElement__|00001012931600]] … '')''.
+:::
+A heading consists of a number, which specifies its level (1 -- 5), its optional attributes.
+The first string is a ""slug"" of the heading text, i.e. transformed it to lower case, replaced space character with minus sign, and some more.
+The second string is the slug, but made unique for the whole zettel.
+Then the heading text follows as a sequence of inline elements.
+
+=== ''THEMATIC''
+:::syntax
+__Thematic__ **=** ''(THEMATIC'' [[__Attributes__|00001012931000#attribute]] '')''.
+:::
+
+=== ''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'' __ListElement__ … '')''.
+
+__UnorderedList__ **=** ''(UNORDERED'' __ListElement__ … '')''.
+
+__QuotationList__ **=** ''(QUOTATION'' __ListElement__ … '')''.
+:::
+
+:::syntax
+__ListElement__ **=** [[__Block__|00001012931000#block]] **|** [[__Inline__|00001012931000#inline]].
+:::
+A list element is either a block or an inline.
+If it is a block, it may contain a nested list.
+=== ''DESCRIPTION''
+:::syntax
+__Description__ **=** ''(DESCRIPTION'' __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''.
+:::
+A description is a sequence of one ore more terms and values.
+
+:::syntax
+__DescriptionTerm__ **=** [[__Inline__|00001012931000#inline]].
+:::
+A description term is just an inline-structured value.
+
+:::syntax
+__DescriptionValues__ **=** ''(BLOCK'' [[__Block__|00001012931000#block]] … '')''.
+:::
+Description values are sequences of blocks.
+
+=== ''TABLE''
+:::syntax
+__Table__ **=** ''(TABLE'' __TableHeader__ __TableRow__ … '')''.
+:::
+A table is a table header and a sequence of table rows.
+
+:::syntax
+__TableHeader__ **=** ''()'' **|** ''(list'' __TableCell__ … '')''.
+:::
+A table header is either the empty list or a list of table cells stating with the ''list'' symbol.
+
+:::syntax
+__TableRow__ **=** ''(list'' __TableCell__ … '')''.
+:::
+A table row is a list with the initial symbol ''list'', followed by table cells.
+
+=== ''CELL'', ''CELL-*''
+There are four kinds of table cells, one for each possible cell alignment.
+The structure is the same for all kind.
+
+:::syntax
+__TableCell__ **=** __DefaultCell__ **|** __CenterCell__ **|** __LeftCell__ **|** __RightCell__.
+:::
+
+:::syntax
+__DefaultCell__ **=** ''(CELL'' [[__InlineElement__|00001012931600]] … '')''.
+:::
+The cell content, specified by the sequence of inline elements, used the default alignment.
+
+:::syntax
+__CenterCell__ **=** ''(CELL-CENTER'' [[__InlineElement__|00001012931600]] … '')''.
+:::
+The cell content, specified by the sequence of inline elements, is centered aligned.
+
+:::syntax
+__LeftCell__ **=** ''(CELL-LEFT'' [[__InlineElement__|00001012931600]] … '')''.
+:::
+The cell content, specified by the sequence of inline elements, is left aligned.
+
+:::syntax
+__RightCell__ **=** ''(CELL-RIGHT'' [[__InlineElement__|00001012931600]] … '')''.
+:::
+The cell content, specified by the sequence of inline elements, is right aligned.
+
+=== ''REGION-*''
+The following lists specifies different kinds of regions.
+A region treat a sequence of block elements to be belonging together in certain ways.
+The have a similar structure.
+
+:::syntax
+__BlockRegion__ **=** ''(REGION-BLOCK'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] [[__Inline__|00001012931000#inline]] '')''.
+:::
+A block region just treats the block to belong in an unspecified way.
+Typically, the reason is given in the attributes.
+The inline describes the block.
+
+:::syntax
+__QuoteRegion__ **=** ''(REGION-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] [[__Inline__|00001012931000#inline]] '')''.
+:::
+A block region just treats the block to contain a longer quotation.
+Attributes may further specify the quotation.
+The inline typically describes author / source of the quotation.
+
+:::syntax
+__VerseRegion__ **=** ''(REGION-VERSE'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] [[__Inline__|00001012931000#inline]] '')''.
+:::
+A block region just treats the block to contain a verse.
+Soft line break are transformed into hard line breaks to save the structure of the verse / poem.
+Attributes may further specify something.
+The inline typically describes author / source of the verse.
+
+=== ''VERBATIM-*''
+The following lists specifies some literal text of more than one line.
+The structure is always the same, the initial symbol denotes the actual usage.
+The content is encoded as a string, most likely to contain control characters that signals the end of a line.
+
+:::syntax
+__CommentVerbatim__ **=** ''(VERBATIM-COMMENT'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as an internal comment not to be interpreted further.
+
+:::syntax
+__EvalVerbatim__ **=** ''(VERBATIM-EVAL'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be evaluated by an (external) software to produce some derived content.
+
+:::syntax
+__HTMLVerbatim__ **=** ''(VERBATIM-HTML'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains HTML code.
+
+:::syntax
+__MathVerbatim__ **=** ''(VERBATIM-MATH'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as special code to be interpreted as mathematical formulas.
+
+:::syntax
+__CodeVerbatim__ **=** ''(VERBATIM-CODE'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as (executable) code.
+
+:::syntax
+__ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as (nested) zettel content.
+
+=== ''BLOB''
+:::syntax
+__BLOB__ **=** ''(BLOB'' [[__Inline__|00001012931000#inline]] String,,1,, String,,2,, '')''.
+:::
+A BLOB contains an image in block mode.
+The inline 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]] '')''.
+:::
+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.
ADDED docs/manual/00001012931600.zettel
Index: docs/manual/00001012931600.zettel
==================================================================
--- docs/manual/00001012931600.zettel
+++ docs/manual/00001012931600.zettel
@@ -0,0 +1,212 @@
+id: 00001012931600
+title: Encoding of Sz Inline Elements
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230403161845
+modified: 20230405123222
+
+=== ''TEXT''
+:::syntax
+__Text__ **=** ''(TEXT'' String '')''.
+:::
+Specifies the string as some text content, typically a word.
+
+=== ''SPACE''
+:::syntax
+__Space__ **=** ''(SPACE'' **[** String **]** '')''.
+:::
+Specifies some space, typically white space.
+If the string is not given it is assumed to be ''" "'' (one space character).
+Otherwise it contains the space characters.
+
+=== ''SOFT''
+:::syntax
+__Soft__ **=** ''(SOFT)''.
+:::
+Denotes a soft line break.
+It is typically translated into a space character, but signals the point in the textual content, where a line break occurred.
+
+=== ''HARD''
+:::syntax
+__Hard__ **=** ''(HARD)''.
+:::
+Specifies a hard line break, i.e. the user wants to have a line break here.
+
+=== ''LINK-*''
+The following lists specify various links, based on the full reference.
+They all have the same structure, with a trailing sequence of __InlineElements__ that contain the linked text.
+
+:::syntax
+__InvalidLink__ **=** ''(LINK-INVALID'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains the invalid link specification.
+
+:::syntax
+__ZettelLink__ **=** ''(LINK-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains the zettel identifier, a zettel reference.
+
+:::syntax
+__SelfLink__ **=** ''(LINK-SELF'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains the number sign character and the name of a zettel mark.
+It reference the same zettel where it occurs.
+
+:::syntax
+__FoundLink__ **=** ''(LINK-FOUND'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains a zettel identifier, a zettel reference, of a zettel known to be included in the Zettelstore.
+
+:::syntax
+__BrokenLink__ **=** ''(LINK-BROKEN'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains a zettel identifier, a zettel reference, of a zettel known to be __not__ included in the Zettelstore.
+
+:::syntax
+__HostedLink__ **=** ''(LINK-HOSTED'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains a link starting with one slash character, denoting an absolute local reference.
+
+:::syntax
+__BasedLink__ **=** ''(LINK-BASED'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains a link starting with two slash characters, denoting a local reference interpreted relative to the Zettelstore base URL.
+
+:::syntax
+__QueryLink__ **=** ''(LINK-BASED'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains a [[query expression|00001007700000]].
+
+:::syntax
+__ExternalLink__ **=** ''(LINK-EXTERNAL'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains a full URL, referencing a resource outside of the Zettelstore server.
+
+=== ''EMBED''
+:::syntax
+__Embed__ **=** ''(EMBED'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+Specifies an embedded element, in most cases some image content or an inline transclusion.
+A transclusion will only be used, if the zettel content was not evaluated.
+
+__Reference__ denoted the referenced zettel.
+
+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,, '')''.
+:::
+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 further encoded.
+Otherwise a base64 encoding is used.
+
+=== ''CITE''
+:::syntax
+__CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''.
+:::
+The string contains the citation key.
+
+=== ''MARK''
+:::syntax
+__Mark__ **=** ''(MARK'' String,,1,, String,,2,, String,,3,, [[__InlineElement__|00001012931600]] … '')''.
+:::
+The first string is the mark string used in the content.
+The second string is the mark string transformed to a slug, i.e. transformed to lower case, remove non-ASCII characters.
+The third string is the slug string, but made unique for the whole zettel.
+Then follows the marked text as a sequence of __InlineElement__s.
+
+=== ''ENDNOTE''
+:::syntax
+__Endnote__ **=** ''(ENDNOTE'' [[__Attributes__|00001012931000#attribute]] ''(quote'' [[__InlineElement__|00001012931600]] … '')'''')''.
+:::
+Specifies endnote / footnote text.
+
+=== ''FORMAT-*''
+The following lists specifies some inline text formatting.
+The structure is always the same, the initial symbol denotes the actual formatting.
+
+:::syntax
+__DeleteFormat__ **=** ''(FORMAT-DELETE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as deleted.
+
+:::syntax
+__EmphasizeFormat__ **=** ''(FORMAT-EMPH'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as emphasized.
+
+:::syntax
+__InsertFormat__ **=** ''(FORMAT-INSERT'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as inserted.
+
+:::syntax
+__QuoteFormat__ **=** ''(FORMAT-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as quoted text.
+
+:::syntax
+__SpanFormat__ **=** ''(FORMAT-SPAN'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as belonging together in a certain, yet unspecified way.
+
+:::syntax
+__SubScriptFormat__ **=** ''(FORMAT-SUB'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as sub-scripted text.
+
+:::syntax
+__SuperScriptFormat__ **=** ''(FORMAT-SUPER'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as super-scripted text.
+
+:::syntax
+__StrongFormat__ **=** ''(FORMAT-STRONG'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
+:::
+The inline text should be treated as strongly emphasized.
+
+=== ''LITERAL-*''
+The following lists specifies some literal text.
+The structure is always the same, the initial symbol denotes the actual usage.
+
+:::syntax
+__CodeLiteral__ **=** ''(LITERAL-CODE'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+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
+__HTMLLiteral__ **=** ''(LITERAL-HTML'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as HTML code.
+
+:::syntax
+__InputLiteral__ **=** ''(LITERAL-INPUT'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as input entered by an 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 an user.
+
+:::syntax
+__ZettelLiteral__ **=** ''(LITERAL-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''.
+:::
+The string contains text that should be treated as (nested) zettel content.
ADDED docs/manual/00001012931800.zettel
Index: docs/manual/00001012931800.zettel
==================================================================
--- docs/manual/00001012931800.zettel
+++ docs/manual/00001012931800.zettel
@@ -0,0 +1,28 @@
+id: 00001012931800
+title: Encoding of Sz Attribute Values
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230403161923
+modified: 20230403163701
+
+An attribute is represented by a single cell.
+The first element of the cell references the attribute key, the second value the corresponding value.
+
+:::syntax
+__AttributeKeyValue__ **=** ''('' __AttributeKey__ ''.'' __AttributeValue__ '')''.
+:::
+
+__AttributeKey__ and __AttributeValue__ are [[string values|00001012930500]].
+
+An empty key denotes the generic attribute.
+
+A key with the value ''"-"'' specifies the default attribute.
+In this case, the attribute value is not interpreted.
+
+Some examples:
+* ''()'' represents the absence of attributes,
+* ''(quote (("-" . "")))'' represent the default attribute,
+* ''(quote (("-" . "") ("" . "syntax")))'' adds the generic attrribute with the value ""syntax"",
+* ''(quote ())'' will also represent the absence of attribute (in a more complicated way),
+* ''(quote (("lang" . "en")))'' denote the attribute key ""lang"" with a value ""en"".
ADDED docs/manual/00001012931900.zettel
Index: docs/manual/00001012931900.zettel
==================================================================
--- docs/manual/00001012931900.zettel
+++ docs/manual/00001012931900.zettel
@@ -0,0 +1,38 @@
+id: 00001012931900
+title: Encoding of Sz Reference Values
+role: manual
+tags: #api #manual #reference #zettelstore
+syntax: zmk
+created: 20230405123046
+modified: 20230405124516
+
+A reference is encoded as the actual reference value, and a symbol describing the state of that actual reference value.
+
+:::syntax
+__Reference__ **=** ''(quote'' __ReferenceState__ String '')''.
+:::
+The ''quote'' is needed for internal reasons, the string contains the actual reference value.
+
+:::syntax
+__ReferenceState__ **=** ''INVALID'' **|** ''ZETTEL'' **|** ''SELF'' **|** ''FOUND'' **|** ''BROKEN'' **|** ''HOSTED'' **|** ''BASED'' **|** ''QUERY'' **|** ''EXTERNAL''.
+:::
+
+The meaning of the state symbols corresponds to that of the symbols used for the description of [[link references|00001012931600#link]].
+
+; ''INVALID''
+: The reference value is invalid.
+; ''ZETTEL''
+: The reference value is a reference to a zettel.
+ This value is only possible before evaluating the zettel.
+; ''SELF''
+: The reference value is a reference to the same zettel, to a specific mark.
+; ''FOUND''
+: The reference value is a valid reference to an existing zettel.
+ This value is only possible after evaluating the zettel.
+; ''BROKEN''
+: The reference value is a valid reference to an missing zettel.
+ This value is only possible after evaluating the zettel.
+; ''HOSTED''
+; ''BASED''
+; ''QUERY''
+; ''EXTERNAL''
Index: docs/manual/00001017000000.zettel
==================================================================
--- docs/manual/00001017000000.zettel
+++ docs/manual/00001017000000.zettel
@@ -2,49 +2,52 @@
title: Tips and Tricks
role: manual
tags: #manual #zettelstore
syntax: zmk
created: 20220803170112
-modified: 20220916132030
+modified: 20230827225202
=== Welcome Zettel
* **Problem:** You want to put your Zettelstore into the public and need a starting zettel for your users.
In addition, you still want a ""home zettel"", with all your references to internal, non-public zettel.
Zettelstore only allows to specify one [[''home-zettel''|00001004020000#home-zettel]].
-* **Solution:**
+* **Solution 1:**
*# Create a new zettel with all your references to internal, non-public zettel.
Let's assume this zettel receives the zettel identifier ''20220803182600''.
*# Create the zettel that should serve as the starting zettel for your users.
It must have syntax [[Zettelmarkup|00001008000000#zmk]], i.e. the syntax metadata must be set to ''zmk''.
If needed, set the runtime configuration [[''home-zettel|00001004020000#home-zettel]] to the value of the identifier of this zettel.
*# At the beginning of the start zettel, add the following [[Zettelmarkup|00001007000000]] text in a separate paragraph: ``{{{20220803182600}}}`` (you have to adapt to the actual value of the zettel identifier for your non-public home zettel).
* **Discussion:** As stated in the description for a [[transclusion|00001007031100]], a transclusion will be ignored, if the transcluded zettel is not visible to the current user.
In effect, the transclusion statement (above paragraph that contained ''{{{...}}}'') is ignored when rendering the zettel.
+* **Solution 2:** Set a user-specific value by adding metadata ''home-zettel'' to the [[user zettel|00001010040200]].
+* **Discussion:** A value for ''home-zettel'' is first searched in the user zettel of the current authenticated user.
+ Only if it is not found, the value is looked up in the runtime configuration zettel.
+ If multiple user should use the same home zettel, its zettel identifier must be set in all relevant user zettel.
=== Role-specific Layout of Zettel in Web User Interface (WebUI)
[!role-css]
* **Problem:** You want to add some CSS when displaying zettel of a specific [[role|00001006020000#role]].
For example, you might want to add a yellow background color for all [[configuration|00001006020100#configuration]] zettel.
Or you want a multi-column layout.
-* **Solution:** If you enable [[''expert-mode''|00001004020000#expert-mode]], you will have access to a zettel called ""[[Zettelstore Role to CSS Map|00000000029000]]"" (its identifier is ''00000000029000'').
- This zettel maps a role name to a zettel that must contain the role-specific CSS code.
+* **Solution:** If you enable [[''expert-mode''|00001004020000#expert-mode]], you will have access to a zettel called ""[[Zettelstore Sxn Start Code|00000000019000]]"" (its identifier is ''00000000019000'').
+ This zettel is the starting point for Sxn code, where you can place a definition for a variable named ""CSS-ROLE-map"".
- First, create a zettel containing the needed CSS: give it any title, its role is preferably ""configuration"" (but this is not a must).
- Set its [[''syntax''|00001006020000#syntax]] must be set to ""[[css|00001008000000#css]]"".
+ But first, create a zettel containing the needed CSS: give it any title, its role is preferably ""configuration"" (but this is not a must).
+ Its [[''syntax''|00001006020000#syntax]] must be set to ""[[css|00001008000000#css]]"".
The content must contain the role-specific CSS code, for example ``body {background-color: #FFFFD0}``for a background in a light yellow color.
Let's assume, the newly created CSS zettel got the identifier ''20220825200100''.
- Now, you have to connect this zettel to the zettel called ""Zettelstore Role CSS Map"".
- Since you have enabled ''expert-mode'', you are allowed to modify it.
- Add the following metadata ''css-configuration-zid: 20220825200100'' to assign the role-specific CSS code for the role ""configuration"" to the CSS zettel containing that CSS.
+ Now, you have to map this freshly created zettel to a role, for example ""zettel"".
+ Since you have enabled ''expert-mode'', you are allowed to modify the zettel ""[[Zettelstore Sxn Start Code|00000000019000]]"".
+ Add the following code to the Sxn Start Code zettel: ``(define CSS-ROLE-map '(("zettel" . "20220825200100")))``.
- In general, its role-assigning metadata must be like this pattern: ''css-ROLE-zid: ID'', where ''ROLE'' is the placeholder for the role, and ''ID'' for the zettel identifier containing CSS code.
- It is allowed to assign more than one role to a specific CSS zettel.
+ In general, the mapping must follow the pattern: ``(ROLE . ID)``, where ''ROLE'' is the placeholder for the role, and ''ID'' for the zettel identifier containing CSS code.
+ For example, if you also want the role ""configuration"" to be rendered using that CSS, the code should be something like ``(define CSS-ROLE-map '(("zettel" . "20220825200100") ("configuration" . "20220825200100")))``.
* **Discussion:** you have to ensure that the CSS zettel is allowed to be read by the intended audience of the zettel with that given role.
For example, if you made zettel with a specific role public visible, the CSS zettel must also have a [[''visibility: public''|00001010070200]] metadata.
-* **Extension:** if you have already established a role-specific layout for zettel, but you additionally want just one another zettel with another role to be rendered with the same CSS, you have to add metadata to the one zettel: ''css-role: ROLE'', where ''ROLE'' is the placeholder for the role that already is assigned to a specific CSS-based layout.
=== Zettel synchronization with iCloud (Apple)
* **Problem:** You use Zettelstore on various macOS computers and you want to use the sameset of zettel across all computers.
* **Solution:** Place your zettel in an iCloud folder.
Index: docs/readmezip.txt
==================================================================
--- docs/readmezip.txt
+++ docs/readmezip.txt
@@ -15,7 +15,6 @@
possible to make it directly available for your local Zettelstore.
The software, including the manual, is licensed under the European Union Public
License 1.2 (or later). See the separate file LICENSE.txt.
-To get in contact with the developer, send an email to ds@zettelstore.de or
-follow Zettelstore on Twitter: https://twitter.com/zettelstore.
+To get in contact with the developer, send an email to ds@zettelstore.de.
DELETED domain/content.go
Index: domain/content.go
==================================================================
--- domain/content.go
+++ domain/content.go
@@ -1,129 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package domain provides domain specific types, constants, and functions.
-package domain
-
-import (
- "bytes"
- "encoding/base64"
- "errors"
- "io"
- "unicode"
- "unicode/utf8"
-
- "zettelstore.de/z/input"
-)
-
-// Content is just the content of a zettel.
-type Content struct {
- data []byte
- isBinary bool
-}
-
-// NewContent creates a new content from a string.
-func NewContent(data []byte) Content {
- return Content{data: data, isBinary: calcIsBinary(data)}
-}
-
-// Length returns the number of bytes stored.
-func (zc *Content) Length() int { return len(zc.data) }
-
-// Equal compares two content values.
-func (zc *Content) Equal(o *Content) bool {
- if zc == nil {
- return o == nil
- }
- if zc.isBinary != o.isBinary {
- return false
- }
- return bytes.Equal(zc.data, o.data)
-}
-
-// Set content to new string value.
-func (zc *Content) Set(data []byte) {
- zc.data = data
- zc.isBinary = calcIsBinary(data)
-}
-
-// Write it to a Writer
-func (zc *Content) Write(w io.Writer) (int, error) {
- return w.Write(zc.data)
-}
-
-// AsString returns the content itself is a string.
-func (zc *Content) AsString() string { return string(zc.data) }
-
-// AsBytes returns the content itself is a byte slice.
-func (zc *Content) AsBytes() []byte { return zc.data }
-
-// IsBinary returns true if the content contains non-unicode values or is,
-// interpreted a text, with a high probability binary content.
-func (zc *Content) IsBinary() bool { return zc.isBinary }
-
-// TrimSpace remove some space character in content, if it is not binary content.
-func (zc *Content) TrimSpace() {
- if zc.isBinary {
- return
- }
- inp := input.NewInput(zc.data)
- pos := inp.Pos
- for inp.Ch != input.EOS {
- if input.IsEOLEOS(inp.Ch) {
- inp.Next()
- pos = inp.Pos
- continue
- }
- if !input.IsSpace(inp.Ch) {
- break
- }
- inp.Next()
- }
- zc.data = bytes.TrimRightFunc(inp.Src[pos:], unicode.IsSpace)
-}
-
-// Encode content for future transmission.
-func (zc *Content) Encode() (data, encoding string) {
- if !zc.isBinary {
- return zc.AsString(), ""
- }
- return base64.StdEncoding.EncodeToString(zc.data), "base64"
-}
-
-// SetDecoded content to the decoded value of the given string.
-func (zc *Content) SetDecoded(data, encoding string) error {
- switch encoding {
- case "":
- zc.data = []byte(data)
- case "base64":
- decoded, err := base64.StdEncoding.DecodeString(data)
- if err != nil {
- return err
- }
- zc.data = decoded
- default:
- return errors.New("unknown encoding " + encoding)
- }
- zc.isBinary = calcIsBinary(zc.data)
- return nil
-}
-
-func calcIsBinary(data []byte) bool {
- if !utf8.Valid(data) {
- return true
- }
- l := len(data)
- for i := 0; i < l; i++ {
- if data[i] == 0 {
- return true
- }
- }
- return false
-}
DELETED domain/content_test.go
Index: domain/content_test.go
==================================================================
--- domain/content_test.go
+++ domain/content_test.go
@@ -1,66 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-package domain_test
-
-import (
- "testing"
-
- "zettelstore.de/z/domain"
-)
-
-func TestContentIsBinary(t *testing.T) {
- t.Parallel()
- td := []struct {
- s string
- exp bool
- }{
- {"abc", false},
- {"äöü", false},
- {"", false},
- {string([]byte{0}), true},
- }
- for i, tc := range td {
- content := domain.NewContent([]byte(tc.s))
- got := content.IsBinary()
- if got != tc.exp {
- t.Errorf("TC=%d: expected %v, got %v", i, tc.exp, got)
- }
- }
-}
-
-func TestTrimSpace(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- in, exp string
- }{
- {"", ""},
- {" ", ""},
- {"abc", "abc"},
- {" abc", " abc"},
- {"abc ", "abc"},
- {"abc \n", "abc"},
- {"abc\n ", "abc"},
- {"\nabc", "abc"},
- {" \nabc", "abc"},
- {" \n abc", " abc"},
- {" \n\n abc", " abc"},
- {" \n \n abc", " abc"},
- {" \n \n abc \n \n ", " abc"},
- }
- for _, tc := range testcases {
- c := domain.NewContent([]byte(tc.in))
- c.TrimSpace()
- got := c.AsString()
- if got != tc.exp {
- t.Errorf("TrimSpace(%q) should be %q, but got %q", tc.in, tc.exp, got)
- }
- }
-}
DELETED domain/id/id.go
Index: domain/id/id.go
==================================================================
--- domain/id/id.go
+++ domain/id/id.go
@@ -1,163 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package id provides domain specific types, constants, and functions about
-// zettel identifier.
-package id
-
-import (
- "strconv"
- "time"
-
- "zettelstore.de/c/api"
-)
-
-// Zid is the internal identifier of a zettel. Typically, it is a
-// time stamp of the form "YYYYMMDDHHmmSS" converted to an unsigned integer.
-// A zettelstore implementation should try to set the last two digits to zero,
-// e.g. the seconds should be zero,
-type Zid uint64
-
-// Some important ZettelIDs.
-const (
- Invalid = Zid(0) // Invalid is a Zid that will never be valid
-)
-
-// ZettelIDs that are used as Zid more than once.
-//
-// Note: if you change some values, ensure that you also change them in the
-// Constant box. They are mentioned there literally, because these
-// constants are not available there.
-var (
- ConfigurationZid = MustParse(api.ZidConfiguration)
- BaseTemplateZid = MustParse(api.ZidBaseTemplate)
- LoginTemplateZid = MustParse(api.ZidLoginTemplate)
- ListTemplateZid = MustParse(api.ZidListTemplate)
- ZettelTemplateZid = MustParse(api.ZidZettelTemplate)
- InfoTemplateZid = MustParse(api.ZidInfoTemplate)
- FormTemplateZid = MustParse(api.ZidFormTemplate)
- RenameTemplateZid = MustParse(api.ZidRenameTemplate)
- DeleteTemplateZid = MustParse(api.ZidDeleteTemplate)
- ContextTemplateZid = MustParse(api.ZidContextTemplate)
- ErrorTemplateZid = MustParse(api.ZidErrorTemplate)
- RoleCSSMapZid = MustParse(api.ZidRoleCSSMap)
- EmojiZid = MustParse(api.ZidEmoji)
- TOCNewTemplateZid = MustParse(api.ZidTOCNewTemplate)
- DefaultHomeZid = MustParse(api.ZidDefaultHome)
-)
-
-const maxZid = 99999999999999
-
-// ParseUint interprets a string as a possible zettel identifier
-// and returns its integer value.
-func ParseUint(s string) (uint64, error) {
- res, err := strconv.ParseUint(s, 10, 47)
- if err != nil {
- return 0, err
- }
- if res == 0 || res > maxZid {
- return res, strconv.ErrRange
- }
- return res, nil
-}
-
-// Parse interprets a string as a zettel identification and
-// returns its value.
-func Parse(s string) (Zid, error) {
- if len(s) != 14 {
- return Invalid, strconv.ErrSyntax
- }
- res, err := ParseUint(s)
- if err != nil {
- return Invalid, err
- }
- return Zid(res), nil
-}
-
-// MustParse tries to interpret a string as a zettel identifier and returns
-// its value or panics otherwise.
-func MustParse(s api.ZettelID) Zid {
- zid, err := Parse(string(s))
- if err == nil {
- return zid
- }
- panic(err)
-}
-
-// String converts the zettel identification to a string of 14 digits.
-// Only defined for valid ids.
-func (zid Zid) String() string {
- var result [14]byte
- zid.toByteArray(&result)
- return string(result[:])
-}
-
-// Bytes converts the zettel identification to a byte slice of 14 digits.
-// Only defined for valid ids.
-func (zid Zid) Bytes() []byte {
- var result [14]byte
- zid.toByteArray(&result)
- return result[:]
-}
-
-// toByteArray converts the Zid into a fixed byte array, usable for printing.
-//
-// Based on idea by Daniel Lemire: "Converting integers to fix-digit representations quickly"
-// https://lemire.me/blog/2021/11/18/converting-integers-to-fix-digit-representations-quickly/
-func (zid Zid) toByteArray(result *[14]byte) {
- date := uint64(zid) / 1000000
- fullyear := date / 10000
- century := fullyear / 100
- year := fullyear % 100
- monthday := date % 10000
- month := monthday / 100
- day := monthday % 100
- time := uint64(zid) % 1000000
- hmtime := time / 100
- second := time % 100
- hour := hmtime / 100
- minute := hmtime % 100
- result[0] = byte(century/10) + '0'
- result[1] = byte(century%10) + '0'
- result[2] = byte(year/10) + '0'
- result[3] = byte(year%10) + '0'
- result[4] = byte(month/10) + '0'
- result[5] = byte(month%10) + '0'
- result[6] = byte(day/10) + '0'
- result[7] = byte(day%10) + '0'
- result[8] = byte(hour/10) + '0'
- result[9] = byte(hour%10) + '0'
- result[10] = byte(minute/10) + '0'
- result[11] = byte(minute%10) + '0'
- result[12] = byte(second/10) + '0'
- result[13] = byte(second%10) + '0'
-}
-
-// IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits.
-func (zid Zid) IsValid() bool { return 0 < zid && zid <= maxZid }
-
-// ZidLayout to transform a date into a Zid and into other internal dates.
-const ZidLayout = "20060102150405"
-
-// New returns a new zettel id based on the current time.
-func New(withSeconds bool) Zid {
- now := time.Now().Local()
- var s string
- if withSeconds {
- s = now.Format(ZidLayout)
- } else {
- s = now.Format("20060102150400")
- }
- res, err := Parse(s)
- if err != nil {
- panic(err)
- }
- return res
-}
DELETED domain/id/id_test.go
Index: domain/id/id_test.go
==================================================================
--- domain/id/id_test.go
+++ domain/id/id_test.go
@@ -1,87 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 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.
-//-----------------------------------------------------------------------------
-
-// Package id_test provides unit tests for testing zettel id specific functions.
-package id_test
-
-import (
- "testing"
-
- "zettelstore.de/z/domain/id"
-)
-
-func TestIsValid(t *testing.T) {
- t.Parallel()
- validIDs := []string{
- "00000000000001",
- "00000000000020",
- "00000000000300",
- "00000000004000",
- "00000000050000",
- "00000000600000",
- "00000007000000",
- "00000080000000",
- "00000900000000",
- "00001000000000",
- "00020000000000",
- "00300000000000",
- "04000000000000",
- "50000000000000",
- "99999999999999",
- "00001007030200",
- "20200310195100",
- }
-
- for i, sid := range validIDs {
- zid, err := id.Parse(sid)
- if err != nil {
- t.Errorf("i=%d: sid=%q is not valid, but should be. err=%v", i, sid, err)
- }
- s := zid.String()
- if s != sid {
- t.Errorf(
- "i=%d: zid=%v does not format to %q, but to %q", i, zid, sid, s)
- }
- }
-
- invalidIDs := []string{
- "", "0", "a",
- "00000000000000",
- "0000000000000a",
- "000000000000000",
- "20200310T195100",
- }
-
- for i, sid := range invalidIDs {
- if zid, err := id.Parse(sid); err == nil {
- t.Errorf("i=%d: sid=%q is valid (zid=%s), but should not be", i, sid, zid)
- }
- }
-}
-
-var sResult string // to disable compiler optimization in loop below
-
-func BenchmarkString(b *testing.B) {
- var s string
- for n := 0; n < b.N; n++ {
- s = id.Zid(12345678901200).String()
- }
- sResult = s
-}
-
-var bResult []byte // to disable compiler optimization in loop below
-
-func BenchmarkBytes(b *testing.B) {
- var bs []byte
- for n := 0; n < b.N; n++ {
- bs = id.Zid(12345678901200).Bytes()
- }
- bResult = bs
-}
DELETED domain/id/set.go
Index: domain/id/set.go
==================================================================
--- domain/id/set.go
+++ domain/id/set.go
@@ -1,119 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 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.
-//-----------------------------------------------------------------------------
-
-package id
-
-// Set is a set of zettel identifier
-type Set map[Zid]struct{}
-
-// NewSet returns a new set of identifier with the given initial values.
-func NewSet(zids ...Zid) Set {
- l := len(zids)
- if l < 8 {
- l = 8
- }
- result := make(Set, l)
- result.AddSlice(zids)
- return result
-}
-
-// NewSetCap returns a new set of identifier with the given capacity and initial values.
-func NewSetCap(c int, zids ...Zid) Set {
- l := len(zids)
- if c < l {
- c = l
- }
- if c < 8 {
- c = 8
- }
- result := make(Set, c)
- result.AddSlice(zids)
- return result
-}
-
-// Zid adds a Zid to the set.
-func (s Set) Zid(zid Zid) Set {
- if s == nil {
- return NewSet(zid)
- }
- s[zid] = struct{}{}
- return s
-}
-
-// Contains return true if the set is nil or if the set contains the given Zettel identifier.
-func (s Set) Contains(zid Zid) bool {
- if s != nil {
- _, found := s[zid]
- return found
- }
- return true
-}
-
-// Add all member from the other set.
-func (s Set) Add(other Set) Set {
- if s == nil {
- return other
- }
- for zid := range other {
- s[zid] = struct{}{}
- }
- return s
-}
-
-// AddSlice adds all identifier of the given slice to the set.
-func (s Set) AddSlice(sl Slice) {
- for _, zid := range sl {
- s[zid] = struct{}{}
- }
-}
-
-// Sorted returns the set as a sorted slice of zettel identifier.
-func (s Set) Sorted() Slice {
- if l := len(s); l > 0 {
- result := make(Slice, 0, l)
- for zid := range s {
- result = append(result, zid)
- }
- result.Sort()
- return result
- }
- return nil
-}
-
-// IntersectOrSet removes all zettel identifier that are not in the other set.
-// Both sets can be modified by this method. One of them is the set returned.
-// It contains the intersection of both, if s is not nil.
-//
-// If s == nil, then the other set is always returned.
-func (s Set) IntersectOrSet(other Set) Set {
- if s == nil {
- return other
- }
- if len(s) > len(other) {
- s, other = other, s
- }
- for zid := range s {
- _, otherOk := other[zid]
- if !otherOk {
- delete(s, zid)
- }
- }
- return s
-}
-
-// Remove all zettel identifier from 's' that are in the set 'other'.
-func (s Set) Remove(other Set) {
- if s == nil || other == nil {
- return
- }
- for zid := range other {
- delete(s, zid)
- }
-}
DELETED domain/id/set_test.go
Index: domain/id/set_test.go
==================================================================
--- domain/id/set_test.go
+++ domain/id/set_test.go
@@ -1,149 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 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.
-//-----------------------------------------------------------------------------
-
-package id_test
-
-import (
- "testing"
-
- "zettelstore.de/z/domain/id"
-)
-
-func TestSetContains(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- s id.Set
- zid id.Zid
- exp bool
- }{
- {nil, id.Invalid, true},
- {nil, 14, true},
- {id.NewSet(), id.Invalid, false},
- {id.NewSet(), 1, false},
- {id.NewSet(), id.Invalid, false},
- {id.NewSet(1), 1, true},
- }
- for i, tc := range testcases {
- got := tc.s.Contains(tc.zid)
- if got != tc.exp {
- t.Errorf("%d: %v.Contains(%v) == %v, but got %v", i, tc.s, tc.zid, tc.exp, got)
- }
- }
-}
-
-func TestSetAdd(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- s1, s2 id.Set
- exp id.Slice
- }{
- {nil, nil, nil},
- {id.NewSet(), nil, nil},
- {id.NewSet(), id.NewSet(), nil},
- {nil, id.NewSet(1), id.Slice{1}},
- {id.NewSet(1), nil, id.Slice{1}},
- {id.NewSet(1), id.NewSet(), id.Slice{1}},
- {id.NewSet(1), id.NewSet(2), id.Slice{1, 2}},
- {id.NewSet(1), id.NewSet(1), id.Slice{1}},
- }
- for i, tc := range testcases {
- sl1 := tc.s1.Sorted()
- sl2 := tc.s2.Sorted()
- got := tc.s1.Add(tc.s2).Sorted()
- if !got.Equal(tc.exp) {
- t.Errorf("%d: %v.Add(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got)
- }
- }
-}
-
-func TestSetSorted(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- set id.Set
- exp id.Slice
- }{
- {nil, nil},
- {id.NewSet(), nil},
- {id.NewSet(9, 4, 6, 1, 7), id.Slice{1, 4, 6, 7, 9}},
- }
- for i, tc := range testcases {
- got := tc.set.Sorted()
- if !got.Equal(tc.exp) {
- t.Errorf("%d: %v.Sorted() should be %v, but got %v", i, tc.set, tc.exp, got)
- }
- }
-}
-
-func TestSetIntersectOrSet(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- s1, s2 id.Set
- exp id.Slice
- }{
- {nil, nil, nil},
- {id.NewSet(), nil, nil},
- {nil, id.NewSet(), nil},
- {id.NewSet(), id.NewSet(), nil},
- {id.NewSet(1), nil, nil},
- {nil, id.NewSet(1), id.Slice{1}},
- {id.NewSet(1), id.NewSet(), nil},
- {id.NewSet(), id.NewSet(1), nil},
- {id.NewSet(1), id.NewSet(2), nil},
- {id.NewSet(2), id.NewSet(1), nil},
- {id.NewSet(1), id.NewSet(1), id.Slice{1}},
- }
- for i, tc := range testcases {
- sl1 := tc.s1.Sorted()
- sl2 := tc.s2.Sorted()
- got := tc.s1.IntersectOrSet(tc.s2).Sorted()
- if !got.Equal(tc.exp) {
- t.Errorf("%d: %v.IntersectOrSet(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got)
- }
- }
-}
-
-func TestSetRemove(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- s1, s2 id.Set
- exp id.Slice
- }{
- {nil, nil, nil},
- {id.NewSet(), nil, nil},
- {id.NewSet(), id.NewSet(), nil},
- {id.NewSet(1), nil, id.Slice{1}},
- {id.NewSet(1), id.NewSet(), id.Slice{1}},
- {id.NewSet(1), id.NewSet(2), id.Slice{1}},
- {id.NewSet(1), id.NewSet(1), id.Slice{}},
- }
- for i, tc := range testcases {
- sl1 := tc.s1.Sorted()
- sl2 := tc.s2.Sorted()
- newS1 := id.NewSet(sl1...)
- newS1.Remove(tc.s2)
- got := newS1.Sorted()
- if !got.Equal(tc.exp) {
- t.Errorf("%d: %v.Remove(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got)
- }
- }
-}
-
-// func BenchmarkSet(b *testing.B) {
-// s := id.Set{}
-// for i := 0; i < b.N; i++ {
-// s[id.Zid(i)] = true
-// }
-// }
-func BenchmarkSet(b *testing.B) {
- s := id.Set{}
- for i := 0; i < b.N; i++ {
- s[id.Zid(i)] = struct{}{}
- }
-}
DELETED domain/id/slice.go
Index: domain/id/slice.go
==================================================================
--- domain/id/slice.go
+++ domain/id/slice.go
@@ -1,69 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2021 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.
-//-----------------------------------------------------------------------------
-
-// Package id provides domain specific types, constants, and functions about
-// zettel identifier.
-package id
-
-import (
- "bytes"
- "sort"
-)
-
-// Slice is a sequence of zettel identifier. A special case is a sorted slice.
-type Slice []Zid
-
-func (zs Slice) Len() int { return len(zs) }
-func (zs Slice) Less(i, j int) bool { return zs[i] < zs[j] }
-func (zs Slice) Swap(i, j int) { zs[i], zs[j] = zs[j], zs[i] }
-
-// Sort a slice of Zids.
-func (zs Slice) Sort() { sort.Sort(zs) }
-
-// Copy a zettel identifier slice
-func (zs Slice) Copy() Slice {
- if zs == nil {
- return nil
- }
- result := make(Slice, len(zs))
- copy(result, zs)
- return result
-}
-
-// Equal reports whether zs and other are the same length and contain the samle zettel
-// identifier. A nil argument is equivalent to an empty slice.
-func (zs Slice) Equal(other Slice) bool {
- if len(zs) != len(other) {
- return false
- }
- if len(zs) == 0 {
- return true
- }
- for i, e := range zs {
- if e != other[i] {
- return false
- }
- }
- return true
-}
-
-func (zs Slice) String() string {
- if len(zs) == 0 {
- return ""
- }
- var buf bytes.Buffer
- for i, zid := range zs {
- if i > 0 {
- buf.WriteByte(' ')
- }
- buf.WriteString(zid.String())
- }
- return buf.String()
-}
DELETED domain/id/slice_test.go
Index: domain/id/slice_test.go
==================================================================
--- domain/id/slice_test.go
+++ domain/id/slice_test.go
@@ -1,88 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2021 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.
-//-----------------------------------------------------------------------------
-
-// Package id provides domain specific types, constants, and functions about
-// zettel identifier.
-package id_test
-
-import (
- "testing"
-
- "zettelstore.de/z/domain/id"
-)
-
-func TestSliceSort(t *testing.T) {
- t.Parallel()
- zs := id.Slice{9, 4, 6, 1, 7}
- zs.Sort()
- exp := id.Slice{1, 4, 6, 7, 9}
- if !zs.Equal(exp) {
- t.Errorf("Slice.Sort did not work. Expected %v, got %v", exp, zs)
- }
-}
-
-func TestCopy(t *testing.T) {
- t.Parallel()
- var orig id.Slice
- got := orig.Copy()
- if got != nil {
- t.Errorf("Nil copy resulted in %v", got)
- }
- orig = id.Slice{9, 4, 6, 1, 7}
- got = orig.Copy()
- if !orig.Equal(got) {
- t.Errorf("Slice.Copy did not work. Expected %v, got %v", orig, got)
- }
-}
-
-func TestSliceEqual(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- s1, s2 id.Slice
- exp bool
- }{
- {nil, nil, true},
- {nil, id.Slice{}, true},
- {nil, id.Slice{1}, false},
- {id.Slice{1}, id.Slice{1}, true},
- {id.Slice{1}, id.Slice{2}, false},
- {id.Slice{1, 2}, id.Slice{2, 1}, false},
- {id.Slice{1, 2}, id.Slice{1, 2}, true},
- }
- for i, tc := range testcases {
- got := tc.s1.Equal(tc.s2)
- if got != tc.exp {
- t.Errorf("%d/%v.Equal(%v)==%v, but got %v", i, tc.s1, tc.s2, tc.exp, got)
- }
- got = tc.s2.Equal(tc.s1)
- if got != tc.exp {
- t.Errorf("%d/%v.Equal(%v)==%v, but got %v", i, tc.s2, tc.s1, tc.exp, got)
- }
- }
-}
-
-func TestSliceString(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- in id.Slice
- exp string
- }{
- {nil, ""},
- {id.Slice{}, ""},
- {id.Slice{1}, "00000000000001"},
- {id.Slice{1, 2}, "00000000000001 00000000000002"},
- }
- for i, tc := range testcases {
- got := tc.in.String()
- if got != tc.exp {
- t.Errorf("%d/%v: expected %q, but got %q", i, tc.in, tc.exp, got)
- }
- }
-}
DELETED domain/meta/collection.go
Index: domain/meta/collection.go
==================================================================
--- domain/meta/collection.go
+++ domain/meta/collection.go
@@ -1,101 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2022 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.
-//-----------------------------------------------------------------------------
-
-package meta
-
-import "sort"
-
-// Arrangement stores metadata within its categories.
-// Typecally a category might be a tag name, a role name, a syntax value.
-type Arrangement map[string][]*Meta
-
-// CreateArrangement by inspecting a given key and use the found
-// value as a category.
-func CreateArrangement(metaList []*Meta, key string) Arrangement {
- if len(metaList) == 0 {
- return nil
- }
- descr := Type(key)
- if descr == nil {
- return nil
- }
- a := make(Arrangement)
- if descr.IsSet {
- for _, m := range metaList {
- if vals, ok := m.GetList(key); ok {
- for _, val := range vals {
- a[val] = append(a[val], m)
- }
- }
- }
- } else {
- for _, m := range metaList {
- if val, ok := m.Get(key); ok && val != "" {
- a[val] = append(a[val], m)
- }
- }
- }
- return a
-}
-
-// Counted returns the list of categories, together with the number of
-// metadata for each category.
-func (a Arrangement) Counted() CountedCategories {
- if len(a) == 0 {
- return nil
- }
- result := make(CountedCategories, 0, len(a))
- for cat, metas := range a {
- result = append(result, CountedCategory{Name: cat, Count: len(metas)})
- }
- return result
-}
-
-// CountedCategory contains of a name and the number how much this name occured
-// somewhere.
-type CountedCategory struct {
- Name string
- Count int
-}
-
-// CountedCategories is the list of CountedCategories.
-// Every name must occur only once.
-type CountedCategories []CountedCategory
-
-// SortByName sorts the list by the name attribute.
-// Since each name must occur only once, two CountedCategories cannot have
-// the same name.
-func (ccs CountedCategories) SortByName() {
- sort.Slice(ccs, func(i, j int) bool { return ccs[i].Name < ccs[j].Name })
-}
-
-// SortByCount sorts the list by the count attribute, descending.
-// If two counts are equal, elements are sorted by name.
-func (ccs CountedCategories) SortByCount() {
- sort.Slice(ccs, func(i, j int) bool {
- iCount, jCount := ccs[i].Count, ccs[j].Count
- if iCount > jCount {
- return true
- }
- if iCount == jCount {
- return ccs[i].Name < ccs[j].Name
- }
- return false
- })
-}
-
-// Categories returns just the category names.
-func (ccs CountedCategories) Categories() []string {
- result := make([]string, len(ccs))
- for i, cc := range ccs {
- result[i] = cc.Name
- }
- return result
-}
DELETED domain/meta/meta.go
Index: domain/meta/meta.go
==================================================================
--- domain/meta/meta.go
+++ domain/meta/meta.go
@@ -1,420 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta provides the domain specific type 'meta'.
-package meta
-
-import (
- "bytes"
- "regexp"
- "sort"
- "strings"
- "unicode"
- "unicode/utf8"
-
- "zettelstore.de/c/api"
- "zettelstore.de/c/maps"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/input"
- "zettelstore.de/z/strfun"
-)
-
-type keyUsage int
-
-const (
- _ keyUsage = iota
- usageUser // Key will be manipulated by the user
- usageComputed // Key is computed by zettelstore
- usageProperty // Key is computed and not stored by zettelstore
-)
-
-// DescriptionKey formally describes each supported metadata key.
-type DescriptionKey struct {
- Name string
- Type *DescriptionType
- usage keyUsage
- Inverse string
-}
-
-// IsComputed returns true, if metadata is computed and not set by the user.
-func (kd *DescriptionKey) IsComputed() bool { return kd.usage >= usageComputed }
-
-// IsProperty returns true, if metadata is a computed property.
-func (kd *DescriptionKey) IsProperty() bool { return kd.usage >= usageProperty }
-
-// IsStoredComputed retruns true, if metadata is computed, but also stored.
-func (kd *DescriptionKey) IsStoredComputed() bool { return kd.usage == usageComputed }
-
-var registeredKeys = make(map[string]*DescriptionKey)
-
-func registerKey(name string, t *DescriptionType, usage keyUsage, inverse string) {
- if _, ok := registeredKeys[name]; ok {
- panic("Key '" + name + "' already defined")
- }
- if inverse != "" {
- if t != TypeID && t != TypeIDSet {
- panic("Inversable key '" + name + "' is not identifier type, but " + t.String())
- }
- inv, ok := registeredKeys[inverse]
- if !ok {
- panic("Inverse Key '" + inverse + "' not found")
- }
- if !inv.IsComputed() {
- panic("Inverse Key '" + inverse + "' is not computed.")
- }
- if inv.Type != TypeIDSet {
- panic("Inverse Key '" + inverse + "' is not an identifier set, but " + inv.Type.String())
- }
- }
- registeredKeys[name] = &DescriptionKey{name, t, usage, inverse}
-}
-
-// IsComputed returns true, if key denotes a computed metadata key.
-func IsComputed(name string) bool {
- if kd, ok := registeredKeys[name]; ok {
- return kd.IsComputed()
- }
- return false
-}
-
-// IsProperty returns true, if key denotes a property metadata value.
-func IsProperty(name string) bool {
- if kd, ok := registeredKeys[name]; ok {
- return kd.IsProperty()
- }
- return false
-}
-
-// IsStoredComputed returns true, if key denotes a computed metadata key that is stored.
-func IsStoredComputed(name string) bool {
- if kd, ok := registeredKeys[name]; ok {
- return kd.IsStoredComputed()
- }
- return false
-}
-
-// Inverse returns the name of the inverse key.
-func Inverse(name string) string {
- if kd, ok := registeredKeys[name]; ok {
- return kd.Inverse
- }
- return ""
-}
-
-// GetDescription returns the key description object of the given key name.
-func GetDescription(name string) DescriptionKey {
- if d, ok := registeredKeys[name]; ok {
- return *d
- }
- return DescriptionKey{Type: Type(name)}
-}
-
-// GetSortedKeyDescriptions delivers all metadata key descriptions as a slice, sorted by name.
-func GetSortedKeyDescriptions() []*DescriptionKey {
- keys := maps.Keys(registeredKeys)
- result := make([]*DescriptionKey, 0, len(keys))
- for _, n := range keys {
- result = append(result, registeredKeys[n])
- }
- return result
-}
-
-// Supported keys.
-func init() {
- registerKey(api.KeyID, TypeID, usageComputed, "")
- registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "")
- registerKey(api.KeyRole, TypeWord, usageUser, "")
- registerKey(api.KeyTags, TypeTagSet, usageUser, "")
- registerKey(api.KeySyntax, TypeWord, usageUser, "")
-
- // Properties that are inverse keys
- registerKey(api.KeyFolge, TypeIDSet, usageProperty, "")
- registerKey(api.KeySuccessors, TypeIDSet, usageProperty, "")
-
- registerKey(api.KeyAuthor, TypeString, usageUser, "")
- registerKey(api.KeyBack, TypeIDSet, usageProperty, "")
- registerKey(api.KeyBackward, TypeIDSet, usageProperty, "")
- registerKey(api.KeyBoxNumber, TypeNumber, usageProperty, "")
- registerKey(api.KeyCopyright, TypeString, usageUser, "")
- registerKey(api.KeyCreated, TypeTimestamp, usageComputed, "")
- registerKey(api.KeyCredential, TypeCredential, usageUser, "")
- registerKey(api.KeyDead, TypeIDSet, usageProperty, "")
- registerKey(api.KeyForward, TypeIDSet, usageProperty, "")
- registerKey(api.KeyLang, TypeWord, usageUser, "")
- registerKey(api.KeyLicense, TypeEmpty, usageUser, "")
- registerKey(api.KeyModified, TypeTimestamp, usageComputed, "")
- registerKey(api.KeyPrecursor, TypeIDSet, usageUser, api.KeyFolge)
- registerKey(api.KeyPredecessor, TypeID, usageUser, api.KeySuccessors)
- registerKey(api.KeyPublished, TypeTimestamp, usageProperty, "")
- registerKey(api.KeyReadOnly, TypeWord, usageUser, "")
- registerKey(api.KeySummary, TypeZettelmarkup, usageUser, "")
- registerKey(api.KeyURL, TypeURL, usageUser, "")
- registerKey(api.KeyUselessFiles, TypeString, usageProperty, "")
- registerKey(api.KeyUserID, TypeWord, usageUser, "")
- registerKey(api.KeyUserRole, TypeWord, usageUser, "")
- registerKey(api.KeyVisibility, TypeWord, usageUser, "")
-}
-
-// NewPrefix is the prefix for metadata key in template zettel for creating new zettel.
-const NewPrefix = "new-"
-
-// Meta contains all meta-data of a zettel.
-type Meta struct {
- Zid id.Zid
- pairs map[string]string
- YamlSep bool
-}
-
-// New creates a new chunk for storing metadata.
-func New(zid id.Zid) *Meta {
- return &Meta{Zid: zid, pairs: make(map[string]string, 5)}
-}
-
-// NewWithData creates metadata object with given data.
-func NewWithData(zid id.Zid, data map[string]string) *Meta {
- pairs := make(map[string]string, len(data))
- for k, v := range data {
- pairs[k] = v
- }
- return &Meta{Zid: zid, pairs: pairs}
-}
-
-// Length returns the number of bytes stored for the metadata.
-func (m *Meta) Length() int {
- if m == nil {
- return 0
- }
- result := 6 // storage needed for Zid
- for k, v := range m.pairs {
- result += len(k) + len(v) + 1 // 1 because separator
- }
- return result
-}
-
-// Clone returns a new copy of the metadata.
-func (m *Meta) Clone() *Meta {
- return &Meta{
- Zid: m.Zid,
- pairs: m.Map(),
- YamlSep: m.YamlSep,
- }
-}
-
-// Map returns a copy of the meta data as a string map.
-func (m *Meta) Map() map[string]string {
- pairs := make(map[string]string, len(m.pairs))
- for k, v := range m.pairs {
- pairs[k] = v
- }
- return pairs
-}
-
-var reKey = regexp.MustCompile("^[0-9a-z][-0-9a-z]{0,254}$")
-
-// KeyIsValid returns true, the the key is a valid string.
-func KeyIsValid(key string) bool {
- return reKey.MatchString(key)
-}
-
-// Pair is one key-value-pair of a Zettel meta.
-type Pair struct {
- Key string
- Value string
-}
-
-var firstKeys = []string{api.KeyTitle, api.KeyRole, api.KeyTags, api.KeySyntax}
-var firstKeySet strfun.Set
-
-func init() {
- firstKeySet = strfun.NewSet(firstKeys...)
-}
-
-// Set stores the given string value under the given key.
-func (m *Meta) Set(key, value string) {
- if key != api.KeyID {
- m.pairs[key] = trimValue(value)
- }
-}
-
-// SetNonEmpty stores the given value under the given key, if the value is non-empty.
-// An empty value will delete the previous association.
-func (m *Meta) SetNonEmpty(key, value string) {
- if value == "" {
- delete(m.pairs, key)
- } else if key != api.KeyID {
- m.pairs[key] = trimValue(value)
- }
-}
-
-func trimValue(value string) string {
- return strings.TrimFunc(value, input.IsSpace)
-}
-
-// Get retrieves the string value of a given key. The bool value signals,
-// whether there was a value stored or not.
-func (m *Meta) Get(key string) (string, bool) {
- if key == api.KeyID {
- return m.Zid.String(), true
- }
- value, ok := m.pairs[key]
- return value, ok
-}
-
-// GetDefault retrieves the string value of the given key. If no value was
-// stored, the given default value is returned.
-func (m *Meta) GetDefault(key, def string) string {
- if value, ok := m.Get(key); ok {
- return value
- }
- return def
-}
-
-// GetTitle returns the title of the metadata. It is the only key that has a
-// defined default value: the string representation of the zettel identifier.
-func (m *Meta) GetTitle() string {
- if title, found := m.Get(api.KeyTitle); found {
- return title
- }
- return m.Zid.String()
-}
-
-// Pairs returns not computed key/values pairs stored, in a specific order.
-// First come the pairs with predefined keys: MetaTitleKey, MetaTagsKey, MetaSyntaxKey,
-// MetaContextKey. Then all other pairs are append to the list, ordered by key.
-func (m *Meta) Pairs() []Pair {
- return m.doPairs(m.getFirstKeys(), notComputedKey)
-}
-
-// ComputedPairs returns all key/values pairs stored, in a specific order. First come
-// the pairs with predefined keys: MetaTitleKey, MetaTagsKey, MetaSyntaxKey,
-// MetaContextKey. Then all other pairs are append to the list, ordered by key.
-func (m *Meta) ComputedPairs() []Pair {
- return m.doPairs(m.getFirstKeys(), anyKey)
-}
-
-// PairsRest returns not computed key/values pairs stored, except the values with
-// predefined keys. The pairs are ordered by key.
-func (m *Meta) PairsRest() []Pair {
- result := make([]Pair, 0, len(m.pairs))
- return m.doPairs(result, notComputedKey)
-}
-
-// ComputedPairsRest returns all key/values pairs stored, except the values with
-// predefined keys. The pairs are ordered by key.
-func (m *Meta) ComputedPairsRest() []Pair {
- result := make([]Pair, 0, len(m.pairs))
- return m.doPairs(result, anyKey)
-}
-
-func notComputedKey(key string) bool { return !IsComputed(key) }
-func anyKey(string) bool { return true }
-
-func (m *Meta) doPairs(firstKeys []Pair, addKeyPred func(string) bool) []Pair {
- keys := m.getKeysRest(addKeyPred)
- for _, k := range keys {
- firstKeys = append(firstKeys, Pair{k, m.pairs[k]})
- }
- return firstKeys
-}
-
-func (m *Meta) getFirstKeys() []Pair {
- result := make([]Pair, 0, len(m.pairs))
- for _, key := range firstKeys {
- if value, ok := m.pairs[key]; ok {
- result = append(result, Pair{key, value})
- }
- }
- return result
-}
-
-func (m *Meta) getKeysRest(addKeyPred func(string) bool) []string {
- keys := make([]string, 0, len(m.pairs))
- for k := range m.pairs {
- if !firstKeySet.Has(k) && addKeyPred(k) {
- keys = append(keys, k)
- }
- }
- sort.Strings(keys)
- return keys
-}
-
-// Delete removes a key from the data.
-func (m *Meta) Delete(key string) {
- if key != api.KeyID {
- delete(m.pairs, key)
- }
-}
-
-// Equal compares to metas for equality.
-func (m *Meta) Equal(o *Meta, allowComputed bool) bool {
- if m == nil && o == nil {
- return true
- }
- if m == nil || o == nil || m.Zid != o.Zid {
- return false
- }
- tested := make(strfun.Set, len(m.pairs))
- for k, v := range m.pairs {
- tested.Set(k)
- if !equalValue(k, v, o, allowComputed) {
- return false
- }
- }
- for k, v := range o.pairs {
- if !tested.Has(k) && !equalValue(k, v, m, allowComputed) {
- return false
- }
- }
- return true
-}
-
-func equalValue(key, val string, other *Meta, allowComputed bool) bool {
- if allowComputed || !IsComputed(key) {
- if valO, ok := other.pairs[key]; !ok || val != valO {
- return false
- }
- }
- return true
-}
-
-// Sanitize all metadata keys and values, so that they can be written safely into a file.
-func (m *Meta) Sanitize() {
- if m == nil {
- return
- }
- for k, v := range m.pairs {
- m.pairs[RemoveNonGraphic(k)] = RemoveNonGraphic(v)
- }
-}
-
-// RemoveNonGraphic changes the given string not to include non-graphical characters.
-// It is needed to sanitize meta data.
-func RemoveNonGraphic(s string) string {
- if s == "" {
- return ""
- }
- pos := 0
- var buf bytes.Buffer
- for pos < len(s) {
- nextPos := strings.IndexFunc(s[pos:], func(r rune) bool { return !unicode.IsGraphic(r) })
- if nextPos < 0 {
- break
- }
- buf.WriteString(s[pos:nextPos])
- buf.WriteByte(' ')
- _, size := utf8.DecodeRuneInString(s[nextPos:])
- pos = nextPos + size
- }
- if pos == 0 {
- return strings.TrimSpace(s)
- }
- buf.WriteString(s[pos:])
- return strings.TrimSpace(buf.String())
-}
DELETED domain/meta/meta_test.go
Index: domain/meta/meta_test.go
==================================================================
--- domain/meta/meta_test.go
+++ domain/meta/meta_test.go
@@ -1,262 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta provides the domain specific type 'meta'.
-package meta
-
-import (
- "strings"
- "testing"
-
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
-)
-
-const testID = id.Zid(98765432101234)
-
-func TestKeyIsValid(t *testing.T) {
- t.Parallel()
- validKeys := []string{"0", "a", "0-", "title", "title-----", strings.Repeat("r", 255)}
- for _, key := range validKeys {
- if !KeyIsValid(key) {
- t.Errorf("Key %q wrongly identified as invalid key", key)
- }
- }
- invalidKeys := []string{"", "-", "-a", "Title", "a_b", strings.Repeat("e", 256)}
- for _, key := range invalidKeys {
- if KeyIsValid(key) {
- t.Errorf("Key %q wrongly identified as valid key", key)
- }
- }
-}
-
-func TestTitleHeader(t *testing.T) {
- t.Parallel()
- m := New(testID)
- if got, ok := m.Get(api.KeyTitle); ok && got != "" {
- t.Errorf("Title is not empty, but %q", got)
- }
- addToMeta(m, api.KeyTitle, " ")
- if got, ok := m.Get(api.KeyTitle); ok && got != "" {
- t.Errorf("Title is not empty, but %q", got)
- }
- const st = "A simple text"
- addToMeta(m, api.KeyTitle, " "+st+" ")
- if got, ok := m.Get(api.KeyTitle); !ok || got != st {
- t.Errorf("Title is not %q, but %q", st, got)
- }
- addToMeta(m, api.KeyTitle, " "+st+"\t")
- const exp = st + " " + st
- if got, ok := m.Get(api.KeyTitle); !ok || got != exp {
- t.Errorf("Title is not %q, but %q", exp, got)
- }
-
- m = New(testID)
- const at = "A Title"
- addToMeta(m, api.KeyTitle, at)
- addToMeta(m, api.KeyTitle, " ")
- if got, ok := m.Get(api.KeyTitle); !ok || got != at {
- t.Errorf("Title is not %q, but %q", at, got)
- }
-}
-
-func checkSet(t *testing.T, exp []string, m *Meta, key string) {
- t.Helper()
- got, _ := m.GetList(key)
- for i, tag := range exp {
- if i < len(got) {
- if tag != got[i] {
- t.Errorf("Pos=%d, expected %q, got %q", i, exp[i], got[i])
- }
- } else {
- t.Errorf("Expected %q, but is missing", exp[i])
- }
- }
- if len(exp) < len(got) {
- t.Errorf("Extra tags: %q", got[len(exp):])
- }
-}
-
-func TestTagsHeader(t *testing.T) {
- t.Parallel()
- m := New(testID)
- checkSet(t, []string{}, m, api.KeyTags)
-
- addToMeta(m, api.KeyTags, "")
- checkSet(t, []string{}, m, api.KeyTags)
-
- addToMeta(m, api.KeyTags, " #t1 #t2 #t3 #t4 ")
- checkSet(t, []string{"#t1", "#t2", "#t3", "#t4"}, m, api.KeyTags)
-
- addToMeta(m, api.KeyTags, "#t5")
- checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags)
-
- addToMeta(m, api.KeyTags, "t6")
- checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags)
-}
-
-func TestSyntax(t *testing.T) {
- t.Parallel()
- m := New(testID)
- if got, ok := m.Get(api.KeySyntax); ok || got != "" {
- t.Errorf("Syntax is not %q, but %q", "", got)
- }
- addToMeta(m, api.KeySyntax, " ")
- if got, _ := m.Get(api.KeySyntax); got != "" {
- t.Errorf("Syntax is not %q, but %q", "", got)
- }
- addToMeta(m, api.KeySyntax, "MarkDown")
- const exp = "markdown"
- if got, ok := m.Get(api.KeySyntax); !ok || got != exp {
- t.Errorf("Syntax is not %q, but %q", exp, got)
- }
- addToMeta(m, api.KeySyntax, " ")
- if got, _ := m.Get(api.KeySyntax); got != "" {
- t.Errorf("Syntax is not %q, but %q", "", got)
- }
-}
-
-func checkHeader(t *testing.T, exp map[string]string, gotP []Pair) {
- t.Helper()
- got := make(map[string]string, len(gotP))
- for _, p := range gotP {
- got[p.Key] = p.Value
- if _, ok := exp[p.Key]; !ok {
- t.Errorf("Key %q is not expected, but has value %q", p.Key, p.Value)
- }
- }
- for k, v := range exp {
- if gv, ok := got[k]; !ok || v != gv {
- if ok {
- t.Errorf("Key %q is not %q, but %q", k, v, got[k])
- } else {
- t.Errorf("Key %q missing, should have value %q", k, v)
- }
- }
- }
-}
-
-func TestDefaultHeader(t *testing.T) {
- t.Parallel()
- m := New(testID)
- addToMeta(m, "h1", "d1")
- addToMeta(m, "H2", "D2")
- addToMeta(m, "H1", "D1.1")
- exp := map[string]string{"h1": "d1 D1.1", "h2": "D2"}
- checkHeader(t, exp, m.Pairs())
- addToMeta(m, "", "d0")
- checkHeader(t, exp, m.Pairs())
- addToMeta(m, "h3", "")
- exp["h3"] = ""
- checkHeader(t, exp, m.Pairs())
- addToMeta(m, "h3", " ")
- checkHeader(t, exp, m.Pairs())
- addToMeta(m, "h4", " ")
- exp["h4"] = ""
- checkHeader(t, exp, m.Pairs())
-}
-
-func TestDelete(t *testing.T) {
- t.Parallel()
- m := New(testID)
- m.Set("key", "val")
- if got, ok := m.Get("key"); !ok || got != "val" {
- t.Errorf("Value != %q, got: %v/%q", "val", ok, got)
- }
- m.Set("key", "")
- if got, ok := m.Get("key"); !ok || got != "" {
- t.Errorf("Value != %q, got: %v/%q", "", ok, got)
- }
- m.Delete("key")
- if got, ok := m.Get("key"); ok || got != "" {
- t.Errorf("Value != %q, got: %v/%q", "", ok, got)
- }
-}
-
-func TestEqual(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- pairs1, pairs2 []string
- allowComputed bool
- exp bool
- }{
- {nil, nil, true, true},
- {nil, nil, false, true},
- {[]string{"a", "a"}, nil, false, false},
- {[]string{"a", "a"}, nil, true, false},
- {[]string{api.KeyFolge, "0"}, nil, true, false},
- {[]string{api.KeyFolge, "0"}, nil, false, true},
- {[]string{api.KeyFolge, "0"}, []string{api.KeyFolge, "0"}, true, true},
- {[]string{api.KeyFolge, "0"}, []string{api.KeyFolge, "0"}, false, true},
- }
- for i, tc := range testcases {
- m1 := pairs2meta(tc.pairs1)
- m2 := pairs2meta(tc.pairs2)
- got := m1.Equal(m2, tc.allowComputed)
- if tc.exp != got {
- t.Errorf("%d: %v =?= %v: expected=%v, but got=%v", i, tc.pairs1, tc.pairs2, tc.exp, got)
- }
- got = m2.Equal(m1, tc.allowComputed)
- if tc.exp != got {
- t.Errorf("%d: %v =!= %v: expected=%v, but got=%v", i, tc.pairs1, tc.pairs2, tc.exp, got)
- }
- }
-
- // Pathologic cases
- var m1, m2 *Meta
- if !m1.Equal(m2, true) {
- t.Error("Nil metas should be treated equal")
- }
- m1 = New(testID)
- if m1.Equal(m2, true) {
- t.Error("Empty meta should not be equal to nil")
- }
- if m2.Equal(m1, true) {
- t.Error("Nil meta should should not be equal to empty")
- }
- m2 = New(testID + 1)
- if m1.Equal(m2, true) {
- t.Error("Different ID should differentiate")
- }
- if m2.Equal(m1, true) {
- t.Error("Different ID should differentiate")
- }
-}
-
-func pairs2meta(pairs []string) *Meta {
- m := New(testID)
- for i := 0; i < len(pairs); i = i + 2 {
- m.Set(pairs[i], pairs[i+1])
- }
- return m
-}
-
-func TestRemoveNonGraphic(t *testing.T) {
- testCases := []struct {
- inp string
- exp string
- }{
- {"", ""},
- {" ", ""},
- {"a", "a"},
- {"a ", "a"},
- {"a b", "a b"},
- {"\n", ""},
- {"a\n", "a"},
- {"a\nb", "a b"},
- {"a\tb", "a b"},
- }
- for i, tc := range testCases {
- got := RemoveNonGraphic(tc.inp)
- if tc.exp != got {
- t.Errorf("%q/%d: expected %q, but got %q", tc.inp, i, tc.exp, got)
- }
- }
-}
DELETED domain/meta/parse.go
Index: domain/meta/parse.go
==================================================================
--- domain/meta/parse.go
+++ domain/meta/parse.go
@@ -1,178 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta provides the domain specific type 'meta'.
-package meta
-
-import (
- "strings"
-
- "zettelstore.de/c/api"
- "zettelstore.de/c/maps"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/input"
- "zettelstore.de/z/strfun"
-)
-
-// NewFromInput parses the meta data of a zettel.
-func NewFromInput(zid id.Zid, inp *input.Input) *Meta {
- if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' {
- skipToEOL(inp)
- inp.EatEOL()
- }
- meta := New(zid)
- for {
- skipSpace(inp)
- switch inp.Ch {
- case '\r':
- if inp.Peek() == '\n' {
- inp.Next()
- }
- fallthrough
- case '\n':
- inp.Next()
- return meta
- case input.EOS:
- return meta
- case '%':
- skipToEOL(inp)
- inp.EatEOL()
- continue
- }
- parseHeader(meta, inp)
- if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' {
- skipToEOL(inp)
- inp.EatEOL()
- meta.YamlSep = true
- return meta
- }
- }
-}
-
-func parseHeader(m *Meta, inp *input.Input) {
- pos := inp.Pos
- for isHeader(inp.Ch) {
- inp.Next()
- }
- key := inp.Src[pos:inp.Pos]
- skipSpace(inp)
- if inp.Ch == ':' {
- inp.Next()
- }
- var val []byte
- for {
- skipSpace(inp)
- pos = inp.Pos
- skipToEOL(inp)
- val = append(val, inp.Src[pos:inp.Pos]...)
- inp.EatEOL()
- if !input.IsSpace(inp.Ch) {
- break
- }
- val = append(val, ' ')
- }
- addToMeta(m, string(key), string(val))
-}
-
-func skipSpace(inp *input.Input) {
- for input.IsSpace(inp.Ch) {
- inp.Next()
- }
-}
-
-func skipToEOL(inp *input.Input) {
- for {
- switch inp.Ch {
- case '\n', '\r', input.EOS:
- return
- }
- inp.Next()
- }
-}
-
-// Return true iff rune is valid for header key.
-func isHeader(ch rune) bool {
- return ('a' <= ch && ch <= 'z') ||
- ('0' <= ch && ch <= '9') ||
- ch == '-' ||
- ('A' <= ch && ch <= 'Z')
-}
-
-type predValidElem func(string) bool
-
-func addToSet(set strfun.Set, elems []string, useElem predValidElem) {
- for _, s := range elems {
- if len(s) > 0 && useElem(s) {
- set.Set(s)
- }
- }
-}
-
-func addSet(m *Meta, key, val string, useElem predValidElem) {
- newElems := strings.Fields(val)
- oldElems, ok := m.GetList(key)
- if !ok {
- oldElems = nil
- }
-
- set := make(strfun.Set, len(newElems)+len(oldElems))
- addToSet(set, newElems, useElem)
- if len(set) == 0 {
- // Nothing to add. Maybe because of rejected elements.
- return
- }
- addToSet(set, oldElems, useElem)
- m.SetList(key, maps.Keys(set))
-}
-
-func addData(m *Meta, k, v string) {
- if o, ok := m.Get(k); !ok || o == "" {
- m.Set(k, v)
- } else if v != "" {
- m.Set(k, o+" "+v)
- }
-}
-
-func addToMeta(m *Meta, key, val string) {
- v := trimValue(val)
- key = strings.ToLower(key)
- if !KeyIsValid(key) {
- return
- }
- switch key {
- case "", api.KeyID:
- // Empty key and 'id' key will be ignored
- return
- }
-
- switch Type(key) {
- case TypeTagSet:
- addSet(m, key, strings.ToLower(v), func(s string) bool { return s[0] == '#' && len(s) > 1 })
- case TypeWord:
- m.Set(key, strings.ToLower(v))
- case TypeWordSet:
- addSet(m, key, strings.ToLower(v), func(s string) bool { return true })
- case TypeID:
- if _, err := id.Parse(v); err == nil {
- m.Set(key, v)
- }
- case TypeIDSet:
- addSet(m, key, v, func(s string) bool {
- _, err := id.Parse(s)
- return err == nil
- })
- case TypeTimestamp:
- if _, ok := TimeValue(v); ok {
- m.Set(key, v)
- }
- default:
- addData(m, key, v)
- }
-}
DELETED domain/meta/parse_test.go
Index: domain/meta/parse_test.go
==================================================================
--- domain/meta/parse_test.go
+++ domain/meta/parse_test.go
@@ -1,168 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta_test provides tests for the domain specific type 'meta'.
-package meta_test
-
-import (
- "strings"
- "testing"
-
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/meta"
- "zettelstore.de/z/input"
-)
-
-func parseMetaStr(src string) *meta.Meta {
- return meta.NewFromInput(testID, input.NewInput([]byte(src)))
-}
-
-func TestEmpty(t *testing.T) {
- t.Parallel()
- m := parseMetaStr("")
- if got, ok := m.Get(api.KeySyntax); ok || got != "" {
- t.Errorf("Syntax is not %q, but %q", "", got)
- }
- if got, ok := m.GetList(api.KeyTags); ok || len(got) > 0 {
- t.Errorf("Tags are not nil, but %v", got)
- }
-}
-
-func TestTitle(t *testing.T) {
- t.Parallel()
- td := []struct{ s, e string }{
- {api.KeyTitle + ": a title", "a title"},
- {api.KeyTitle + ": a\n\t title", "a title"},
- {api.KeyTitle + ": a\n\t title\r\n x", "a title x"},
- {api.KeyTitle + " AbC", "AbC"},
- {api.KeyTitle + " AbC\n ded", "AbC ded"},
- {api.KeyTitle + ": o\ntitle: p", "o p"},
- {api.KeyTitle + ": O\n\ntitle: P", "O"},
- {api.KeyTitle + ": b\r\ntitle: c", "b c"},
- {api.KeyTitle + ": B\r\n\r\ntitle: C", "B"},
- {api.KeyTitle + ": r\rtitle: q", "r q"},
- {api.KeyTitle + ": R\r\rtitle: Q", "R"},
- }
- for i, tc := range td {
- m := parseMetaStr(tc.s)
- if got, ok := m.Get(api.KeyTitle); !ok || got != tc.e {
- t.Log(m)
- t.Errorf("TC=%d: expected %q, got %q", i, tc.e, got)
- }
- }
-}
-
-func TestTags(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- src string
- exp string
- }{
- {"", ""},
- {api.KeyTags + ":", ""},
- {api.KeyTags + ": c", ""},
- {api.KeyTags + ": #", ""},
- {api.KeyTags + ": #c", "c"},
- {api.KeyTags + ": #c #", "c"},
- {api.KeyTags + ": #c #b", "b c"},
- {api.KeyTags + ": #c # #", "c"},
- {api.KeyTags + ": #c # #b", "b c"},
- }
- for i, tc := range testcases {
- m := parseMetaStr(tc.src)
- tags, found := m.GetTags(api.KeyTags)
- if !found {
- if tc.exp != "" {
- t.Errorf("%d / %q: no %s found", i, tc.src, api.KeyTags)
- }
- continue
- }
- if tc.exp == "" && len(tags) > 0 {
- t.Errorf("%d / %q: expected no %s, but got %v", i, tc.src, api.KeyTags, tags)
- continue
- }
- got := strings.Join(tags, " ")
- if tc.exp != got {
- t.Errorf("%d / %q: expected %q, got: %q", i, tc.src, tc.exp, got)
- }
- }
-}
-
-func TestNewFromInput(t *testing.T) {
- t.Parallel()
- testcases := []struct {
- input string
- exp []meta.Pair
- }{
- {"", []meta.Pair{}},
- {" a:b", []meta.Pair{{"a", "b"}}},
- {"%a:b", []meta.Pair{}},
- {"a:b\r\n\r\nc:d", []meta.Pair{{"a", "b"}}},
- {"a:b\r\n%c:d", []meta.Pair{{"a", "b"}}},
- {"% a:b\r\n c:d", []meta.Pair{{"c", "d"}}},
- {"---\r\na:b\r\n", []meta.Pair{{"a", "b"}}},
- {"---\r\na:b\r\n--\r\nc:d", []meta.Pair{{"a", "b"}, {"c", "d"}}},
- {"---\r\na:b\r\n---\r\nc:d", []meta.Pair{{"a", "b"}}},
- {"---\r\na:b\r\n----\r\nc:d", []meta.Pair{{"a", "b"}}},
- {"new-title:\nnew-url:", []meta.Pair{{"new-title", ""}, {"new-url", ""}}},
- }
- for i, tc := range testcases {
- meta := parseMetaStr(tc.input)
- if got := meta.Pairs(); !equalPairs(tc.exp, got) {
- t.Errorf("TC=%d: expected=%v, got=%v", i, tc.exp, got)
- }
- }
-
- // Test, whether input position is correct.
- inp := input.NewInput([]byte("---\na:b\n---\nX"))
- m := meta.NewFromInput(testID, inp)
- exp := []meta.Pair{{"a", "b"}}
- if got := m.Pairs(); !equalPairs(exp, got) {
- t.Errorf("Expected=%v, got=%v", exp, got)
- }
- expCh := 'X'
- if gotCh := inp.Ch; gotCh != expCh {
- t.Errorf("Expected=%v, got=%v", expCh, gotCh)
- }
-}
-
-func equalPairs(one, two []meta.Pair) bool {
- if len(one) != len(two) {
- return false
- }
- for i := 0; i < len(one); i++ {
- if one[i].Key != two[i].Key || one[i].Value != two[i].Value {
- return false
- }
- }
- return true
-}
-
-func TestPrecursorIDSet(t *testing.T) {
- t.Parallel()
- var testdata = []struct {
- inp string
- exp string
- }{
- {"", ""},
- {"123", ""},
- {"12345678901234", "12345678901234"},
- {"123 12345678901234", "12345678901234"},
- {"12345678901234 123", "12345678901234"},
- {"01234567890123 123 12345678901234", "01234567890123 12345678901234"},
- {"12345678901234 01234567890123", "01234567890123 12345678901234"},
- }
- for i, tc := range testdata {
- m := parseMetaStr(api.KeyPrecursor + ": " + tc.inp)
- if got, ok := m.Get(api.KeyPrecursor); (!ok && tc.exp != "") || tc.exp != got {
- t.Errorf("TC=%d: expected %q, but got %q when parsing %q", i, tc.exp, got, tc.inp)
- }
- }
-}
DELETED domain/meta/type.go
Index: domain/meta/type.go
==================================================================
--- domain/meta/type.go
+++ domain/meta/type.go
@@ -1,208 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta provides the domain specific type 'meta'.
-package meta
-
-import (
- "strconv"
- "strings"
- "sync"
- "time"
-
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
-)
-
-// DescriptionType is a description of a specific key type.
-type DescriptionType struct {
- Name string
- IsSet bool
-}
-
-// String returns the string representation of the given type
-func (t DescriptionType) String() string { return t.Name }
-
-var registeredTypes = make(map[string]*DescriptionType)
-
-func registerType(name string, isSet bool) *DescriptionType {
- if _, ok := registeredTypes[name]; ok {
- panic("Type '" + name + "' already registered")
- }
- t := &DescriptionType{name, isSet}
- registeredTypes[name] = t
- return t
-}
-
-// Supported key types.
-var (
- TypeCredential = registerType(api.MetaCredential, false)
- TypeEmpty = registerType(api.MetaEmpty, false)
- TypeID = registerType(api.MetaID, false)
- TypeIDSet = registerType(api.MetaIDSet, true)
- TypeNumber = registerType(api.MetaNumber, false)
- TypeString = registerType(api.MetaString, false)
- TypeTagSet = registerType(api.MetaTagSet, true)
- TypeTimestamp = registerType(api.MetaTimestamp, false)
- TypeURL = registerType(api.MetaURL, false)
- TypeWord = registerType(api.MetaWord, false)
- TypeWordSet = registerType(api.MetaWordSet, true)
- TypeZettelmarkup = registerType(api.MetaZettelmarkup, false)
-)
-
-// Type returns a type hint for the given key. If no type hint is specified,
-// TypeUnknown is returned.
-func (*Meta) Type(key string) *DescriptionType {
- return Type(key)
-}
-
-var (
- cachedTypedKeys = make(map[string]*DescriptionType)
- mxTypedKey sync.RWMutex
- suffixTypes = map[string]*DescriptionType{
- "-number": TypeNumber,
- "-role": TypeWord,
- "-set": TypeWordSet,
- "-title": TypeZettelmarkup,
- "-url": TypeURL,
- "-zettel": TypeID,
- "-zid": TypeID,
- "-zids": TypeIDSet,
- }
-)
-
-// Type returns a type hint for the given key. If no type hint is specified,
-// TypeEmpty is returned.
-func Type(key string) *DescriptionType {
- if k, ok := registeredKeys[key]; ok {
- return k.Type
- }
- mxTypedKey.RLock()
- k, ok := cachedTypedKeys[key]
- mxTypedKey.RUnlock()
- if ok {
- return k
- }
- for suffix, t := range suffixTypes {
- if strings.HasSuffix(key, suffix) {
- mxTypedKey.Lock()
- defer mxTypedKey.Unlock()
- cachedTypedKeys[key] = t
- return t
- }
- }
- return TypeEmpty
-}
-
-// SetList stores the given string list value under the given key.
-func (m *Meta) SetList(key string, values []string) {
- if key != api.KeyID {
- for i, val := range values {
- values[i] = trimValue(val)
- }
- m.pairs[key] = strings.Join(values, " ")
- }
-}
-
-// SetNow stores the current timestamp under the given key.
-func (m *Meta) SetNow(key string) {
- m.Set(key, time.Now().Local().Format(id.ZidLayout))
-}
-
-// BoolValue returns the value interpreted as a bool.
-func BoolValue(value string) bool {
- if len(value) > 0 {
- switch value[0] {
- case '0', 'f', 'F', 'n', 'N':
- return false
- }
- }
- return true
-}
-
-// GetBool returns the boolean value of the given key.
-func (m *Meta) GetBool(key string) bool {
- if value, ok := m.Get(key); ok {
- return BoolValue(value)
- }
- return false
-}
-
-// TimeValue returns the time value of the given value.
-func TimeValue(value string) (time.Time, bool) {
- if t, err := time.Parse(id.ZidLayout, value); err == nil {
- return t, true
- }
- return time.Time{}, false
-}
-
-// GetTime returns the time value of the given key.
-func (m *Meta) GetTime(key string) (time.Time, bool) {
- if value, ok := m.Get(key); ok {
- return TimeValue(value)
- }
- return time.Time{}, false
-}
-
-// ListFromValue transforms a string value into a list value.
-func ListFromValue(value string) []string {
- return strings.Fields(value)
-}
-
-// GetList retrieves the string list value of a given key. The bool value
-// signals, whether there was a value stored or not.
-func (m *Meta) GetList(key string) ([]string, bool) {
- value, ok := m.Get(key)
- if !ok {
- return nil, false
- }
- return ListFromValue(value), true
-}
-
-// GetTags returns the list of tags as a string list. Each tag does not begin
-// with the '#' character, in contrast to `GetList`.
-func (m *Meta) GetTags(key string) ([]string, bool) {
- tagsValue, ok := m.Get(key)
- if !ok {
- return nil, false
- }
- tags := ListFromValue(strings.ToLower(tagsValue))
- for i, tag := range tags {
- tags[i] = CleanTag(tag)
- }
- return tags, len(tags) > 0
-}
-
-// CleanTag removes the number character ('#') from a tag value and lowercases it.
-func CleanTag(tag string) string {
- if len(tag) > 1 && tag[0] == '#' {
- return tag[1:]
- }
- return tag
-}
-
-// GetListOrNil retrieves the string list value of a given key. If there was
-// nothing stores, a nil list is returned.
-func (m *Meta) GetListOrNil(key string) []string {
- if value, ok := m.GetList(key); ok {
- return value
- }
- return nil
-}
-
-// GetNumber retrieves the numeric value of a given key.
-func (m *Meta) GetNumber(key string, def int64) int64 {
- if value, ok := m.Get(key); ok {
- if num, err := strconv.ParseInt(value, 10, 64); err == nil {
- return num
- }
- }
- return def
-}
DELETED domain/meta/type_test.go
Index: domain/meta/type_test.go
==================================================================
--- domain/meta/type_test.go
+++ domain/meta/type_test.go
@@ -1,65 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 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.
-//-----------------------------------------------------------------------------
-
-// Package meta_test provides tests for the domain specific type 'meta'.
-package meta_test
-
-import (
- "strconv"
- "testing"
- "time"
-
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
-)
-
-func TestNow(t *testing.T) {
- t.Parallel()
- m := meta.New(id.Invalid)
- m.SetNow("key")
- val, ok := m.Get("key")
- if !ok {
- t.Error("Unable to get value of key")
- }
- if len(val) != 14 {
- t.Errorf("Value is not 14 digits long: %q", val)
- }
- if _, err := strconv.ParseInt(val, 10, 64); err != nil {
- t.Errorf("Unable to parse %q as an int64: %v", val, err)
- }
- if _, ok = m.GetTime("key"); !ok {
- t.Errorf("Unable to get time from value %q", val)
- }
-}
-
-func TestGetTime(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- value string
- valid bool
- exp time.Time
- }{
- {"", false, time.Time{}},
- {"1", false, time.Time{}},
- {"00000000000000", false, time.Time{}},
- {"98765432109876", false, time.Time{}},
- {"20201221111905", true, time.Date(2020, time.December, 21, 11, 19, 5, 0, time.UTC)},
- }
- for i, tc := range testCases {
- got, ok := meta.TimeValue(tc.value)
- if ok != tc.valid {
- t.Errorf("%d: parsing of %q should be %v, but got %v", i, tc.value, tc.valid, ok)
- continue
- }
- if got != tc.exp {
- t.Errorf("%d: parsing of %q should return %v, but got %v", i, tc.value, tc.exp, got)
- }
- }
-}
DELETED domain/meta/values.go
Index: domain/meta/values.go
==================================================================
--- domain/meta/values.go
+++ domain/meta/values.go
@@ -1,89 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-package meta
-
-import (
- "fmt"
-
- "zettelstore.de/c/api"
-)
-
-// Visibility enumerates the variations of the 'visibility' meta key.
-type Visibility int
-
-// Supported values for visibility.
-const (
- _ Visibility = iota
- VisibilityUnknown
- VisibilityPublic
- VisibilityCreator
- VisibilityLogin
- VisibilityOwner
- VisibilityExpert
-)
-
-var visMap = map[string]Visibility{
- api.ValueVisibilityPublic: VisibilityPublic,
- api.ValueVisibilityCreator: VisibilityCreator,
- api.ValueVisibilityLogin: VisibilityLogin,
- api.ValueVisibilityOwner: VisibilityOwner,
- api.ValueVisibilityExpert: VisibilityExpert,
-}
-var revVisMap = map[Visibility]string{}
-
-func init() {
- for k, v := range visMap {
- revVisMap[v] = k
- }
-}
-
-// GetVisibility returns the visibility value of the given string
-func GetVisibility(val string) Visibility {
- if vis, ok := visMap[val]; ok {
- return vis
- }
- return VisibilityUnknown
-}
-
-func (v Visibility) String() string {
- if s, ok := revVisMap[v]; ok {
- return s
- }
- return fmt.Sprintf("Unknown (%d)", v)
-}
-
-// UserRole enumerates the supported values of meta key 'user-role'.
-type UserRole int
-
-// Supported values for user roles.
-const (
- _ UserRole = iota
- UserRoleUnknown
- UserRoleCreator
- UserRoleReader
- UserRoleWriter
- UserRoleOwner
-)
-
-var urMap = map[string]UserRole{
- api.ValueUserRoleCreator: UserRoleCreator,
- api.ValueUserRoleReader: UserRoleReader,
- api.ValueUserRoleWriter: UserRoleWriter,
- api.ValueUserRoleOwner: UserRoleOwner,
-}
-
-// GetUserRole role returns the user role of the given string.
-func GetUserRole(val string) UserRole {
- if ur, ok := urMap[val]; ok {
- return ur
- }
- return UserRoleUnknown
-}
DELETED domain/meta/write.go
Index: domain/meta/write.go
==================================================================
--- domain/meta/write.go
+++ domain/meta/write.go
@@ -1,58 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta provides the domain specific type 'meta'.
-package meta
-
-import "io"
-
-// Write writes metadata to a writer, excluding computed and propery values.
-func (m *Meta) Write(w io.Writer) (int, error) {
- return m.doWrite(w, IsComputed)
-}
-
-// WriteComputed writes metadata to a writer, including computed values,
-// but excluding property values.
-func (m *Meta) WriteComputed(w io.Writer) (int, error) {
- return m.doWrite(w, IsProperty)
-}
-
-func (m *Meta) doWrite(w io.Writer, ignoreKeyPred func(string) bool) (length int, err error) {
- for _, p := range m.ComputedPairs() {
- key := p.Key
- if ignoreKeyPred(key) {
- continue
- }
- if err != nil {
- break
- }
- var l int
- l, err = io.WriteString(w, key)
- length += l
- if err == nil {
- l, err = w.Write(colonSpace)
- length += l
- }
- if err == nil {
- l, err = io.WriteString(w, p.Value)
- length += l
- }
- if err == nil {
- l, err = w.Write(newline)
- length += l
- }
- }
- return length, err
-}
-
-var (
- colonSpace = []byte{':', ' '}
- newline = []byte{'\n'}
-)
DELETED domain/meta/write_test.go
Index: domain/meta/write_test.go
==================================================================
--- domain/meta/write_test.go
+++ domain/meta/write_test.go
@@ -1,59 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package meta_test provides tests for the domain specific type 'meta'.
-package meta_test
-
-import (
- "bytes"
- "strings"
- "testing"
-
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
-)
-
-const testID = id.Zid(98765432101234)
-
-func newMeta(title string, tags []string, syntax string) *meta.Meta {
- m := meta.New(testID)
- if title != "" {
- m.Set(api.KeyTitle, title)
- }
- if tags != nil {
- m.Set(api.KeyTags, strings.Join(tags, " "))
- }
- if syntax != "" {
- m.Set(api.KeySyntax, syntax)
- }
- return m
-}
-func assertWriteMeta(t *testing.T, m *meta.Meta, expected string) {
- t.Helper()
- var buf bytes.Buffer
- m.Write(&buf)
- if got := buf.String(); got != expected {
- t.Errorf("\nExp: %q\ngot: %q", expected, got)
- }
-}
-
-func TestWriteMeta(t *testing.T) {
- t.Parallel()
- assertWriteMeta(t, newMeta("", nil, ""), "")
-
- m := newMeta("TITLE", []string{"#t1", "#t2"}, "syntax")
- assertWriteMeta(t, m, "title: TITLE\ntags: #t1 #t2\nsyntax: syntax\n")
-
- m = newMeta("TITLE", nil, "")
- m.Set("user", "zettel")
- m.Set("auth", "basic")
- assertWriteMeta(t, m, "title: TITLE\nauth: basic\nuser: zettel\n")
-}
DELETED domain/zettel.go
Index: domain/zettel.go
==================================================================
--- domain/zettel.go
+++ domain/zettel.go
@@ -1,29 +0,0 @@
-//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 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.
-//-----------------------------------------------------------------------------
-
-// Package domain provides domain specific types, constants, and functions.
-package domain
-
-import "zettelstore.de/z/domain/meta"
-
-// Zettel is the main data object of a zettelstore.
-type Zettel struct {
- Meta *meta.Meta // Some additional meta-data.
- Content Content // The content of the zettel itself.
-}
-
-// Length returns the number of bytes to store the zettel (in a domain view,
-// not in a technical view).
-func (z Zettel) Length() int { return z.Meta.Length() + z.Content.Length() }
-
-// Equal compares two zettel for equality.
-func (z Zettel) Equal(o Zettel, allowComputed bool) bool {
- return z.Meta.Equal(o.Meta, allowComputed) && z.Content.Equal(&o.Content)
-}
Index: encoder/encoder.go
==================================================================
--- encoder/encoder.go
+++ encoder/encoder.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// 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
@@ -15,13 +15,13 @@
import (
"errors"
"fmt"
"io"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/ast"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/meta"
)
// Encoder is an interface that allows to encode different parts of a zettel.
type Encoder interface {
WriteZettel(io.Writer, *ast.ZettelNode, EvalMetaFunc) (int, error)
@@ -71,13 +71,5 @@
for enc := range registry {
result = append(result, enc)
}
return result
}
-
-// GetDefaultEncoding returns the encoding that should be used as default.
-func GetDefaultEncoding() api.EncodingEnum {
- if _, ok := registry[api.EncoderZJSON]; ok {
- return api.EncoderZJSON
- }
- panic("No ZJSON encoding registered")
-}
Index: encoder/encoder_blob_test.go
==================================================================
--- encoder/encoder_blob_test.go
+++ encoder/encoder_blob_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// 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
@@ -11,16 +11,16 @@
package encoder_test
import (
"testing"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/input"
"zettelstore.de/z/parser"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
_ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser.
)
type blobTestCase struct {
@@ -38,15 +38,15 @@
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{
- encoderZJSON: `[{"":"BLOB","q":"PNG","s":"png","o":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="}]`,
- encoderHTML: `

`,
- encoderSexpr: `((BLOB "PNG" "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="))`,
+ encoderHTML: `
`,
+ encoderSz: `(BLOCK (BLOB (INLINE (TEXT "PNG")) "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="))`,
+ encoderSHTML: `((p (img (@ (alt . "PNG") (src . "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==")))))`,
encoderText: "",
- encoderZmk: `%% Unable to display BLOB with title 'PNG' and syntax 'png'.`,
+ encoderZmk: `%% Unable to display BLOB with description 'PNG' and syntax 'png'.`,
},
},
}
func TestBlob(t *testing.T) {
Index: encoder/encoder_block_test.go
==================================================================
--- encoder/encoder_block_test.go
+++ encoder/encoder_block_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// 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
@@ -13,145 +13,182 @@
var tcsBlock = []zmkTestCase{
{
descr: "Empty Zettelmarkup should produce near nothing",
zmk: "",
expect: expectMap{
- encoderZJSON: `[]`,
encoderHTML: "",
- encoderSexpr: `()`,
+ encoderMD: "",
+ encoderSz: `(BLOCK)`,
+ encoderSHTML: `()`,
encoderText: "",
encoderZmk: useZmk,
},
},
{
descr: "Simple text: Hello, world",
zmk: "Hello, world",
expect: expectMap{
- encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Hello,"},{"":"Space"},{"":"Text","s":"world"}]}]`,
encoderHTML: "Hello, world
",
- encoderSexpr: `((PARA (TEXT "Hello,") (SPACE) (TEXT "world")))`,
+ encoderMD: "Hello, world",
+ encoderSz: `(BLOCK (PARA (TEXT "Hello,") (SPACE) (TEXT "world")))`,
+ encoderSHTML: `((p "Hello," " " "world"))`,
encoderText: "Hello, world",
encoderZmk: useZmk,
},
},
{
descr: "Simple block comment",
zmk: "%%%\nNo\nrender\n%%%",
expect: expectMap{
- encoderZJSON: `[{"":"CommentBlock","s":"No\nrender"}]`,
encoderHTML: ``,
- encoderSexpr: `((VERBATIM-COMMENT () "No\nrender"))`,
+ encoderMD: "",
+ encoderSz: `(BLOCK (VERBATIM-COMMENT () "No\nrender"))`,
+ encoderSHTML: `(())`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Rendered block comment",
zmk: "%%%{-}\nRender\n%%%",
expect: expectMap{
- encoderZJSON: `[{"":"CommentBlock","a":{"-":""},"s":"Render"}]`,
- encoderHTML: "",
- encoderSexpr: `((VERBATIM-COMMENT (("-" "")) "Render"))`,
+ encoderHTML: "\n",
+ encoderMD: "",
+ encoderSz: `(BLOCK (VERBATIM-COMMENT (quote (("-" . ""))) "Render"))`,
+ encoderSHTML: "((@@@ \"Render\"))",
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Simple Heading",
zmk: `=== Top`,
expect: expectMap{
- encoderZJSON: `[{"":"Heading","n":1,"s":"top","i":[{"":"Text","s":"Top"}]}]`,
encoderHTML: "Top
",
- encoderSexpr: `((HEADING 1 () "top" "top" (TEXT "Top")))`,
+ encoderMD: "# Top",
+ encoderSz: `(BLOCK (HEADING 1 () "top" "top" (INLINE (TEXT "Top"))))`,
+ encoderSHTML: `((h2 (@ (id . "top")) "Top"))`,
encoderText: `Top`,
encoderZmk: useZmk,
},
},
{
descr: "Simple List",
zmk: "* A\n* B\n* C",
expect: expectMap{
- encoderZJSON: `[{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"A"}]}],[{"":"Para","i":[{"":"Text","s":"B"}]}],[{"":"Para","i":[{"":"Text","s":"C"}]}]]}]`,
encoderHTML: "",
- encoderSexpr: `((UNORDERED ((TEXT "A")) ((TEXT "B")) ((TEXT "C"))))`,
+ encoderMD: "* A\n* B\n* C",
+ encoderSz: `(BLOCK (UNORDERED (INLINE (TEXT "A")) (INLINE (TEXT "B")) (INLINE (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{
- encoderZJSON: `[{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"T1"}]},{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"T2"}]}]]}],[{"":"Para","i":[{"":"Text","s":"T3"}]},{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"T4"}]}],[{"":"Para","i":[{"":"Text","s":"T5"}]}]]}],[{"":"Para","i":[{"":"Text","s":"T6"}]}]]}]`,
encoderHTML: ``,
- encoderSexpr: `((UNORDERED ((PARA (TEXT "T1")) (UNORDERED ((TEXT "T2")))) ((PARA (TEXT "T3")) (UNORDERED ((TEXT "T4")) ((TEXT "T5")))) ((PARA (TEXT "T6")))))`,
+ encoderMD: "* T1\n * T2\n* T3\n * T4\n * T5\n* T6",
+ encoderSz: `(BLOCK (UNORDERED (BLOCK (PARA (TEXT "T1")) (UNORDERED (INLINE (TEXT "T2")))) (BLOCK (PARA (TEXT "T3")) (UNORDERED (INLINE (TEXT "T4")) (INLINE (TEXT "T5")))) (BLOCK (PARA (TEXT "T6")))))`,
+ encoderSHTML: `((ul (li (p "T1") (ul (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{
- encoderZJSON: `[{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"Item1.1"}]}],[{"":"Para","i":[{"":"Text","s":"Item1.2"}]}],[{"":"Para","i":[{"":"Text","s":"Item1.3"}]}],[{"":"Para","i":[{"":"Text","s":"Item2.1"}]}],[{"":"Para","i":[{"":"Text","s":"Item2.2"}]}]]}]`,
encoderHTML: "- Item1.1
- Item1.2
- Item1.3
- Item2.1
- Item2.2
",
- encoderSexpr: `((UNORDERED ((TEXT "Item1.1")) ((TEXT "Item1.2")) ((TEXT "Item1.3")) ((TEXT "Item2.1")) ((TEXT "Item2.2"))))`,
+ encoderMD: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2",
+ encoderSz: `(BLOCK (UNORDERED (INLINE (TEXT "Item1.1")) (INLINE (TEXT "Item1.2")) (INLINE (TEXT "Item1.3")) (INLINE (TEXT "Item2.1")) (INLINE (TEXT "Item2.2"))))`,
+ 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",
zmk: `---`,
expect: expectMap{
- encoderZJSON: `[{"":"Thematic"}]`,
encoderHTML: "
",
- encoderSexpr: `((THEMATIC ()))`,
+ encoderMD: "---",
+ encoderSz: `(BLOCK (THEMATIC ()))`,
+ encoderSHTML: `((hr))`,
+ encoderText: ``,
+ encoderZmk: useZmk,
+ },
+ },
+ {
+ descr: "Thematic break with attribute",
+ zmk: `---{lang="zmk"}`,
+ expect: expectMap{
+ encoderHTML: `
`,
+ encoderMD: "---",
+ encoderSz: `(BLOCK (THEMATIC (quote (("lang" . "zmk")))))`,
+ encoderSHTML: `((hr (@ (lang . "zmk"))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "No list after paragraph",
zmk: "Text\n*abc",
expect: expectMap{
- encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"},{"":"Soft"},{"":"Text","s":"*abc"}]}]`,
encoderHTML: "Text *abc
",
- encoderSexpr: `((PARA (TEXT "Text") (SOFT) (TEXT "*abc")))`,
+ encoderMD: "Text\n*abc",
+ encoderSz: `(BLOCK (PARA (TEXT "Text") (SOFT) (TEXT "*abc")))`,
+ encoderSHTML: `((p "Text" " " "*abc"))`,
encoderText: `Text *abc`,
encoderZmk: useZmk,
},
},
{
descr: "A list after paragraph",
zmk: "Text\n# abc",
expect: expectMap{
- encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"}]},{"":"Ordered","c":[[{"":"Para","i":[{"":"Text","s":"abc"}]}]]}]`,
encoderHTML: "Text
- abc
",
- encoderSexpr: `((PARA (TEXT "Text")) (ORDERED ((TEXT "abc"))))`,
+ encoderMD: "Text\n\n1. abc",
+ encoderSz: `(BLOCK (PARA (TEXT "Text")) (ORDERED (INLINE (TEXT "abc"))))`,
+ encoderSHTML: `((p "Text") (ol (li "abc")))`,
encoderText: "Text\nabc",
encoderZmk: useZmk,
},
},
+ {
+ descr: "Simple List Quote",
+ zmk: "> ToBeOrNotToBe",
+ expect: expectMap{
+ encoderHTML: "ToBeOrNotToBe
",
+ encoderMD: "> ToBeOrNotToBe",
+ encoderSz: `(BLOCK (QUOTATION (INLINE (TEXT "ToBeOrNotToBe"))))`,
+ encoderSHTML: `((blockquote (p "ToBeOrNotToBe")))`,
+ encoderText: "ToBeOrNotToBe",
+ encoderZmk: useZmk,
+ },
+ },
{
descr: "Simple Quote Block",
zmk: "<<<\nToBeOrNotToBe\n<<< Romeo",
expect: expectMap{
- encoderZJSON: `[{"":"Excerpt","b":[{"":"Para","i":[{"":"Text","s":"ToBeOrNotToBe"}]}],"i":[{"":"Text","s":"Romeo"}]}]`,
encoderHTML: "ToBeOrNotToBe
Romeo
",
- encoderSexpr: `((REGION-QUOTE () ((PARA (TEXT "ToBeOrNotToBe"))) ((TEXT "Romeo"))))`,
+ encoderMD: "> ToBeOrNotToBe",
+ encoderSz: `(BLOCK (REGION-QUOTE () (BLOCK (PARA (TEXT "ToBeOrNotToBe"))) (INLINE (TEXT "Romeo"))))`,
+ encoderSHTML: `((blockquote (p "ToBeOrNotToBe") (cite "Romeo")))`,
encoderText: "ToBeOrNotToBe\nRomeo",
encoderZmk: useZmk,
},
},
{
descr: "Quote Block with multiple paragraphs",
zmk: "<<<\nToBeOr\n\nNotToBe\n<<< Romeo",
expect: expectMap{
- encoderZJSON: `[{"":"Excerpt","b":[{"":"Para","i":[{"":"Text","s":"ToBeOr"}]},{"":"Para","i":[{"":"Text","s":"NotToBe"}]}],"i":[{"":"Text","s":"Romeo"}]}]`,
encoderHTML: "ToBeOr
NotToBe
Romeo
",
- encoderSexpr: `((REGION-QUOTE () ((PARA (TEXT "ToBeOr")) (PARA (TEXT "NotToBe"))) ((TEXT "Romeo"))))`,
+ encoderMD: "> ToBeOr\n\n> NotToBe",
+ encoderSz: `(BLOCK (REGION-QUOTE () (BLOCK (PARA (TEXT "ToBeOr")) (PARA (TEXT "NotToBe"))) (INLINE (TEXT "Romeo"))))`,
+ encoderSHTML: `((blockquote (p "ToBeOr") (p "NotToBe") (cite "Romeo")))`,
encoderText: "ToBeOr\nNotToBe\nRomeo",
encoderZmk: useZmk,
},
},
{
@@ -164,13 +201,14 @@
Paragraph
Spacy Para
""" Author`,
expect: expectMap{
- encoderZJSON: "[{\"\":\"Poem\",\"b\":[{\"\":\"Para\",\"i\":[{\"\":\"Text\",\"s\":\"A\"},{\"\":\"Space\",\"s\":\"\u00a0\"},{\"\":\"Text\",\"s\":\"line\"},{\"\":\"Hard\"},{\"\":\"Space\",\"s\":\"\u00a0\u00a0\"},{\"\":\"Text\",\"s\":\"another\"},{\"\":\"Space\",\"s\":\"\u00a0\"},{\"\":\"Text\",\"s\":\"line\"},{\"\":\"Hard\"},{\"\":\"Text\",\"s\":\"Back\"}]},{\"\":\"Para\",\"i\":[{\"\":\"Text\",\"s\":\"Paragraph\"}]},{\"\":\"Para\",\"i\":[{\"\":\"Space\",\"s\":\"\u00a0\u00a0\u00a0\u00a0\"},{\"\":\"Text\",\"s\":\"Spacy\"},{\"\":\"Space\",\"s\":\"\u00a0\u00a0\"},{\"\":\"Text\",\"s\":\"Para\"}]}],\"i\":[{\"\":\"Text\",\"s\":\"Author\"}]}]",
encoderHTML: "A\u00a0line
\u00a0\u00a0another\u00a0line
Back
Paragraph
\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para
Author ",
- encoderSexpr: "((REGION-VERSE () ((PARA (TEXT \"A\") (SPACE \"\u00a0\") (TEXT \"line\") (HARD) (SPACE \"\u00a0\u00a0\") (TEXT \"another\") (SPACE \"\u00a0\") (TEXT \"line\") (HARD) (TEXT \"Back\")) (PARA (TEXT \"Paragraph\")) (PARA (SPACE \"\u00a0\u00a0\u00a0\u00a0\") (TEXT \"Spacy\") (SPACE \"\u00a0\u00a0\") (TEXT \"Para\"))) ((TEXT \"Author\"))))",
+ encoderMD: "",
+ encoderSz: "(BLOCK (REGION-VERSE () (BLOCK (PARA (TEXT \"A\") (SPACE \"\u00a0\") (TEXT \"line\") (HARD) (SPACE \"\u00a0\u00a0\") (TEXT \"another\") (SPACE \"\u00a0\") (TEXT \"line\") (HARD) (TEXT \"Back\")) (PARA (TEXT \"Paragraph\")) (PARA (SPACE \"\u00a0\u00a0\u00a0\u00a0\") (TEXT \"Spacy\") (SPACE \"\u00a0\u00a0\") (TEXT \"Para\"))) (INLINE (TEXT \"Author\"))))",
+ encoderSHTML: "((div (p \"A\" \"\u00a0\" \"line\" (br) \"\u00a0\u00a0\" \"another\" \"\u00a0\" \"line\" (br) \"Back\") (p \"Paragraph\") (p \"\u00a0\u00a0\u00a0\u00a0\" \"Spacy\" \"\u00a0\u00a0\" \"Para\") (cite \"Author\")))",
encoderText: "A line\n another line\nBack\nParagraph\n Spacy Para\nAuthor",
encoderZmk: "\"\"\"\nA\u00a0line\\\n\u00a0\u00a0another\u00a0line\\\nBack\nParagraph\n\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para\n\"\"\" Author",
},
},
{
@@ -179,79 +217,98 @@
A simple
span
and much more
:::`,
expect: expectMap{
- encoderZJSON: `[{"":"Block","b":[{"":"Para","i":[{"":"Text","s":"A"},{"":"Space"},{"":"Text","s":"simple"},{"":"Soft"},{"":"Space"},{"":"Text","s":"span"},{"":"Soft"},{"":"Text","s":"and"},{"":"Space"},{"":"Text","s":"much"},{"":"Space"},{"":"Text","s":"more"}]}]}]`,
encoderHTML: "A simple span and much more
",
- encoderSexpr: `((REGION-BLOCK () ((PARA (TEXT "A") (SPACE) (TEXT "simple") (SOFT) (SPACE) (TEXT "span") (SOFT) (TEXT "and") (SPACE) (TEXT "much") (SPACE) (TEXT "more"))) ()))`,
+ encoderMD: "",
+ encoderSz: `(BLOCK (REGION-BLOCK () (BLOCK (PARA (TEXT "A") (SPACE) (TEXT "simple") (SOFT) (SPACE) (TEXT "span") (SOFT) (TEXT "and") (SPACE) (TEXT "much") (SPACE) (TEXT "more"))) (INLINE)))`,
+ encoderSHTML: `((div (p "A" " " "simple" " " " " "span" " " "and" " " "much" " " "more")))`,
encoderText: `A simple span and much more`,
encoderZmk: useZmk,
},
},
{
descr: "Simple Verbatim Code",
zmk: "```\nHello\nWorld\n```",
expect: expectMap{
- encoderZJSON: `[{"":"CodeBlock","s":"Hello\nWorld"}]`,
encoderHTML: "Hello\nWorld
",
- encoderSexpr: `((VERBATIM-CODE () "Hello\nWorld"))`,
+ encoderMD: " Hello\n World",
+ encoderSz: `(BLOCK (VERBATIM-CODE () "Hello\nWorld"))`,
+ encoderSHTML: `((pre (code "Hello\nWorld")))`,
encoderText: "Hello\nWorld",
encoderZmk: useZmk,
},
},
{
descr: "Simple Verbatim Code with visible spaces",
zmk: "```{-}\nHello World\n```",
expect: expectMap{
- encoderZJSON: `[{"":"CodeBlock","a":{"-":""},"s":"Hello World"}]`,
encoderHTML: "Hello\u2423World
",
- encoderSexpr: `((VERBATIM-CODE (("-" "")) "Hello World"))`,
+ encoderMD: " Hello World",
+ encoderSz: `(BLOCK (VERBATIM-CODE (quote (("-" . ""))) "Hello World"))`,
+ encoderSHTML: "((pre (code \"Hello\u2423World\")))",
encoderText: "Hello World",
encoderZmk: useZmk,
},
},
{
descr: "Simple Verbatim Eval",
zmk: "~~~\nHello\nWorld\n~~~",
expect: expectMap{
- encoderZJSON: `[{"":"EvalBlock","s":"Hello\nWorld"}]`,
encoderHTML: "Hello\nWorld
",
- encoderSexpr: `((VERBATIM-EVAL () "Hello\nWorld"))`,
+ 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{
- encoderZJSON: `[{"":"MathBlock","s":"Hello\n\\LaTeX"}]`,
encoderHTML: "Hello\n\\LaTeX
",
- encoderSexpr: `((VERBATIM-MATH () "Hello\n\\LaTeX"))`,
+ 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",
expect: expectMap{
- encoderZJSON: `[{"":"Description","d":[{"i":[{"":"Text","s":"Zettel"}],"e":[[{"":"Para","i":[{"":"Text","s":"Paper"}]}],[{"":"Para","i":[{"":"Text","s":"Note"}]}]]},{"i":[{"":"Text","s":"Zettelkasten"}],"e":[[{"":"Para","i":[{"":"Text","s":"Slip"},{"":"Space"},{"":"Text","s":"box"}]}]]}]}]`,
- encoderHTML: "- Zettel
- Paper
- Note
- Zettelkasten
- Slip box
",
- encoderSexpr: `((DESCRIPTION ((TEXT "Zettel")) (((TEXT "Paper")) ((TEXT "Note"))) ((TEXT "Zettelkasten")) (((TEXT "Slip") (SPACE) (TEXT "box")))))`,
+ encoderHTML: "- Zettel
Paper
Note
- Zettelkasten
Slip box
",
+ encoderMD: "",
+ encoderSz: `(BLOCK (DESCRIPTION (INLINE (TEXT "Zettel")) (BLOCK (BLOCK (PARA (TEXT "Paper"))) (BLOCK (PARA (TEXT "Note")))) (INLINE (TEXT "Zettelkasten")) (BLOCK (BLOCK (PARA (TEXT "Slip") (SPACE) (TEXT "box"))))))`,
+ encoderSHTML: `((dl (dt "Zettel") (dd (p "Paper")) (dd (p "Note")) (dt "Zettelkasten") (dd (p "Slip" " " "box"))))`,
+ encoderText: "Zettel\nPaper\nNote\nZettelkasten\nSlip box",
+ encoderZmk: useZmk,
+ },
+ },
+ {
+ descr: "Description List with paragraphs as item",
+ zmk: "; Zettel\n: Paper\n\n Note\n; Zettelkasten\n: Slip box",
+ expect: expectMap{
+ encoderHTML: "- Zettel
Paper
Note
- Zettelkasten
Slip box
",
+ encoderMD: "",
+ encoderSz: `(BLOCK (DESCRIPTION (INLINE (TEXT "Zettel")) (BLOCK (BLOCK (PARA (TEXT "Paper")) (PARA (TEXT "Note")))) (INLINE (TEXT "Zettelkasten")) (BLOCK (BLOCK (PARA (TEXT "Slip") (SPACE) (TEXT "box"))))))`,
+ encoderSHTML: `((dl (dt "Zettel") (dd (p "Paper") (p "Note")) (dt "Zettelkasten") (dd (p "Slip" " " "box"))))`,
encoderText: "Zettel\nPaper\nNote\nZettelkasten\nSlip box",
encoderZmk: useZmk,
},
},
{
descr: "Simple Table",
zmk: "|c1|c2|c3\n|d1||d3",
expect: expectMap{
- encoderZJSON: `[{"":"Table","p":[[],[[{"i":[{"":"Text","s":"c1"}]},{"i":[{"":"Text","s":"c2"}]},{"i":[{"":"Text","s":"c3"}]}],[{"i":[{"":"Text","s":"d1"}]},{"i":[]},{"i":[{"":"Text","s":"d3"}]}]]]}]`,
encoderHTML: ``,
- encoderSexpr: `((TABLE () ((CELL (TEXT "c1")) (CELL (TEXT "c2")) (CELL (TEXT "c3"))) ((CELL (TEXT "d1")) (CELL) (CELL (TEXT "d3")))))`,
+ encoderMD: "",
+ encoderSz: `(BLOCK (TABLE () (list (CELL (TEXT "c1")) (CELL (TEXT "c2")) (CELL (TEXT "c3"))) (list (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,
},
},
{
@@ -259,52 +316,78 @@
zmk: `|h1>|=h2|h3:|
|%--+---+---+
|","i":[{"":"Text","s":"h1"}]},{"i":[{"":"Text","s":"h2"}]},{"s":":","i":[{"":"Text","s":"h3"}]}],[[{"s":"<","i":[{"":"Text","s":"c1"}]},{"i":[{"":"Text","s":"c2"}]},{"s":":","i":[{"":"Text","s":"c3"}]}],[{"s":">","i":[{"":"Text","s":"f1"}]},{"i":[{"":"Text","s":"f2"}]},{"s":":","i":[{"":"Text","s":"=f3"}]}]]]}]`,
encoderHTML: ``,
- encoderSexpr: `((TABLE ((CELL-RIGHT (TEXT "h1")) (CELL (TEXT "h2")) (CELL-CENTER (TEXT "h3"))) ((CELL-LEFT (TEXT "c1")) (CELL (TEXT "c2")) (CELL-CENTER (TEXT "c3"))) ((CELL-RIGHT (TEXT "f1")) (CELL (TEXT "f2")) (CELL-CENTER (TEXT "=f3")))))`,
+ encoderMD: "",
+ encoderSz: `(BLOCK (TABLE (list (CELL-RIGHT (TEXT "h1")) (CELL (TEXT "h2")) (CELL-CENTER (TEXT "h3"))) (list (CELL-LEFT (TEXT "c1")) (CELL (TEXT "c2")) (CELL-CENTER (TEXT "c3"))) (list (CELL-RIGHT (TEXT "f1")) (CELL (TEXT "f2")) (CELL-CENTER (TEXT "=f3")))))`,
+ encoderSHTML: `((table (thead (tr (td (@ (class . "right")) "h1") (td "h2") (td (@ (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:
|Text1- Endnote \u21a9\ufe0e
",
+ encoderMD: "Text",
+ encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (quote (INLINE (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{
- encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"},{"":"Footnote","i":[{"":"Text","s":"Footnote"}]}]}]`,
- encoderHTML: `Text1
- Footnote ↩︎
`,
- encoderSexpr: `((PARA (TEXT "Text") (FOOTNOTE () (TEXT "Footnote"))))`,
- encoderText: "Text Footnote",
+ encoderHTML: "Text1
- Endnote2 \u21a9\ufe0e
- Nested \u21a9\ufe0e
",
+ encoderMD: "Text",
+ encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (quote (INLINE (TEXT "Endnote") (ENDNOTE () (quote (INLINE (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{
- encoderZJSON: `[{"":"Transclude","a":{"width":"100px"},"q":"external","s":"http://example.com/image"}]`,
encoderHTML: `
`,
- encoderSexpr: `((TRANSCLUDE (("width" "100px")) (EXTERNAL "http://example.com/image")))`,
+ encoderMD: "",
+ encoderSz: `(BLOCK (TRANSCLUDE (quote (("width" . "100px"))) (quote (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: "",
zmk: ``,
expect: expectMap{
- encoderZJSON: `[]`,
encoderHTML: ``,
- encoderSexpr: `()`,
+ encoderSz: `(BLOCK)`,
+ encoderSHTML: `()`,
encoderText: "",
encoderZmk: useZmk,
},
},
}
// func TestEncoderBlock(t *testing.T) {
// executeTestCases(t, tcsBlock)
// }
Index: encoder/encoder_inline_test.go
==================================================================
--- encoder/encoder_inline_test.go
+++ encoder/encoder_inline_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// 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
@@ -13,497 +13,601 @@
var tcsInline = []zmkTestCase{
{
descr: "Empty Zettelmarkup should produce near nothing (inline)",
zmk: "",
expect: expectMap{
- encoderZJSON: `[]`,
encoderHTML: "",
- encoderSexpr: `()`,
+ encoderMD: "",
+ encoderSz: `(INLINE)`,
+ encoderSHTML: `()`,
encoderText: "",
encoderZmk: useZmk,
},
},
{
descr: "Simple text: Hello, world (inline)",
zmk: `Hello, world`,
expect: expectMap{
- encoderZJSON: `[{"":"Text","s":"Hello,"},{"":"Space"},{"":"Text","s":"world"}]`,
encoderHTML: "Hello, world",
- encoderSexpr: `((TEXT "Hello,") (SPACE) (TEXT "world"))`,
+ encoderMD: "Hello, world",
+ encoderSz: `(INLINE (TEXT "Hello,") (SPACE) (TEXT "world"))`,
+ encoderSHTML: `("Hello," " " "world")`,
encoderText: "Hello, world",
encoderZmk: useZmk,
},
},
+ {
+ descr: "Soft Break",
+ zmk: "soft\nbreak",
+ expect: expectMap{
+ encoderHTML: "soft break",
+ encoderMD: "soft\nbreak",
+ encoderSz: `(INLINE (TEXT "soft") (SOFT) (TEXT "break"))`,
+ encoderSHTML: `("soft" " " "break")`,
+ encoderText: "soft break",
+ encoderZmk: useZmk,
+ },
+ },
+ {
+ descr: "Hard Break",
+ zmk: "hard\\\nbreak",
+ expect: expectMap{
+ encoderHTML: "hard
break",
+ encoderMD: "hard\\\nbreak",
+ encoderSz: `(INLINE (TEXT "hard") (HARD) (TEXT "break"))`,
+ encoderSHTML: `("hard" (br) "break")`,
+ encoderText: "hard\nbreak",
+ encoderZmk: useZmk,
+ },
+ },
{
descr: "Emphasized formatting",
zmk: "__emph__",
expect: expectMap{
- encoderZJSON: `[{"":"Emph","i":[{"":"Text","s":"emph"}]}]`,
encoderHTML: "emph",
- encoderSexpr: `((FORMAT-EMPH () (TEXT "emph")))`,
+ encoderMD: "*emph*",
+ encoderSz: `(INLINE (FORMAT-EMPH () (TEXT "emph")))`,
+ encoderSHTML: `((em "emph"))`,
encoderText: "emph",
encoderZmk: useZmk,
},
},
{
descr: "Strong formatting",
zmk: "**strong**",
expect: expectMap{
- encoderZJSON: `[{"":"Strong","i":[{"":"Text","s":"strong"}]}]`,
encoderHTML: "strong",
- encoderSexpr: `((FORMAT-STRONG () (TEXT "strong")))`,
+ encoderMD: "__strong__",
+ encoderSz: `(INLINE (FORMAT-STRONG () (TEXT "strong")))`,
+ encoderSHTML: `((strong "strong"))`,
encoderText: "strong",
encoderZmk: useZmk,
},
},
{
descr: "Insert formatting",
zmk: ">>insert>>",
expect: expectMap{
- encoderZJSON: `[{"":"Insert","i":[{"":"Text","s":"insert"}]}]`,
encoderHTML: "insert",
- encoderSexpr: `((FORMAT-INSERT () (TEXT "insert")))`,
+ encoderMD: "insert",
+ encoderSz: `(INLINE (FORMAT-INSERT () (TEXT "insert")))`,
+ encoderSHTML: `((ins "insert"))`,
encoderText: "insert",
encoderZmk: useZmk,
},
},
{
descr: "Delete formatting",
zmk: "~~delete~~",
expect: expectMap{
- encoderZJSON: `[{"":"Delete","i":[{"":"Text","s":"delete"}]}]`,
encoderHTML: "delete",
- encoderSexpr: `((FORMAT-DELETE () (TEXT "delete")))`,
+ encoderMD: "delete",
+ encoderSz: `(INLINE (FORMAT-DELETE () (TEXT "delete")))`,
+ encoderSHTML: `((del "delete"))`,
encoderText: "delete",
encoderZmk: useZmk,
},
},
{
descr: "Update formatting",
zmk: "~~old~~>>new>>",
expect: expectMap{
- encoderZJSON: `[{"":"Delete","i":[{"":"Text","s":"old"}]},{"":"Insert","i":[{"":"Text","s":"new"}]}]`,
encoderHTML: "oldnew",
- encoderSexpr: `((FORMAT-DELETE () (TEXT "old")) (FORMAT-INSERT () (TEXT "new")))`,
+ encoderMD: "oldnew",
+ encoderSz: `(INLINE (FORMAT-DELETE () (TEXT "old")) (FORMAT-INSERT () (TEXT "new")))`,
+ encoderSHTML: `((del "old") (ins "new"))`,
encoderText: "oldnew",
encoderZmk: useZmk,
},
},
{
descr: "Superscript formatting",
zmk: "^^superscript^^",
expect: expectMap{
- encoderZJSON: `[{"":"Super","i":[{"":"Text","s":"superscript"}]}]`,
encoderHTML: `superscript`,
- encoderSexpr: `((FORMAT-SUPER () (TEXT "superscript")))`,
+ encoderMD: "superscript",
+ encoderSz: `(INLINE (FORMAT-SUPER () (TEXT "superscript")))`,
+ encoderSHTML: `((sup "superscript"))`,
encoderText: `superscript`,
encoderZmk: useZmk,
},
},
{
descr: "Subscript formatting",
zmk: ",,subscript,,",
expect: expectMap{
- encoderZJSON: `[{"":"Sub","i":[{"":"Text","s":"subscript"}]}]`,
encoderHTML: `subscript`,
- encoderSexpr: `((FORMAT-SUB () (TEXT "subscript")))`,
+ encoderMD: "subscript",
+ encoderSz: `(INLINE (FORMAT-SUB () (TEXT "subscript")))`,
+ encoderSHTML: `((sub "subscript"))`,
encoderText: `subscript`,
encoderZmk: useZmk,
},
},
{
descr: "Quotes formatting",
zmk: `""quotes""`,
expect: expectMap{
- encoderZJSON: `[{"":"Quote","i":[{"":"Text","s":"quotes"}]}]`,
encoderHTML: "quotes
",
- encoderSexpr: `((FORMAT-QUOTE () (TEXT "quotes")))`,
+ encoderMD: "quotes
",
+ encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "quotes")))`,
+ encoderSHTML: `((q "quotes"))`,
encoderText: `quotes`,
encoderZmk: useZmk,
},
},
{
descr: "Quotes formatting (german)",
zmk: `""quotes""{lang=de}`,
expect: expectMap{
- encoderZJSON: `[{"":"Quote","a":{"lang":"de"},"i":[{"":"Text","s":"quotes"}]}]`,
encoderHTML: `quotes
`,
- encoderSexpr: `((FORMAT-QUOTE (("lang" "de")) (TEXT "quotes")))`,
+ encoderMD: "quotes
",
+ encoderSz: `(INLINE (FORMAT-QUOTE (quote (("lang" . "de"))) (TEXT "quotes")))`,
+ encoderSHTML: `((span (@ (lang . "de")) (q "quotes")))`,
encoderText: `quotes`,
encoderZmk: `""quotes""{lang="de"}`,
},
},
{
descr: "Span formatting",
zmk: `::span::`,
expect: expectMap{
- encoderZJSON: `[{"":"Span","i":[{"":"Text","s":"span"}]}]`,
encoderHTML: `span`,
- encoderSexpr: `((FORMAT-SPAN () (TEXT "span")))`,
+ encoderMD: "span",
+ encoderSz: `(INLINE (FORMAT-SPAN () (TEXT "span")))`,
+ encoderSHTML: `((span "span"))`,
encoderText: `span`,
encoderZmk: useZmk,
},
},
{
descr: "Code formatting",
zmk: "``code``",
expect: expectMap{
- encoderZJSON: `[{"":"Code","s":"code"}]`,
encoderHTML: `code
`,
- encoderSexpr: `((LITERAL-CODE () "code"))`,
+ encoderMD: "`code`",
+ encoderSz: `(INLINE (LITERAL-CODE () "code"))`,
+ encoderSHTML: `((code "code"))`,
encoderText: `code`,
encoderZmk: useZmk,
},
},
{
descr: "Code formatting with visible space",
zmk: "``x y``{-}",
expect: expectMap{
- encoderZJSON: `[{"":"Code","a":{"-":""},"s":"x y"}]`,
encoderHTML: "x\u2423y
",
- encoderSexpr: `((LITERAL-CODE (("-" "")) "x y"))`,
+ encoderMD: "`x y`",
+ encoderSz: `(INLINE (LITERAL-CODE (quote (("-" . ""))) "x y"))`,
+ encoderSHTML: "((code \"x\u2423y\"))",
encoderText: `x y`,
encoderZmk: useZmk,
},
},
{
descr: "HTML in Code formatting",
zmk: "``