Skip to content

Commit 60ab7c7

Browse files
authored
feat: support svg elements (#5330)
Ref #5258 Now users can create svg elements and paste svg code or html with svg. https://github.com/user-attachments/assets/77738ede-e4ee-4fd8-b128-5679170b30b4
1 parent 82ecb10 commit 60ab7c7

File tree

6 files changed

+1267
-123
lines changed

6 files changed

+1267
-123
lines changed

packages/html-data/bin/aria.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "@webstudio-is/sdk";
1212
import { generateWebstudioComponent } from "@webstudio-is/react-sdk";
1313
import {
14-
findTags,
14+
findByTags,
1515
getAttr,
1616
getTextContent,
1717
loadPage,
@@ -34,11 +34,11 @@ const overrides: Record<string, Partial<Attribute>> = {
3434

3535
const html = await loadPage("aria1.3", "https://www.w3.org/TR/wai-aria-1.3");
3636
const document = parseHtml(html);
37-
const list = findTags(document, "dl").find(
37+
const list = findByTags(document, "dl").find(
3838
(table) => getAttr(table, "id")?.value === "index_state_prop"
3939
);
40-
const terms = findTags(list, "dt");
41-
const details = findTags(list, "dd");
40+
const terms = findByTags(list, "dt");
41+
const details = findByTags(list, "dd");
4242
const descriptions = new Map<string, string>();
4343
for (let index = 0; index < terms.length; index += 1) {
4444
const term = getTextContent(terms[index]);

packages/html-data/bin/attributes.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "@webstudio-is/sdk";
1111
import { generateWebstudioComponent } from "@webstudio-is/react-sdk";
1212
import {
13-
findTags,
13+
findByTags,
1414
getAttr,
1515
getTextContent,
1616
loadHtmlIndices,
@@ -123,11 +123,11 @@ const overrides: Record<
123123
// Crawl WHATWG HTML.
124124
const html = await loadHtmlIndices();
125125
const document = parseHtml(html);
126-
const table = findTags(document, "table").find(
126+
const table = findByTags(document, "table").find(
127127
(table) => getAttr(table, "id")?.value === "attributes-1"
128128
);
129-
const [tbody] = findTags(table, "tbody");
130-
const rows = findTags(tbody, "tr");
129+
const [tbody] = findByTags(table, "tbody");
130+
const rows = findByTags(tbody, "tr");
131131

132132
const attributesByTag: Record<string, Attribute[]> = {};
133133
// textarea does not have value attribute and text content is used as initial value

packages/html-data/bin/crawler.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Element = DefaultTreeAdapterMap["element"];
1313

1414
type Attribute = Element["attrs"][number];
1515

16-
export const findTags = (
16+
export const findByTags = (
1717
node: undefined | Node,
1818
tagName: string,
1919
result: NodeWithChildren[] = []
@@ -23,7 +23,28 @@ export const findTags = (
2323
result.push(node);
2424
}
2525
for (const child of node.childNodes) {
26-
findTags(child, tagName, result);
26+
findByTags(child, tagName, result);
27+
}
28+
}
29+
return result;
30+
};
31+
32+
export const findByClasses = (
33+
node: undefined | Node,
34+
className: string,
35+
result: NodeWithChildren[] = []
36+
): NodeWithChildren[] => {
37+
if (node && "childNodes" in node) {
38+
if (
39+
"tagName" in node &&
40+
node.attrs.some(
41+
(item) => item.name === "class" && item.value === className
42+
)
43+
) {
44+
result.push(node);
45+
}
46+
for (const child of node.childNodes) {
47+
findByClasses(child, className, result);
2748
}
2849
}
2950
return result;
@@ -72,3 +93,6 @@ export const loadHtmlIndices = () =>
7293
"html-spec-indices",
7394
"https://html.spec.whatwg.org/multipage/indices.html"
7495
);
96+
97+
export const loadSvgSinglePage = () =>
98+
loadPage("svg-spec", "https://www.w3.org/TR/SVG11/single-page.html");

packages/html-data/bin/elements.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { dirname } from "node:path";
22
import { mkdir, writeFile } from "node:fs/promises";
33
import {
4-
findTags,
4+
findByClasses,
5+
findByTags,
56
getTextContent,
67
loadHtmlIndices,
8+
loadSvgSinglePage,
79
parseHtml,
810
} from "./crawler";
911

1012
// Crawl WHATWG HTML.
1113

12-
const html = await loadHtmlIndices();
13-
const document = parseHtml(html);
14-
1514
type Element = {
1615
description: string;
1716
categories: string[];
@@ -24,12 +23,15 @@ const elementsByTag: Record<string, Element> = {};
2423
* scrape elements table with content model
2524
*/
2625
{
27-
const table = findTags(document, "table").find((table) => {
28-
const [caption] = findTags(table, "caption");
26+
const html = await loadHtmlIndices();
27+
const document = parseHtml(html);
28+
29+
const table = findByTags(document, "table").find((table) => {
30+
const [caption] = findByTags(table, "caption");
2931
return getTextContent(caption).toLowerCase().includes("list of elements");
3032
});
31-
const [tbody] = findTags(table, "tbody");
32-
const rows = findTags(tbody, "tr");
33+
const [tbody] = findByTags(table, "tbody");
34+
const rows = findByTags(tbody, "tr");
3335
const parseList = (text: string) => {
3436
return text
3537
.trim()
@@ -54,6 +56,7 @@ const elementsByTag: Record<string, Element> = {};
5456
return item;
5557
}
5658
);
59+
categories.unshift("html-element");
5760
let children = parseList(getTextContent(row.childNodes[4]));
5861
for (const tag of elements) {
5962
// textarea does not have value attribute and text content is used as initial value
@@ -78,6 +81,39 @@ const elementsByTag: Record<string, Element> = {};
7881
}
7982
}
8083

84+
{
85+
const svg = await loadSvgSinglePage();
86+
const document = parseHtml(svg);
87+
const summaries = findByClasses(document, "element-summary");
88+
for (const summary of summaries) {
89+
const [name] = findByClasses(summary, "element-summary-name").map((item) =>
90+
getTextContent(item).slice(1, -1)
91+
);
92+
const children: string[] = [];
93+
const [dl] = findByTags(summary, "dl");
94+
for (let index = 0; index < dl.childNodes.length; index += 1) {
95+
const child = dl.childNodes[index];
96+
if (getTextContent(child).toLowerCase().includes("content model")) {
97+
const dd = dl.childNodes[index + 1];
98+
for (const elementName of findByClasses(dd, "element-name")) {
99+
children.push(getTextContent(elementName).slice(1, -1));
100+
}
101+
}
102+
}
103+
if (elementsByTag[name]) {
104+
console.info(`${name} element from SVG specification is skipped`);
105+
continue;
106+
}
107+
const categories = name === "svg" ? ["flow", "phrasing"] : ["none"];
108+
categories.unshift("svg-element");
109+
elementsByTag[name] = {
110+
description: "",
111+
categories,
112+
children,
113+
};
114+
}
115+
}
116+
81117
const contentModel = `type Element = {
82118
description: string;
83119
categories: string[];

0 commit comments

Comments
 (0)