Skip to content

Commit

Permalink
feat(hiccup): add support for XML/DTD proc tags, update readme, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Feb 18, 2019
1 parent 844106b commit ede2842
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 13 deletions.
54 changes: 50 additions & 4 deletions packages/hiccup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ This project is part of the
- [Stateful component](#stateful-component)
- [Component objects](#component-objects)
- [Behavior control attributes](#behavior-control-attributes)
- [Comments](#comments)
- [XML / DTD processing instructions](#xml--dtd-processing-instructions)
- [API](#api)
- [serialize(tree: any, ctx?: any, escape = false): string](#serializetree-any-ctx-any-escape--false-string)
- [escape(str: string): string](#escapestr-string-string)
Expand Down Expand Up @@ -57,11 +59,12 @@ rendering etc. For interactive use cases, please see companion package
- 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 element attribute value generation via function values
- Dynamic derived attribute value generation via function values
- CSS formatting of `style` attribute objects
- Optional HTML entity encoding
- Support for comments and XML/DTD processing instructions
- Branch-local behavior control attributes to control serialization
- Small (2.2KB minified) & fast
- Small (1.9KB minified) & fast

*) Lazy composition here means that functions are only executed at
serialization time. Examples below...
Expand All @@ -70,13 +73,16 @@ serialization time. Examples below...

- Serverside rendering
- Static site, feed generation
- SVG asset generation
- `.innerHTML` body generation
- SVG asset creation
- Shape trees for declarative canvas API drawing

### Related packages

- [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/master/packages/hdom)
- [@thi.ng/hdom-canvas](https://github.com/thi-ng/umbrella/tree/master/packages/hdom-canvas)
- [@thi.ng/hiccup-css](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-css)
- [@thi.ng/hiccup-markdown](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-markdown)
- [@thi.ng/hiccup-svg](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-svg)

### No special sauce needed (or wanted)
Expand Down Expand Up @@ -466,6 +472,46 @@ serialize(["div.container", ["div", {__skip: true}, "ignore me"]]);
// <div class="container"></div>
```

### Comments

Single or multiline comments can be included using the special `COMMENT`
tag (`__COMMENT__`) (always WITHOUT attributes!).

```ts
[COMMENT, "Hello world"]
// <!-- Hello world -->

[COMMENT, "Hello", "world"]
// <!--
// Hello
// world
// -->
```

### XML / DTD processing instructions

Currently, the only processing / DTD instructions supported are:

- `?xml`
- `!DOCTYTPE`
- `!ELEMENT`
- `!ENTITY`
- `!ATTLIST`

These are used as follows (attribs are only allowed for `?xml`, all
others only accept a body string which is taken as is):

```ts
["?xml", { version: "1.0", standalone: "yes" }]
// <?xml version="1.0" standalone="yes"?>

["!DOCTYPE", "html"]
// <!DOCTYPE html>
```

Emitted processing instructions are always succeeded by a newline
character.

## API

The library exposes these two functions:
Expand Down Expand Up @@ -528,7 +574,7 @@ new tree (or undefined).
```ts
const foo = (ctx, a, b) => ["div#" + a, ctx.foo, b];

serialize([foo, "id", "body"], {foo: {class: "black"}})
serialize([foo, "id", "body"], { foo: { class: "black" }})
// <div id="id" class="black">body</div>
```

Expand Down
11 changes: 10 additions & 1 deletion packages/hiccup/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
export const SVG_NS = "http://www.w3.org/2000/svg";
export const XLINK_NS = "http://www.w3.org/1999/xlink";
export const XHTML_NS = "http://www.w3.org/1999/xhtml";

export const TAG_REGEXP = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/;

export const PROC_TAGS = {
"?xml": "?>\n",
"!DOCTYPE": ">\n",
"!ENTITY": ">\n",
"!ELEMENT": ">\n",
"!ATTLIST": ">\n",
};

// tslint:disable-next-line
export const SVG_TAGS = "animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view"
.split(" ")
.reduce((acc, x) => (acc[x] = 1, acc), {});

// tslint:disable-next-line
export const VOID_TAGS = "area base br circle col command ellipse embed hr img input keygen line link meta param path polygon polyline rect source stop track use wbr"
export const VOID_TAGS = "area base br circle col command ellipse embed hr img input keygen line link meta param path polygon polyline rect source stop track use wbr ?xml"
.split(" ")
.reduce((acc, x) => (acc[x] = 1, acc), {});

Expand Down
37 changes: 29 additions & 8 deletions packages/hiccup/src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { illegalArgs } from "@thi.ng/errors";
import {
COMMENT,
NO_SPANS,
PROC_TAGS,
TAG_REGEXP,
VOID_TAGS
} from "./api";
Expand Down Expand Up @@ -72,9 +73,9 @@ import { escape } from "./escape";
* function MUST be a valid new tree (or `undefined`).
*
* If the `ctx` object it'll be passed to each embedded component fns.
* Optionally call `derefContext()` prior to `serialize()` to
* auto-deref context keys with values implementing the thi.ng/api
* `IDeref` interface.
* Optionally call `derefContext()` prior to `serialize()` to auto-deref
* context keys with values implementing the thi.ng/api `IDeref`
* interface.
*
* ```js
* const foo = (ctx, a, b) => ["div#" + a, ctx.foo, b];
Expand Down Expand Up @@ -104,7 +105,7 @@ import { escape } from "./escape";
* entire tree branch will be excluded from the output.
*
* Single or multiline comments can be included using the special
* `COMMENT` tag (always WITHOUT attributes!).
* `COMMENT` tag (`__COMMENT__`) (always WITHOUT attributes!).
*
* ```
* [COMMENT, "Hello world"]
Expand All @@ -117,6 +118,25 @@ import { escape } from "./escape";
* -->
* ```
*
* Currently, the only processing / DTD instructions supported are:
*
* - `?xml`
* - `!DOCTYTPE`
* - `!ELEMENT`
* - `!ENTITY`
* - `!ATTLIST`
*
* These are used as follows (attribs are only allowed for `?xml`, all
* others only accept a body string which is taken as is):
*
* ```
* ["?xml", { version: "1.0", standalone: "yes" }]
* // <?xml version="1.0" standalone="yes"?>
*
* ["!DOCTYPE", "html"]
* // <!DOCTYPE html>
* ```
*
* @param tree hiccup elements / component tree
* @param ctx arbitrary user context object
* @param escape auto-escape entities
Expand Down Expand Up @@ -196,16 +216,17 @@ const _serialize = (
if (VOID_TAGS[tag]) {
illegalArgs(`No body allowed in tag: ${tag}`);
}
res += ">";
span = span && !NO_SPANS[tag];
const proc = PROC_TAGS[tag];
res += proc ? " " : ">";
span = span && !proc && !NO_SPANS[tag];
for (let i = 0, n = body.length; i < n; i++) {
res += _serialize(body[i], ctx, esc, span, keys, [...path, i]);
}
return res += `</${tag}>`;
return res += (proc || `</${tag}>`);
} else if (!VOID_TAGS[tag]) {
return res += `></${tag}>`;
}
return res += "/>";
return res += (PROC_TAGS[tag] || "/>");
}
if (isNotStringAndIterable(tree)) {
return _serializeIter(tree, ctx, esc, span, keys, path);
Expand Down
12 changes: 12 additions & 0 deletions packages/hiccup/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,16 @@ describe("serialize", () => {
["a", ["b", { __skip: true }, "bb"], ["b", "bbb"]],
`<a><b>bbb</b></a>`
);

check(
"doctype",
["!DOCTYPE", "html"],
"<!DOCTYPE html>\n"
);

check(
"?xml",
["?xml", { version: "1.0", standalone: "yes" }],
`<?xml version="1.0" standalone="yes"?>\n`
);
});

0 comments on commit ede2842

Please sign in to comment.