-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxml-jsx.ts
114 lines (102 loc) · 2.83 KB
/
xml-jsx.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// This file is essentially copied from https://github.com/dyedgreen/deno-jsx/blob/main/mod.ts
// It removes the logic that disallows certain HTML tags from having content, therefore making it suitable for XML.
declare global {
namespace JSX {
// deno-lint-ignore no-explicit-any
type Element = any;
interface IntrinsicElements {
[elemName: string]: unknown;
}
interface ElementChildrenAttribute {
children: Record<never, never>;
}
}
}
type literal = string | number;
type NodeSet =
| Node
| literal
| null
| false
| (Node | literal | null | false)[];
export interface Component<P = unknown> {
(props: P): NodeSet | Promise<NodeSet>;
}
interface Children {
children?: (Node | literal | null | false)[];
}
// deno-lint-ignore no-explicit-any
export interface Node<P = any> {
type: Component<P> | string;
props: P & Children;
}
export function h(
type: Component | string,
props?: { [prop: string]: unknown },
...children: (Node | literal | null | false)[]
): JSX.Element {
return { type, props: { ...props, children } };
}
export function Fragment({ children }: Children) {
return children;
}
function escapeHTML(text: string): string {
const entities: { [char: string]: string } = {
"<": "<",
">": ">",
"&": "&",
"'": "'",
'"': """,
};
return text.replaceAll(/[&<>"']/g, (char) => {
return entities[char];
});
}
async function renderNodeSetToString(nodes: NodeSet): Promise<string> {
if (nodes == null || nodes === false) {
return "";
} else if (typeof nodes !== "object") {
return escapeHTML(`${nodes}`);
} else if (Array.isArray(nodes)) {
return (await Promise.all(
nodes.map((child: NodeSet): Promise<string> =>
renderNodeSetToString(child)
),
)).join("");
} else {
return await renderToString(nodes);
}
}
/**
* Renders a given JSX node to a string.
*/
export async function renderToString(jsx: Node): Promise<string> {
if (typeof jsx.type === "function") {
return await renderNodeSetToString(await jsx.type(jsx.props));
} else {
// render props
const props = Object.entries(jsx.props).map((
[prop, value]: [string, unknown],
): string => {
switch (prop) {
case "dangerouslySetInnerHTML":
case "children":
return "";
default:
return ` ${prop}="${
"".concat(value as string).replace(/\"/g, '\\"')
}"`;
}
}).join("");
// render inner HTML
const children = jsx.props?.children ?? [];
let innerHTML = "";
if (jsx.props.dangerouslySetInnerHTML != null) {
innerHTML = jsx.props.dangerouslySetInnerHTML?.__html ?? "";
} else {
innerHTML = await renderNodeSetToString(children);
}
// render HTML tag
return `<${jsx.type}${props}>${innerHTML}</${jsx.type}>`;
}
}