kawa.dev

PostsDiarySlide

Webクリップの仕組みについて

2022-12-25

会社の同僚に相談して、それっぽい実装が出来たので簡単に書きます。

html を Parse して Article タグの中で、再帰的に p,h,img などを取得する。XSS を回避するためにサニタイズして表示すると実現できた。

上記のやり方だと、レンダリング後に色々処理をする部分が取得できないが、それは別途考えようかと思う。また、CORS の問題から、HTML の取得は別途サーバー側で行う必要がある。

コードは以下の通り。

type WebType = { url: string, }; const WebViewer = (props: { web: WebType }) => { const [innerHtml, setInnerHtml] = (useState < undefined) | (string > undefined); useEffect(() => { const parser = new DOMParser(); const doc = parser.parseFromString(props.web.html, "text/html"); const article = doc.querySelector("article"); if (article == null) { // 解析できないエラーを表示する return; } const newDoc = parser.parseFromString("", "text/html"); const htmlElm = newDoc.querySelector("body"); getContentFromHtmlElement(article, (node) => { htmlElm?.append(node); }); setInnerHtml(sanitize(newDoc.body.innerHTML)); }, [props.web, setInnerHtml]); return ( <> {innerHtml && ( <div dangerouslySetInnerHTML={{ __html: innerHtml, }} /> )} </> ); }; const getContentFromHtmlElement = ( element: HTMLElement | ChildNode, addNode: (node: HTMLElement | ChildNode) => void ): HTMLElement | ChildNode | undefined => { const name = element.nodeName; if ( name === "H1" || name === "H2" || name === "H3" || name === "H4" || name === "H5" || name === "H6" || name === "P" || name === "A" || name === "IMG" || name === "UL" || name === "OL" || name === "LI" || name === "BLOCKQUOTE" || name === "PRE" || name === "CODE" || name === "TABLE" || name === "TR" || name === "TD" || name === "TH" || name === "BR" ) { return element; } if (!element.hasChildNodes()) { return undefined; } for (let i = 0; i < element.childNodes.length; i++) { const node = element.childNodes[i]; const getNode = getContentFromHtmlElement(node, addNode); if (getNode != null) { addNode(getNode); } } };