@thi.ng/hiccup
Advanced tools
Comparing version 4.3.3 to 5.0.0
@@ -11,2 +11,4 @@ /** @internal */ | ||
export declare const CDATA = "!CDATA"; | ||
/** @internal */ | ||
export declare const DOCTYPE = "!DOCTYPE"; | ||
/** | ||
@@ -13,0 +15,0 @@ * XML processing instruction in hiccup format. |
@@ -15,2 +15,4 @@ /** @internal */ | ||
export const CDATA = "!CDATA"; | ||
/** @internal */ | ||
export const DOCTYPE = "!DOCTYPE"; | ||
/** | ||
@@ -26,3 +28,3 @@ * XML processing instruction in hiccup format. | ||
*/ | ||
export const DOCTYPE_HTML = ["!DOCTYPE", "html"]; | ||
export const DOCTYPE_HTML = [DOCTYPE, "html"]; | ||
/** @internal */ | ||
@@ -29,0 +31,0 @@ export const NO_SPANS = { |
# Change Log | ||
- **Last updated**: 2023-09-06T13:36:28Z | ||
- **Last updated**: 2023-09-19T10:42:50Z | ||
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub) | ||
@@ -12,2 +12,23 @@ | ||
# [5.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/hiccup@5.0.0) (2023-09-19) | ||
#### 🛑 Breaking changes | ||
- add SerializeOpts, update serialize() ([442d777](https://github.com/thi-ng/umbrella/commit/442d777)) | ||
- BREAKING CHANGE: update serialize() args, replace with options object | ||
- only a breaking change for "advanced" use cases | ||
- add SerializeOpts to simplify serialize() args | ||
- add customizable entity escaping (via new opts) | ||
- add/update tests | ||
#### 🩹 Bug fixes | ||
- update entity escapes in serialize() ([369d83e](https://github.com/thi-ng/umbrella/commit/369d83e)) | ||
- use [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/main/packages/strings) escapeEntitiesNum() for better XML/SVG compatibility | ||
- add tests | ||
#### ♻️ Refactoring | ||
- minor updates ([94b3de6](https://github.com/thi-ng/umbrella/commit/94b3de6)) | ||
## [4.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/hiccup@4.3.0) (2023-08-27) | ||
@@ -14,0 +35,0 @@ |
@@ -8,6 +8,8 @@ import { deref } from "@thi.ng/api/deref"; | ||
v = deref(rules[r]); | ||
isFunction(v) && (v = v(rules)); | ||
v != null && (css += `${r}:${v};`); | ||
if (isFunction(v)) | ||
v = v(rules); | ||
if (v != null) | ||
css += `${r}:${v};`; | ||
} | ||
return css; | ||
}; |
@@ -19,5 +19,6 @@ import { implementsFunction } from "@thi.ng/checks/implements-function"; | ||
const v = res[k]; | ||
implementsFunction(v, "deref") && (res[k] = v.deref()); | ||
if (implementsFunction(v, "deref")) | ||
res[k] = v.deref(); | ||
} | ||
return res; | ||
}; |
{ | ||
"name": "@thi.ng/hiccup", | ||
"version": "4.3.3", | ||
"version": "5.0.0", | ||
"description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", | ||
@@ -40,3 +40,3 @@ "type": "module", | ||
"@thi.ng/errors": "^2.3.5", | ||
"@thi.ng/strings": "^3.5.0" | ||
"@thi.ng/strings": "^3.6.0" | ||
}, | ||
@@ -131,3 +131,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "b2ef5a1b8932d067af4ec2fc7da03d59d6868dc7\n" | ||
"gitHead": "c22e8996cee284ebe8ea88582beb1ab5fc6ee503\n" | ||
} |
170
README.md
@@ -39,3 +39,2 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
- [serialize()](#serialize) | ||
- [escape()](#escape) | ||
- [Authors](#authors) | ||
@@ -49,13 +48,14 @@ - [License](#license) | ||
Inspired by [Hiccup](https://github.com/weavejester/hiccup) and | ||
[Reagent](http://reagent-project.github.io/) for Clojure/ClojureScript. | ||
[Reagent](http://reagent-project.github.io/) for Clojure/ClojureScript, this | ||
package provides key infrastructure for a number of other related libraries. | ||
Forget all the custom toy DSLs for templating and instead use the full | ||
power of ES6 to directly define fully data-driven, purely functional and | ||
easily *composable* components for static serialization to HTML & | ||
friends. | ||
Forget all the custom toy DSLs for templating and instead use the full power of | ||
modern JavaScript to directly define fully data-driven, purely functional and | ||
easily *composable* components for static serialization to HTML & friends. | ||
This library is suitable for static website generation, server side rendering | ||
This library is suitable for any SGML-style (HTML/XML/SVG/RSS/Atom etc.) | ||
serialization, including static website/asset generation, server side rendering | ||
etc. For interactive use cases, please see companion packages | ||
[@thi.ng/rdom](https://github.com/thi-ng/umbrella/tree/develop/packages/rdom) | ||
(or the older | ||
(or the older, now unmaintained | ||
[@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/develop/packages/hdom)) | ||
@@ -66,14 +66,14 @@ and their various support packages. | ||
- Only uses arrays, functions, ES6 iterables / iterators / generators | ||
- Only uses JS arrays, plain objects, functions, ES6 iterables / iterators / generators | ||
- Eager & lazy component composition using embedded functions / closures | ||
- Support for self-closing tags (incl. validation), boolean attributes | ||
- Arbitrary user context object injection for component functions | ||
- Dynamic derived attribute value generation via function values | ||
- Arbitrary user context object injection for embedded component functions | ||
- Dynamically derived attribute value generation via function values | ||
- CSS formatting of `style` attribute objects | ||
- Optional HTML entity encoding | ||
- Optional HTML/XML entity encoding | ||
- Support for comments and XML/DTD processing instructions | ||
- Branch-local behavior control attributes to control serialization | ||
- Branch-local behavior control attributes to customize serialization | ||
- Small (1.9KB minified) & fast | ||
(*) Lazy composition here means that functions are only executed at | ||
<sup>(*)</sup> Lazy composition here means that functions are only executed at | ||
serialization time. Examples below... | ||
@@ -92,20 +92,23 @@ | ||
Using only vanilla language features simplifies the development, | ||
composability, reusability and testing of components. Furthermore, no | ||
custom template parser is required and you're only restricted by the | ||
expressiveness of the language / environment, not by your template | ||
engine. | ||
Using only vanilla language features simplifies the development, removes need | ||
for extra tooling, improves composability, reusability, transformation and | ||
testing of components. No custom template parser (a la JSX or Handlebars etc.) | ||
is required and you're only restricted by the expressiveness of the language / | ||
environment, not by your template engine. | ||
Components can be defined as simple functions returning arrays or loaded | ||
via JSON/JSONP. | ||
Components can be defined as simple arrays and/or functions returning arrays or | ||
can be dynamically generated or loaded via JSON... | ||
### What is Hiccup? | ||
For many years, [Hiccup](https://github.com/weavejester/hiccup) has been | ||
the de-facto standard to encode HTML/XML datastructures in Clojure. This | ||
library brings & extends this convention into ES6. A valid Hiccup tree | ||
is any flat (though, usually nested) array of the following possible | ||
structures. Any functions embedded in the tree are expected to return | ||
values of the same structure. Please see [examples](#examples) & | ||
[API](#api) further explanations... | ||
For many years, [Hiccup](https://github.com/weavejester/hiccup) has been the | ||
de-facto standard to encode HTML/XML datastructures in Clojure (and many years | ||
before that, [the overall idea was introduced in Scheme by Oleg Kiselyov and | ||
Kirill Lisovsky in | ||
1999](https://web.archive.org/web/20011225105556/http://okmij.org/ftp/Scheme/xml.html)). | ||
This library brings & extends this convention into ES6. A valid Hiccup tree is | ||
any flat (though, usually nested) array of the following possible structures. | ||
Any functions embedded in the tree are expected to return values of the same | ||
structure. Please see [examples](#examples) & [API](#api) further | ||
explanations... | ||
@@ -134,2 +137,3 @@ ```ts | ||
- [@thi.ng/hiccup-html](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-html) - 100+ type-checked HTML5 element functions for [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup) related infrastructure | ||
- [@thi.ng/hiccup-html-parse](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-html-parse) - HTML parsing and transformation to nested JS arrays in hiccup format | ||
- [@thi.ng/hiccup-markdown](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-markdown) - Markdown parser & serializer from/to Hiccup format | ||
@@ -177,3 +181,3 @@ - [@thi.ng/hiccup-svg](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup-svg) - SVG element functions for [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/develop/packages/hiccup) & related tooling | ||
Package sizes (brotli'd, pre-treeshake): ESM: 2.09 KB | ||
Package sizes (brotli'd, pre-treeshake): ESM: 2.14 KB | ||
@@ -214,3 +218,3 @@ ## Dependencies | ||
Tag names support | ||
[Zencoding/Emmet](https://docs.emmet.io/abbreviations/syntax/#id-and-class) | ||
[Emmet/Zencoding](https://docs.emmet.io/abbreviations/syntax/#id-and-class) | ||
style ID & class attribute expansion: | ||
@@ -256,10 +260,7 @@ | ||
If an attribute specifies a function as value, the function is called | ||
with the entire attribute object as argument. This allows for the | ||
dynamic generation of attribute values, based on existing ones. The | ||
result MUST be a string. | ||
If an attribute specifies a function as value, the function is called with the | ||
entire attribute object as argument (incl. any `id` or `class` attribs derived | ||
from an Emmet-style tag name). This allows for the dynamic generation of | ||
attribute values, based on existing ones. The result MUST be a string. | ||
**Function values for event attributes (any attrib name starting with | ||
"on") WILL BE OMITTED from output.** | ||
```ts | ||
@@ -273,2 +274,5 @@ ["div#foo", { bar: (attribs) => attribs.id + "-bar" }] | ||
Function values for event attributes (any attrib name starting with | ||
"on") WILL BE OMITTED from output: | ||
```ts | ||
@@ -310,5 +314,7 @@ ["div#foo", { onclick: () => alert("foo") }, "click me!"] | ||
Every component function will receive an arbitrary user defined context | ||
object as first argument. This context object is passed to `serialize()` | ||
and is then auto-injected for every component function call. | ||
Every component function will receive an arbitrary user defined context object | ||
as first argument. This context object can be passed to | ||
[`serialize()`](https://docs.thi.ng/umbrella/hiccup/functions/serialize.html) | ||
via its [options argument]() and is then passed as arg to every component function | ||
call. | ||
@@ -334,3 +340,4 @@ The context object should contain any global component configuration, | ||
[section, "Hello world", "Easy theming"], | ||
{ theme } | ||
// pass context object via options | ||
{ ctx: { theme } } | ||
); | ||
@@ -340,8 +347,8 @@ // <section class="bg-black moon-gray bt b--dark-gray mt3"><h1 class="white f3">Hello world</h1>Easy theming</section> | ||
**Note:** Of course the context is ONLY auto-injected for lazily | ||
embedded component functions (as shown above), i.e. if the functions are | ||
wrapped in arrays and only called during serialization. If you call a | ||
component function directly, you MUST pass the context (or `null`) as | ||
first arg yourself. Likewise, if a component function doesn't make use | ||
of the context you can either: | ||
**Note:** Of course the context is ONLY auto-injected for lazily embedded | ||
component functions (like the examples shown above), i.e. if the functions are | ||
wrapped in arrays and only called during serialization. If you call such a | ||
component function directly, you MUST pass the context (or `null`) as first arg | ||
yourself. Likewise, if a component function doesn't make use of the context you | ||
can use either: | ||
@@ -377,4 +384,6 @@ ```ts | ||
```ts | ||
const fs = require("fs"); | ||
```ts tangle:export/readme-circles.js | ||
import { serialize } from "@thi.ng/hiccup"; | ||
import { repeatedly } from "@thi.ng/transducers"; | ||
import { writeFileSync } from "fs"; | ||
@@ -384,3 +393,3 @@ // creates an unstyled SVG circle element | ||
// context handling is described further below | ||
const circle = (_, x, y, r) => ["circle", { cx: x | 0, cy: y | 0, r: r | 0 }]; | ||
const circle = (_, x, y, r) => ["circle", { cx: ~~x, cy: ~~y, r: ~~r }]; | ||
@@ -399,19 +408,16 @@ // note how this next component lazily composes `circle`. | ||
// generator to produce iterable of `n` calls to `fn` | ||
function* repeatedly(n, fn) { | ||
while (n-- > 0) yield fn(); | ||
} | ||
// generate 100 random circles and write serialized SVG to file | ||
// `randomCircle` is wrapped | ||
import { SVG_NS } from "@thi.ng/hiccup"; | ||
import { XML_SVG } from "@thi.ng/prefixes"; | ||
const doc = [ | ||
"svg", { xmlns: SVG_NS, width: 1000, height: 1000 }, | ||
"svg", { xmlns: XML_SVG, width: 1000, height: 1000 }, | ||
["g", { fill: "none", stroke: "red" }, | ||
repeatedly(100, randomCircle)]]; | ||
repeatedly(randomCircle, 100)]]; | ||
fs.writeFileSync("circles.svg", serialize(doc)); | ||
writeFileSync("export/circles.svg", serialize(doc)); | ||
``` | ||
Resulting example output: | ||
```xml | ||
@@ -431,3 +437,5 @@ <svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000"> | ||
```ts | ||
```js tangle:export/readme-glossary.js | ||
import { serialize } from "@thi.ng/hiccup"; | ||
// data | ||
@@ -457,6 +465,9 @@ const glossary = { | ||
// the 2nd arg `true` enforces HTML entity encoding (off by default) | ||
serialize(widget, null, true); | ||
// serialize with enforced HTML entity encoding (off by default) | ||
console.log(serialize(widget, { escape: true })); | ||
``` | ||
(Re)formatted output (generated HTML will always be dense, without intermittent | ||
white space): | ||
```html | ||
@@ -480,3 +491,5 @@ <div class="widget"> | ||
```ts | ||
```js tangle:export/readme-toc.js | ||
import { serialize } from "@thi.ng/hiccup"; | ||
// stateful component to create hierarchically | ||
@@ -511,8 +524,12 @@ // indexed & referencable section headlines: | ||
serialize([ | ||
"div.toc", | ||
TOC.map(([level, title]) => [section, level, title]) | ||
]); | ||
console.log( | ||
serialize([ | ||
"div.toc", | ||
TOC.map(([level, title]) => [section, level, title]) | ||
]) | ||
); | ||
``` | ||
Re-formatted HTML output: | ||
```html | ||
@@ -541,3 +558,3 @@ <div class="toc"> | ||
```ts | ||
```js | ||
const component = { | ||
@@ -559,4 +576,4 @@ render: (ctx, title, ...body) => ["section", ["h1", title], ...body] | ||
```ts | ||
serialize(["div.container", ["div", {__skip: true}, "ignore me"]]); | ||
```js | ||
serialize(["div.container", ["div", { __skip: true }, "ignore me"]]); | ||
// <div class="container"></div> | ||
@@ -649,3 +666,3 @@ ``` | ||
```ts | ||
["div", {style: {color: "red", background: "#000"}}] | ||
["div", { style: { color: "red", background: "#000" } }] | ||
// <div style="color:red;background:#000;"></div> | ||
@@ -670,3 +687,3 @@ ``` | ||
serialize([foo, "id", "body"], { foo: { class: "black" }}) | ||
serialize([foo, "id", "body"], { foo: { class: "black" } }) | ||
// <div id="id" class="black">body</div> | ||
@@ -683,11 +700,2 @@ ``` | ||
### escape() | ||
Signature: `escape(str: string): string` | ||
Helper function. Applies HTML entity replacement on given string. If | ||
`serialize()` is called with `true` as 2nd argument, entity encoding is | ||
done automatically ([list of entities | ||
considered](https://github.com/thi-ng/umbrella/blob/develop/packages/hiccup/src/api.ts#L11)). | ||
## Authors | ||
@@ -694,0 +702,0 @@ |
@@ -0,2 +1,41 @@ | ||
import type { FnU } from "@thi.ng/api"; | ||
/** | ||
* Options to customize the behavior of {@link serialize}. | ||
*/ | ||
export interface SerializeOpts { | ||
/** | ||
* Arbitrary user context object | ||
*/ | ||
ctx?: any; | ||
/** | ||
* If true, auto-escape entities via {@link SerializeOpts.escapeFn}. | ||
* | ||
* @defaultValue false | ||
*/ | ||
escape: boolean; | ||
/** | ||
* Only used if {@link SerializeOpts.escape} is enabled. Function to escape | ||
* entities. By default uses | ||
* [`escapeEntitiesNum()`](https://docs.thi.ng/umbrella/strings/functions/escapeEntitiesNum.html). | ||
*/ | ||
escapeFn: FnU<string>; | ||
/** | ||
* If true (default: false), all text content will be wrapped in `<span>` | ||
* elements (to ensure DOM compatibility with hdom). The only elements for | ||
* spans are never created are listed in {@link NO_SPANS}. | ||
* | ||
* @defaultValue false | ||
*/ | ||
span: boolean; | ||
/** | ||
* If true (default: false), all elements will have an autogenerated `key` | ||
* attribute injected. If {@link SerializeOpts.span} is enabled, `keys` will | ||
* be enabled by default too (since in this case we assume the output is | ||
* meant to be compatible with [`thi.ng/hdom`](https://thi.ng/hdom)). | ||
* | ||
* @defaultValue false | ||
*/ | ||
keys: boolean; | ||
} | ||
/** | ||
* Recursively normalizes and serializes given tree as HTML/SVG/XML string. | ||
@@ -18,3 +57,3 @@ * Expands any embedded component functions with their results. | ||
* | ||
* Tags can be defined in "Zencoding" convention, e.g. | ||
* Tags can be defined in "Emmet" convention, e.g. | ||
* | ||
@@ -41,3 +80,3 @@ * ```js | ||
* ```js | ||
* ["div", {style: {color: "red", background: "#000"}}] | ||
* ["div", { style: { color: "red", background: "#000" } }] | ||
* // <div style="color:red;background:#000;"></div> | ||
@@ -59,3 +98,3 @@ * ``` | ||
* | ||
* If the `ctx` object it'll be passed to each embedded component fns. | ||
* If the `ctx` option is given it'll be passed to each embedded component fns. | ||
* Optionally call {@link derefContext} prior to {@link serialize} to auto-deref | ||
@@ -69,3 +108,3 @@ * context keys with values implementing the | ||
* | ||
* serialize([foo, "id", "body"], { foo: { class: "black" } }) | ||
* serialize([foo, "id", "body"], { ctx: { foo: { class: "black" } } }) | ||
* // <div id="id" class="black">body</div> | ||
@@ -79,21 +118,13 @@ * ``` | ||
* | ||
* If the optional `span` flag is true (default: false), all text content will | ||
* be wrapped in <span> elements (this is to ensure DOM compatibility with | ||
* hdom). The only elements for spans are never created are listed in `NO_SPANS` | ||
* in `api.ts`. | ||
* | ||
* If the optional `keys` flag is true (default: false), all elements will have | ||
* an autogenerated `key` attribute injected. If `span` is enabled, `keys` will | ||
* be enabled by default too (since in this case we assume the output is meant | ||
* to be compatible with [`thi.ng/hdom`](https://thi.ng/hdom)). | ||
* | ||
* hiccup & hdom control attributes (i.e. attrib names prefixed with `__`) will | ||
* be omitted from the output. The only control attrib supported by this package | ||
* is `__serialize`. If set to `false`, the entire tree branch will be excluded | ||
* from the output. | ||
* is `__serialize`. If set to `false`, the entire tree branch below (and | ||
* including) the element with that attrib will be excluded from the output. | ||
* | ||
* **See {@link SerializeOpts} for further available options.** | ||
* | ||
* Single or multiline comments can be included using the special `COMMENT` tag | ||
* (`__COMMENT__`) (always WITHOUT attributes!). | ||
* (`"__COMMENT__"`) (always WITHOUT attributes!). | ||
* | ||
* ``` | ||
* ```js | ||
* [COMMENT, "Hello world"] | ||
@@ -103,6 +134,6 @@ * // <!-- Hello world --> | ||
* [COMMENT, "Hello", "world"] | ||
* <!-- | ||
* Hello | ||
* world | ||
* --> | ||
* // <!-- | ||
* // Hello | ||
* // world | ||
* // --> | ||
* ``` | ||
@@ -121,7 +152,7 @@ * | ||
* | ||
* ``` | ||
* ["?xml", { version: "1.0", standalone: "yes" }] | ||
* ```js | ||
* serialize(["?xml", { version: "1.0", standalone: "yes" }]) | ||
* // <?xml version="1.0" standalone="yes"?> | ||
* | ||
* ["!DOCTYPE", "html"] | ||
* ["!DOCTYPE", "html"] // (also available as DOCTYPE_HTML) | ||
* // <!DOCTYPE html> | ||
@@ -131,8 +162,5 @@ * ``` | ||
* @param tree - hiccup elements / component tree | ||
* @param ctx - arbitrary user context object | ||
* @param escape - auto-escape entities | ||
* @param span - use spans for text content | ||
* @param keys - attach key attribs | ||
* @param opts - options | ||
*/ | ||
export declare const serialize: (tree: any, ctx?: any, escape?: boolean, span?: boolean, keys?: boolean, path?: number[]) => string; | ||
export declare const serialize: (tree: any, opts?: Partial<SerializeOpts>, path?: number[]) => string; | ||
//# sourceMappingURL=serialize.d.ts.map |
126
serialize.js
@@ -9,3 +9,3 @@ import { deref, isDeref } from "@thi.ng/api/deref"; | ||
import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; | ||
import { escapeEntities } from "@thi.ng/strings/entities"; | ||
import { escapeEntitiesNum } from "@thi.ng/strings/entities"; | ||
import { ATTRIB_JOIN_DELIMS, CDATA, COMMENT, NO_CLOSE_EMPTY, NO_SPANS, PROC_TAGS, VOID_TAGS, } from "./api.js"; | ||
@@ -32,3 +32,3 @@ import { css } from "./css.js"; | ||
* | ||
* Tags can be defined in "Zencoding" convention, e.g. | ||
* Tags can be defined in "Emmet" convention, e.g. | ||
* | ||
@@ -55,3 +55,3 @@ * ```js | ||
* ```js | ||
* ["div", {style: {color: "red", background: "#000"}}] | ||
* ["div", { style: { color: "red", background: "#000" } }] | ||
* // <div style="color:red;background:#000;"></div> | ||
@@ -73,3 +73,3 @@ * ``` | ||
* | ||
* If the `ctx` object it'll be passed to each embedded component fns. | ||
* If the `ctx` option is given it'll be passed to each embedded component fns. | ||
* Optionally call {@link derefContext} prior to {@link serialize} to auto-deref | ||
@@ -83,3 +83,3 @@ * context keys with values implementing the | ||
* | ||
* serialize([foo, "id", "body"], { foo: { class: "black" } }) | ||
* serialize([foo, "id", "body"], { ctx: { foo: { class: "black" } } }) | ||
* // <div id="id" class="black">body</div> | ||
@@ -93,21 +93,13 @@ * ``` | ||
* | ||
* If the optional `span` flag is true (default: false), all text content will | ||
* be wrapped in <span> elements (this is to ensure DOM compatibility with | ||
* hdom). The only elements for spans are never created are listed in `NO_SPANS` | ||
* in `api.ts`. | ||
* | ||
* If the optional `keys` flag is true (default: false), all elements will have | ||
* an autogenerated `key` attribute injected. If `span` is enabled, `keys` will | ||
* be enabled by default too (since in this case we assume the output is meant | ||
* to be compatible with [`thi.ng/hdom`](https://thi.ng/hdom)). | ||
* | ||
* hiccup & hdom control attributes (i.e. attrib names prefixed with `__`) will | ||
* be omitted from the output. The only control attrib supported by this package | ||
* is `__serialize`. If set to `false`, the entire tree branch will be excluded | ||
* from the output. | ||
* is `__serialize`. If set to `false`, the entire tree branch below (and | ||
* including) the element with that attrib will be excluded from the output. | ||
* | ||
* **See {@link SerializeOpts} for further available options.** | ||
* | ||
* Single or multiline comments can be included using the special `COMMENT` tag | ||
* (`__COMMENT__`) (always WITHOUT attributes!). | ||
* (`"__COMMENT__"`) (always WITHOUT attributes!). | ||
* | ||
* ``` | ||
* ```js | ||
* [COMMENT, "Hello world"] | ||
@@ -117,6 +109,6 @@ * // <!-- Hello world --> | ||
* [COMMENT, "Hello", "world"] | ||
* <!-- | ||
* Hello | ||
* world | ||
* --> | ||
* // <!-- | ||
* // Hello | ||
* // world | ||
* // --> | ||
* ``` | ||
@@ -135,7 +127,7 @@ * | ||
* | ||
* ``` | ||
* ["?xml", { version: "1.0", standalone: "yes" }] | ||
* ```js | ||
* serialize(["?xml", { version: "1.0", standalone: "yes" }]) | ||
* // <?xml version="1.0" standalone="yes"?> | ||
* | ||
* ["!DOCTYPE", "html"] | ||
* ["!DOCTYPE", "html"] // (also available as DOCTYPE_HTML) | ||
* // <!DOCTYPE html> | ||
@@ -145,24 +137,32 @@ * ``` | ||
* @param tree - hiccup elements / component tree | ||
* @param ctx - arbitrary user context object | ||
* @param escape - auto-escape entities | ||
* @param span - use spans for text content | ||
* @param keys - attach key attribs | ||
* @param opts - options | ||
*/ | ||
export const serialize = (tree, ctx, escape = false, span = false, keys = span, path = [0]) => _serialize(tree, ctx, escape, span, keys, path); | ||
const _serialize = (tree, ctx, esc, span, keys, path) => tree == null | ||
export const serialize = (tree, opts, path = [0]) => { | ||
const $opts = { | ||
escape: false, | ||
escapeFn: escapeEntitiesNum, | ||
span: false, | ||
keys: false, | ||
...opts, | ||
}; | ||
if (opts?.keys == null && $opts.span) | ||
$opts.keys = true; | ||
return _serialize(tree, $opts, path); | ||
}; | ||
const _serialize = (tree, opts, path) => tree == null | ||
? "" | ||
: Array.isArray(tree) | ||
? serializeElement(tree, ctx, esc, span, keys, path) | ||
? serializeElement(tree, opts, path) | ||
: isFunction(tree) | ||
? _serialize(tree(ctx), ctx, esc, span, keys, path) | ||
? _serialize(tree(opts.ctx), opts, path) | ||
: implementsFunction(tree, "toHiccup") | ||
? _serialize(tree.toHiccup(ctx), ctx, esc, span, keys, path) | ||
? _serialize(tree.toHiccup(opts.ctx), opts, path) | ||
: isDeref(tree) | ||
? _serialize(tree.deref(), ctx, esc, span, keys, path) | ||
? _serialize(tree.deref(), opts, path) | ||
: isNotStringAndIterable(tree) | ||
? serializeIter(tree, ctx, esc, span, keys, path) | ||
: ((tree = esc ? escapeEntities(String(tree)) : String(tree)), span) | ||
? `<span${keys ? ` key="${path.join("-")}"` : ""}>${tree}</span>` | ||
? serializeIter(tree, opts, path) | ||
: ((tree = __escape(String(tree), opts)), opts.span) | ||
? `<span${opts.keys ? ` key="${path.join("-")}"` : ""}>${tree}</span>` | ||
: tree; | ||
const serializeElement = (tree, ctx, esc, span, keys, path) => { | ||
const serializeElement = (tree, opts, path) => { | ||
let tag = tree[0]; | ||
@@ -172,5 +172,5 @@ return !tree.length | ||
: isFunction(tag) | ||
? _serialize(tag.apply(null, [ctx, ...tree.slice(1)]), ctx, esc, span, keys, path) | ||
? _serialize(tag.apply(null, [opts.ctx, ...tree.slice(1)]), opts, path) | ||
: implementsFunction(tag, "render") | ||
? _serialize(tag.render.apply(null, [ctx, ...tree.slice(1)]), ctx, esc, span, keys, path) | ||
? _serialize(tag.render.apply(null, [opts.ctx, ...tree.slice(1)]), opts, path) | ||
: tag === COMMENT | ||
@@ -181,8 +181,8 @@ ? serializeComment(tree) | ||
: isString(tag) | ||
? serializeTag(tree, ctx, esc, span, keys, path) | ||
? serializeTag(tree, opts, path) | ||
: isNotStringAndIterable(tree) | ||
? serializeIter(tree, ctx, esc, span, keys, path) | ||
? serializeIter(tree, opts, path) | ||
: illegalArgs(`invalid tree node: ${tree}`); | ||
}; | ||
const serializeTag = (tree, ctx, esc, span, keys, path) => { | ||
const serializeTag = (tree, opts, path) => { | ||
tree = normalize(tree); | ||
@@ -192,12 +192,12 @@ const attribs = tree[1]; | ||
return ""; | ||
keys && attribs.key === undefined && (attribs.key = path.join("-")); | ||
opts.keys && attribs.key === undefined && (attribs.key = path.join("-")); | ||
const tag = tree[0]; | ||
const body = tree[2] | ||
? serializeBody(tag, tree[2], ctx, esc, span, keys, path) | ||
? serializeBody(tag, tree[2], opts, path) | ||
: !VOID_TAGS[tag] && !NO_CLOSE_EMPTY[tag] | ||
? `></${tag}>` | ||
: PROC_TAGS[tag] || "/>"; | ||
return `<${tag}${serializeAttribs(attribs, esc)}${body}`; | ||
return `<${tag}${serializeAttribs(attribs, opts)}${body}`; | ||
}; | ||
const serializeAttribs = (attribs, esc) => { | ||
const serializeAttribs = (attribs, opts) => { | ||
let res = ""; | ||
@@ -207,3 +207,3 @@ for (let a in attribs) { | ||
continue; | ||
const v = serializeAttrib(attribs, a, deref(attribs[a]), esc); | ||
const v = serializeAttrib(attribs, a, deref(attribs[a]), opts); | ||
v != null && (res += v); | ||
@@ -213,3 +213,3 @@ } | ||
}; | ||
const serializeAttrib = (attribs, a, v, esc) => { | ||
const serializeAttrib = (attribs, a, v, opts) => { | ||
return v == null | ||
@@ -224,6 +224,6 @@ ? null | ||
: a === "data" | ||
? serializeDataAttribs(v, esc) | ||
: attribPair(a, v, esc); | ||
? serializeDataAttribs(v, opts) | ||
: attribPair(a, v, opts); | ||
}; | ||
const attribPair = (a, v, esc) => { | ||
const attribPair = (a, v, opts) => { | ||
v = | ||
@@ -237,5 +237,5 @@ a === "style" && isPlainObject(v) | ||
: v.toString(); | ||
return v.length ? ` ${a}="${esc ? escapeEntities(v) : v}"` : null; | ||
return v.length ? ` ${a}="${__escape(v, opts)}"` : null; | ||
}; | ||
const serializeDataAttribs = (data, esc) => { | ||
const serializeDataAttribs = (data, opts) => { | ||
let res = ""; | ||
@@ -245,7 +245,7 @@ for (let id in data) { | ||
isFunction(v) && (v = v(data)); | ||
v != null && (res += ` data-${id}="${esc ? escapeEntities(v) : v}"`); | ||
v != null && (res += ` data-${id}="${__escape(v, opts)}"`); | ||
} | ||
return res; | ||
}; | ||
const serializeBody = (tag, body, ctx, esc, span, keys, path) => { | ||
const serializeBody = (tag, body, opts, path) => { | ||
if (VOID_TAGS[tag]) { | ||
@@ -256,5 +256,6 @@ illegalArgs(`No body allowed in tag: ${tag}`); | ||
let res = proc ? " " : ">"; | ||
span = span && !proc && !NO_SPANS[tag]; | ||
if (opts.span && !proc && !NO_SPANS[tag]) | ||
opts = { ...opts, span: true }; | ||
for (let i = 0, n = body.length; i < n; i++) { | ||
res += _serialize(body[i], ctx, esc, span, keys, [...path, i]); | ||
res += _serialize(body[i], opts, [...path, i]); | ||
} | ||
@@ -270,3 +271,3 @@ return res + (proc || `</${tag}>`); | ||
const serializeCData = (tree) => `<![CDATA[\n${tree.slice(1).join("\n")}\n]]>`; | ||
const serializeIter = (iter, ctx, esc, span, keys, path) => { | ||
const serializeIter = (iter, opts, path) => { | ||
const res = []; | ||
@@ -276,5 +277,6 @@ const p = path.slice(0, path.length - 1); | ||
for (let i of iter) { | ||
res.push(_serialize(i, ctx, esc, span, keys, [...p, k++])); | ||
res.push(_serialize(i, opts, [...p, k++])); | ||
} | ||
return res.join(""); | ||
}; | ||
const __escape = (x, opts) => opts.escape ? opts.escapeFn(x) : x; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
80776
732
695
Updated@thi.ng/strings@^3.6.0