Skip to content

Instantly share code, notes, and snippets.

@rhengles
Created March 18, 2021 21:21
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save rhengles/d70bb32452fa182d28425e4e89dbbfd4 to your computer and use it in GitHub Desktop.
Parse HTML string into React Elements
import React from "react";
import { htmlFragmentToReact } from "./html-to-element";
import { allowTag, parserModify } from "./html-post-parse-fns";
export default function Component(props) {
return <div className="component-example">
{htmlFragmentToReact(postNode.html, null, React.createElement, allowTag, parserModify)}
</div>;
}
import { Fragment } from "react";
export function allowTextNotInTable (el, elAdapter, parents) {
const lastParent = parents.slice(-1)[0];
const pname = lastParent && elAdapter.nameGet(lastParent);
switch (pname) {
case 'table':
case 'tbody':
case 'thead':
case 'tr':
return;
default:
return el;
}
}
export function allowTagBasic (el, elAdapter, parents) {
if (elAdapter.isFragment(el)) return Fragment;
if (elAdapter.isText(el)) return allowTextNotInTable (el, elAdapter, parents);
const name = elAdapter.nameGet(el);
switch (name) {
case 'hr':
this.allowChildren(false);
break;
}
switch (name) {
case 'br':
case 'del':
case 'em':
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
case 'p':
case 'span':
case 'strong':
return name;
default:
console.warn(name, `parse html ignorou tag "${name}"`, el);
}
}
export function allowTag (el, elAdapter, parents) {
if (elAdapter.isFragment(el)) return Fragment;
if (elAdapter.isText(el)) return allowTextNotInTable (el, elAdapter, parents);
const name = elAdapter.nameGet(el);
switch (name) {
case 'hr':
this.allowChildren(false);
break;
}
switch (name) {
case 'a':
case 'blockquote':
case 'br':
case 'code':
case 'dd':
case 'del':
case 'div':
case 'dl':
case 'dt':
case 'em':
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
case 'hr':
case 'img':
case 'li':
case 'ol':
case 'p':
case 'path':
case 'pre':
case 'span':
case 'strong':
case 'svg':
case 'table':
case 'tbody':
case 'td':
case 'th':
case 'thead':
case 'tr':
case 'ul':
return name;
default:
console.warn(name, `parse html ignorou tag "${name}"`, el);
}
}
const reClass = /^class$/i;
const reStyle = /^style$/i;
const reFillRule = /^fill-rule$/i;
export function parserModify(ev) {
switch (ev.name) {
case 'tagAttribute':
const {attr, tag} = ev;
const {name: attrName} = attr;
if (reStyle.test(attrName)) {
if ('position:relative;' !== attr.value) {
console.warn(`html com style`, attr, tag, ev);
}
return;
} else if (reClass.test(attrName)) {
// console.warn(`Page templates/post: html com class`, attr, tag, ev);
attr.name = 'className';
} else if (reFillRule.test(attrName)) {
// console.warn(`Page templates/post: html com class`, attr, tag, ev);
attr.name = 'fillRule';
}
break;
}
return ev;
}
import XMLParser from "@arijs/stream-xml-parser/src/xmlparser";
import TreeBuilder from "@arijs/stream-xml-parser/src/treebuilder";
import htmlVoidTagMap from "@arijs/stream-xml-parser/src/htmlvoidtagmap";
// import reactAdapter from "./element-adapter-react";
import elDefault from "@arijs/stream-xml-parser/src/element/default";
export default function htmlToElement(html, evModify = x => x) {
const elAdapter = elDefault();
const tb = new TreeBuilder({
element: elAdapter,
tagVoidMap: htmlVoidTagMap,
});
const xp = new XMLParser((ev) => (ev = evModify(ev)) && tb.parserEvent(ev));
xp.end(html);
const root = tb.root.tag;
// const children = elAdapter.childrenGet(root);
// const toReact = list => list.flatMap(el => elToReact(el, elAdapter, createElement, allowTag))
return { root, elAdapter, tb, xp };
}
export function htmlFragmentToReact(html, meta, createElement, allowTag, evModify) {
const {root, elAdapter} = htmlToElement(html, evModify);
return elToReact(root, elAdapter, meta, createElement, allowTag);
}
export function htmlChildrenToReact(html, meta, createElement, allowTag, evModify) {
const {root, elAdapter} = htmlToElement(html, evModify);
return listToReact(elAdapter.childrenGet(root), elAdapter, meta, createElement, allowTag, [root]);
}
export function listToReact(list, elAdapter, meta, createElement, allowTag, parents) {
return list.flatMap(el => elToReact(el, elAdapter, meta, createElement, allowTag, parents));
}
export function elToReact(el, elAdapter, meta, createElement, allowTag, parents = []) {
let allowChildren = true;
const fnCtx = {
allowChildren: v => allowChildren = v,
};
if (
// elAdapter.isFragment(el) ||
elAdapter.isComment(el) ||
elAdapter.isDeclaration(el) ||
elAdapter.isInstruction(el)
) {
return [];
}
if (
elAdapter.isText(el)
) {
const elText = allowTag.call(fnCtx, el, elAdapter, parents);
return elText ? [elAdapter.textValueGet(elText)] : [];
}
// elAdapter.isFragment(el) ? Fragment :
const type = allowTag.call(fnCtx, el, elAdapter, parents);
if (!type) return [];
let children = allowChildren
? listToReact(elAdapter.childrenGet(el), elAdapter, meta, createElement, allowTag, parents.concat(el))
: [];
// if (children.length === 1) children = children[0];
const props = {...meta};
elAdapter.attrsEach(el, (name, value) => {
switch (name) {
case 'key':
case 'ref':
case 'children':
console.warn(`Element/React: Ignored property "${name}" on`, el);
return;
default: props[name] = value;
}
});
return createElement(type, props, ...children);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment