From 77a97c184e4b2ad0d16bc5670482ef0f5fea90b6 Mon Sep 17 00:00:00 2001 From: g-a-v-i-n Date: Wed, 20 Jul 2022 22:48:46 -0400 Subject: [PATCH] wip: validator for svg tags and properties --- lib/forge-std | 2 +- packages/inspector/hooks/useFormattedSVG.tsx | 48 +++++++++++++++++++- packages/inspector/package.json | 2 + packages/inspector/pages/index.tsx | 4 +- pnpm-lock.yaml | 12 +++++ 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index 27e14b7..9825609 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 27e14b7f2448e5f5ac32719f51fe652aa0b0733e +Subproject commit 98256095f37d83653ba1617d06ccd9604cbd7c6d diff --git a/packages/inspector/hooks/useFormattedSVG.tsx b/packages/inspector/hooks/useFormattedSVG.tsx index b5d8d4e..b64a50b 100644 --- a/packages/inspector/hooks/useFormattedSVG.tsx +++ b/packages/inspector/hooks/useFormattedSVG.tsx @@ -1,7 +1,8 @@ import { useState, useEffect } from "react"; -// import babelParser from 'prettier/parser-babel' import htmlParser from "prettier/parser-html"; import prettier from "prettier/standalone"; +import { parse } from "svg-parser"; +import { svgElementAttributes } from "svg-element-attributes"; const options = { parser: "html", @@ -25,7 +26,12 @@ export const useFormattedSvg = (svg: string) => { if (svg === null) { setFormattedSvg(""); } else { - setFormattedSvg(formatSVG(svg)); + const ast = parse(svg); + + validateTagsAndProperties(ast); + + const formatted = formatSVG(svg); + setFormattedSvg(formatted); } } catch (e) { setError(e.message); @@ -34,3 +40,41 @@ export const useFormattedSvg = (svg: string) => { return { formattedSvg, error }; }; + +// Overrides +svgElementAttributes.svg.push("xmlns", "xmlns:xlink"); +svgElementAttributes.mpath.push("xlink:href"); + +const validTags = Object.keys(svgElementAttributes).filter((tag) => tag !== "*"); + +function validateTagsAndProperties(node) { + if (node.children) { + node.children.forEach((child) => { + + if (child.type === "element") { + + // Check tag names first + if (validTags.includes(child.tagName) === false) { + // somehow write a traceback that ascends the tree + throw Error(`Invalid tag name: ${child.tagName}`); + } + + // Check attributes + const validPropertiesForThisTag = [ + ...svgElementAttributes[child.tagName], + ...svgElementAttributes["*"], + ]; + const invalidProperties = Object.keys(child.properties).filter( + (attr) => validPropertiesForThisTag.includes(attr) === false + ); + + if (invalidProperties.length > 0) { + throw Error( + `Invalid attributes for tag ${child.tagName}: ${invalidProperties.join(", ")}` + ); + } + } + validateTagsAndProperties(child); + }); + } +} diff --git a/packages/inspector/package.json b/packages/inspector/package.json index a2f799c..b570989 100644 --- a/packages/inspector/package.json +++ b/packages/inspector/package.json @@ -26,6 +26,8 @@ "react-dom": "^18.0.0", "react-icons": "^4.3.1", "react-syntax-highlighter": "^15.5.0", + "svg-element-attributes": "^2.0.1", + "svg-parser": "^2.0.4", "ts-node": "^10.7.0", "typescript": "^4.6.3", "xml-formatter": "^2.6.1" diff --git a/packages/inspector/pages/index.tsx b/packages/inspector/pages/index.tsx index b06b54c..6473510 100644 --- a/packages/inspector/pages/index.tsx +++ b/packages/inspector/pages/index.tsx @@ -68,8 +68,8 @@ export default function Index() { {error.length === 0 ? null : ( <> -
-
Error parsing SVG
+
+
Error parsing SVG
{error}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccc60cc..d009a70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,8 @@ importers: react-dom: ^18.0.0 react-icons: ^4.3.1 react-syntax-highlighter: ^15.5.0 + svg-element-attributes: ^2.0.1 + svg-parser: ^2.0.4 tailwindcss: ^3.1.4 ts-node: ^10.7.0 typescript: ^4.6.3 @@ -69,6 +71,8 @@ importers: react-dom: 18.0.0_react@18.0.0 react-icons: 4.3.1_react@18.0.0 react-syntax-highlighter: 15.5.0_react@18.0.0 + svg-element-attributes: 2.0.1 + svg-parser: 2.0.4 ts-node: 10.7.0_52efxrzidnucw2w35vvobhxasa typescript: 4.6.3 xml-formatter: 2.6.1 @@ -3818,6 +3822,14 @@ packages: engines: {node: '>= 0.4'} dev: true + /svg-element-attributes/2.0.1: + resolution: {integrity: sha512-d4U6t13gf8du5wLRRX7wd0sYp7QQrkvncB+v419Y0IdUIYu90KsScWOFnktyBzE9y9X5/CYLUft60ZxgED3bjA==} + dev: false + + /svg-parser/2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + dev: false + /table/5.4.6: resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==} engines: {node: '>=6.0.0'}