Skip to content

Instantly share code, notes, and snippets.

@SpiffGreen
Last active August 25, 2021 22:28
Show Gist options
  • Save SpiffGreen/dfd77f18e63e143c13f445d30d315e58 to your computer and use it in GitHub Desktop.
Save SpiffGreen/dfd77f18e63e143c13f445d30d315e58 to your computer and use it in GitHub Desktop.
A simple implementation of ast-to-html and html-to-ast algorithms
const { htmlToAST, toks } = require("./html-to-ast");
const SELF_CLOSING_TAG = "SELF_CLOSING_TAG";
const OPENING_TAG = "OPENING_TAG";
const TEXT_ELEMENT = "TEXT_ELEMENT";
function astToHTML(ast) {
const start = "<" + ast.TagName + ">";
let mid = "";
const end = "</" + ast.TagName + ">";
while(ast.children.length > 0) {
const child = ast.children.shift();
switch(child.classification) {
case TEXT_ELEMENT:
mid += child.value;
break;
case OPENING_TAG:
mid += astToHTML(child);
break;
case SELF_CLOSING_TAG:
const selfClosing = "<" + child.TagName + " " + child.attribStrings + "/>";
mid += selfClosing;
break;
default:
throw new Error("Sorry, an error occurred while parsing ast");
}
}
return ast.TagName ? start + mid + end : mid;
}
console.log(astToHTML(htmlToAST(toks, {})));
"use strict";
// Constants
const CODE_EXAMPLE = `<p>hello <b>Spiff</b> <hr / >this is my picture <img src="/bill.png" /> is blah blak <br /> </p>`;
const SELF_CLOSING_TAG = "SELF_CLOSING_TAG";
const CLOSING_TAG = "CLOSING_TAG";
const OPENING_TAG = "OPENING_TAG";
const TEXT_ELEMENT = "TEXT_ELEMENT";
// Assertions
function isOpenTag(str) {
return str.match(/^<\s*[a-z]+\s*[^>]*>$/g) ? true : false;
}
function isSelfClosingTag(str) {
if(!str.trim().startsWith("<")) return false;
return str.match(/\/\s*>/g) ? true : false;
}
function isClosingTag(str) {
if(!str.trim().startsWith("<")) return false;
return str.match(/^<\s*\/\s*[a-zA-Z]+\s*>$/g);
}
// Classes to handle token types
class OpenHTMLTag {
constructor(token) {
this.type = "tag";
this.TagName = "";
this.classification = OPENING_TAG;
this.compile(token);
}
compile(token) {
const reg = /<\s*([a-zA-Z]+)\s*([^>]*)>/g;
let match = reg.exec(token.trim());
this.TagName = match[1];
this.attribStrings = match[2];
}
}
class ClosingHTMLTag {
constructor(token) {
this.type = "tag";
this.TagName = "";
this.classification = CLOSING_TAG;
this.compile(token);
}
compile(token) {
const reg = /<\s*\/([a-zA-Z]+)\s*>/g;
let match = reg.exec(token.trim());
this.TagName = match[1];
}
}
class SelfClosingHTMLTag {
constructor(token) {
this.type = "tag";
this.TagName = "";
this.classification = SELF_CLOSING_TAG;
this.compile(token);
}
compile(token) {
const reg = /<\s*([a-zA-Z]+)\s*([^>]*)\/\s*>/g;
let match = reg.exec(token.trim());
this.TagName = match[1];
this.attribStrings = match[2];
}
}
class Text {
constructor(token) {
this.classification = TEXT_ELEMENT;
this.value = token;
this.type = "text";
}
}
/**
* @description A simple function to convert the said into tokens
* @param {String} str Code to tokenize
* @returns {Array<String>} An array of tokens
*/
function tokenize(str) {
return str.match(/<[^>]+>|[A-Za-z0-9\s]*/g);
}
let tokens = tokenize(CODE_EXAMPLE);
const toks = tokens.map(i => {
if(isSelfClosingTag(i)) return new SelfClosingHTMLTag(i);
else if(isClosingTag(i)) return new ClosingHTMLTag(i);
else if(isOpenTag(i)) return new OpenHTMLTag(i);
else return new Text(i);
});
module.exports.toks = toks;
module.exports.htmlToAST = function htmlToAST(toks, ast = {}) {
ast.children = [];
while(toks.length > 0) {
const item = toks.shift();
switch(item.classification) {
case SELF_CLOSING_TAG:
ast.children.push(item);
break;
case OPENING_TAG:
ast.children.push(htmlToAST(toks, item));
break;
case CLOSING_TAG:
return ast;
break;
case TEXT_ELEMENT:
ast.children.push(item);
break;
default:
throw new Error("Sorry, an error occured while parsing code");
}
}
return ast;
}
// console.log(experimentalParse(toks));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment