Skip to content

Commit 76d8afa

Browse files
Update module.js
1 parent 73f76a6 commit 76d8afa

File tree

1 file changed

+88
-71
lines changed

1 file changed

+88
-71
lines changed

module/module.js

Lines changed: 88 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
//HTML Widget Version 5.11
1+
//HTML Widget Version 6.00
22
//https://github.com/Normal-Tangerine8609/Scriptable-HTML-Widget
33

44
module.exports = async function htmlWidget(input, debug, addons) {
5+
56
// Primitive types for adding and validating
67
const types = {
78
colour: {
@@ -436,22 +437,64 @@ module.exports = async function htmlWidget(input, debug, addons) {
436437
}
437438
}
438439
}
440+
441+
// Function to parse a HTML
442+
function parseHTML(string) {
443+
// Declare the parser
444+
let parser = new XMLParser(string)
439445

440-
// Format addons to only the functions and find if they are self closing
441-
let selfClosers = ["img", "spacer"]
442-
if (addons) {
443-
for (let addon of Object.keys(addons)) {
444-
if (typeof addons[addon] == "object") {
445-
if (addons[addon].isSelfClosing) {
446-
selfClosers.push(addon)
447-
}
448-
addons[addon] = addons[addon].func
446+
// Root of html
447+
let main = {
448+
isRoot: true,
449+
name: "root",
450+
children: []
451+
}
452+
453+
// Node to add onto
454+
let target = main
455+
456+
// Store symbols to go back to parent nodes
457+
let goBack = {}
458+
459+
// Store the new node and switch targets
460+
parser.didStartElement = (name, attrs) => {
461+
let backTo = Symbol("unique")
462+
goBack[backTo] = target
463+
let newTarget = {
464+
name,
465+
attrs,
466+
innerText: "",
467+
children: [],
468+
end: backTo
449469
}
470+
target.children.push(newTarget)
471+
target = newTarget
472+
}
473+
474+
// Add the inner text to the node
475+
parser.foundCharacters = (text) => {
476+
target.innerText += target.innerText === "" ? text : " " + text
477+
}
478+
479+
// Go back to the parent node
480+
parser.didEndElement = (name) => {
481+
sym = target.end
482+
delete target.end
483+
target = goBack[sym]
484+
}
485+
486+
// Throw error on invalid input
487+
parser.parseErrorOccurred = () => {
488+
error(14)
450489
}
490+
if (!main.isRoot) {
491+
error(25, main.name)
492+
}
493+
494+
// Parse and return the root
495+
parser.parse()
496+
return main
451497
}
452-
// https://github.com/henryluki/html-parser
453-
// Added comment support
454-
const STARTTAG_REX=/^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,ENDTAG_REX=/^<\/([-A-Za-z0-9_]+)[^>]*>/,ATTR_REX=/([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;function makeMap(t){return t.split(",").reduce((t,e)=>(t[e]=!0,t),{})}const EMPTY_MAKER=makeMap(selfClosers.join(",")),FILLATTRS_MAKER=makeMap("no-css,children");function isEmptyMaker(t){return!!EMPTY_MAKER[t]}function isFillattrsMaker(t){return!!FILLATTRS_MAKER[t]}class TagStart{constructor(t,e){this.name=t,this.attributes=this.getAttributes(e)}getAttributes(t){let e={};return t.replace(ATTR_REX,function(t,n){const s=Array.prototype.slice.call(arguments),r=s[2]?s[2]:s[3]?s[3]:s[4]?s[4]:isFillattrsMaker(n)?n:"";e[n]=r.replace(/(^|[^\\])"/g,'$1\\"')}),e}}class TagEmpty extends TagStart{constructor(t,e){super(t,e)}}class TagEnd{constructor(t){this.name=t}}class Text{constructor(t){this.text=t}}const ElEMENT_TYPE="Element",TEXT_TYPE="Text";function createElement(t){const e=t.name,n=t.attributes;return t instanceof TagEmpty?{type:ElEMENT_TYPE,tagName:e,attributes:n}:{type:ElEMENT_TYPE,tagName:e,attributes:n,children:[]}}function createText(t){const e=t.text;return{type:TEXT_TYPE,content:e}}function createNodeFactory(t,e){switch(t){case ElEMENT_TYPE:return createElement(e);case TEXT_TYPE:return createText(e)}}function parse(t){let e={tag:"root",children:[]},n=[e];n.last=(()=>n[n.length-1]);for(let e=0;e<t.length;e++){const s=t[e];if(s instanceof TagStart){const t=createNodeFactory(ElEMENT_TYPE,s);t.children?n.push(t):n.last().children.push(t)}else if(s instanceof TagEnd){let t=n[n.length-2],e=n.pop();t.children.push(e)}else s instanceof Text?n.last().children.push(createNodeFactory(TEXT_TYPE,s)):"Comment"!=s.type||n.last().children.push(s)}return e}function tokenize(t){let e=t,n=[];const s=Date.now()+1e3;for(;e;){if(0===e.indexOf("\x3c!--")){const t=e.indexOf("--\x3e")+3;n.push({type:"Comment",text:e.substring(4,t-3)}),e=e.substring(t);continue}if(0===e.indexOf("</")){const t=e.match(ENDTAG_REX);if(!t)continue;e=e.substring(t[0].length);const s=t[1];if(isEmptyMaker(s))continue;n.push(new TagEnd(s));continue}if(0===e.indexOf("<")){const t=e.match(STARTTAG_REX);if(!t)continue;e=e.substring(t[0].length);const s=t[1],r=t[2],a=isEmptyMaker(s)?new TagEmpty(s,r):new TagStart(s,r);n.push(a);continue}const t=e.indexOf("<"),r=t<0?e:e.substring(0,t);if(e=t<0?"":e.substring(t),n.push(new Text(r)),Date.now()>=s)break}return n}function htmlParser(t){return parse(tokenize(t))}
455498

456499
// Set base variables
457500
let currentStack,
@@ -460,8 +503,8 @@ module.exports = async function htmlWidget(input, debug, addons) {
460503
gradientNumber = -1
461504

462505
// Get only the first widget tag
463-
let widgetBody = htmlParser(input).children.filter((element) => {
464-
if (element.tagName == "widget") {
506+
let widgetBody = parseHTML(input).children.filter((element) => {
507+
if (element.name == "widget") {
465508
return element
466509
}
467510
})[0]
@@ -470,15 +513,10 @@ module.exports = async function htmlWidget(input, debug, addons) {
470513
error(17)
471514
}
472515
// Get all direct style tags
473-
let styleTags = widgetBody.children.filter((e) => e.tagName == "style")
516+
let styleTags = widgetBody.children.filter((e) => e.name == "style")
474517
let cssTexts = ""
475518
for (let styleTag of styleTags) {
476-
// Get all text children
477-
for (let item of styleTag.children) {
478-
if (item.type == "Text") {
479-
cssTexts += "\n" + item.content.trim()
480-
}
481-
}
519+
cssTexts += "\n" + styleTag.innerText
482520
}
483521
let mainCss = []
484522
let rules = cssTexts.match(/[\s\S]+?{[\s\S]*?}/g) || []
@@ -565,13 +603,13 @@ module.exports = async function htmlWidget(input, debug, addons) {
565603
}
566604
return root
567605
}
568-
606+
569607
// repeat with all rules on all tags and see if they fit the criteria
570608
for (let rule of mainCss) {
571609
applyCss(widgetBody, rule)
572610
}
573611
function applyCss(tag, rule) {
574-
if (tag.type == "Text" || tag.tagName == "style") {
612+
if (tag.name == "style") {
575613
return
576614
}
577615
addCss(tag, rule, 0)
@@ -582,21 +620,21 @@ module.exports = async function htmlWidget(input, debug, addons) {
582620
}
583621
}
584622
function addCss(tag, rule, index) {
585-
if (tag.type == "Text" || tag.tagName == "style") {
623+
if (tag.name == "style") {
586624
return
587625
}
588626
for (let cssClass of rule.selector[index].classes) {
589-
if (!tag.attributes.class) {
627+
if (!tag.attrs.class) {
590628
return
591629
}
592-
if (!tag.attributes.class.split(" ").includes(cssClass)) {
630+
if (!tag.attrs.class.split(" ").includes(cssClass)) {
593631
return
594632
}
595633
}
596634
if (
597635
rule.selector[index].tag &&
598636
rule.selector[index].tag !== "*" &&
599-
rule.selector[index].tag !== tag.tagName
637+
rule.selector[index].tag !== tag.name
600638
) {
601639
return
602640
}
@@ -626,40 +664,31 @@ module.exports = async function htmlWidget(input, debug, addons) {
626664
// Compile function
627665
async function compile(tag) {
628666
// Do nothing when compile normal text or css
629-
if (tag.type == "Text" || tag.tagName == "style") {
667+
if (tag.type == "Text" || tag.name == "style") {
630668
return
631669
}
632670
// Throw an error if there is a nestled widget tag
633-
if (tag.tagName == "widget" && code) {
671+
if (tag.name == "widget" && code) {
634672
error(19)
635673
}
636674
// Increment incrementor
637-
if (incrementors[tag.tagName] || incrementors[tag.tagName] == 0) {
638-
incrementors[tag.tagName]++
675+
if (incrementors[tag.name] || incrementors[tag.name] == 0) {
676+
incrementors[tag.name]++
639677
} else {
640-
incrementors[tag.tagName] = 0
678+
incrementors[tag.name] = 0
641679
}
642-
let incrementor = incrementors[tag.tagName]
680+
let incrementor = incrementors[tag.name]
643681
// Get innerText
644-
let textArray = []
645-
tag.children = tag.children || []
646-
for (let item of tag.children) {
647-
if (item.type == "Text") {
648-
textArray.push(item.content.trim())
649-
}
650-
}
651-
let innerText = textArray
652-
.join(" ")
682+
let innerText = tag.innerText
653683
.replace(/&lt;/g, "<")
654684
.replace(/&gt/g, ">")
655685
.replace(/&amp;/g, "&")
656686
.replace(/\n\s+/g, "\\n")
657-
658687
let attributeCss = {}
659688

660689
// Add attributes to css
661-
for (let key of Object.keys(tag.attributes)) {
662-
let value = tag.attributes[key].trim()
690+
for (let key of Object.keys(tag.attrs)) {
691+
let value = tag.attrs[key].trim()
663692
if (key !== "class") {
664693
attributeCss[key] = value
665694
}
@@ -668,7 +697,7 @@ module.exports = async function htmlWidget(input, debug, addons) {
668697
let finalCss = {}
669698
if (tag.css) {
670699
for (let rule of tag.css) {
671-
if (!Object.keys(tag.attributes).includes("no-css")) {
700+
if (!Object.keys(tag.attrs).includes("no-css")) {
672701
for (let key of Object.keys(rule.css)) {
673702
delete finalCss[key]
674703
finalCss[key] = rule.css[key]
@@ -684,7 +713,7 @@ module.exports = async function htmlWidget(input, debug, addons) {
684713

685714
// Switch for each tag name
686715
let mapping, linesBefore, codeLines
687-
switch (tag.tagName) {
716+
switch (tag.name) {
688717
case "spacer":
689718
// Add the spacer to the code and validate for the space attribute
690719
code += `\nlet spacer${incrementor} = ${currentStack}.addSpacer(${
@@ -929,25 +958,25 @@ module.exports = async function htmlWidget(input, debug, addons) {
929958
break
930959
default:
931960
// Throw an error if it is not a valid addon
932-
if (!Object.keys(addons).includes(tag["tagName"])) {
933-
error(21, tag["tagName"])
961+
if (!Object.keys(addons).includes(tag.name)) {
962+
error(21, tag["name"])
934963
}
935-
code += `\n// <${tag.tagName}>`
964+
code += `\n// <${tag.name}>`
936965
// Run the addon
937-
await addons[tag["tagName"]](
966+
await addons[tag.name](
938967
validate,
939968
template,
940969
update,
941970
finalCss,
942971
attributeCss,
943972
innerText
944973
)
945-
code += `\n// </${tag.tagName}>`
974+
code += `\n// </${tag.name}>`
946975
return
947976
// Function to add the raw html of the addon to the widget
948977
async function template(input) {
949978
// Parse the template
950-
let parsedInput = htmlParser(input)
979+
let parsedInput = parseHTML(input)
951980
// Run through all children to determine where to put the tag children and add the no-css attribute
952981
parsedInput.children = parsedInput.children.map((e) =>
953982
putChildren(e, tag.children)
@@ -971,35 +1000,21 @@ module.exports = async function htmlWidget(input, debug, addons) {
9711000
code = codeLines.join("\n")
9721001
// Function to add the no-css attribute to all children and put the tag children into the template
9731002
function putChildren(tag, children) {
974-
9751003
if (tag.children) {
9761004
tag.children.map((e) => {
9771005
putChildren(e, children)
9781006
})
9791007
}
980-
if (
981-
tag.attributes &&
982-
Object.keys(tag.attributes).includes("children")
983-
) {
1008+
if (Object.keys(tag.attrs).includes("children")) {
9841009
for (let item of children) {
9851010
tag.children.push(item)
9861011
}
9871012
}
988-
if (tag.attributes) {
989-
tag.attributes["no-css"] = ""
990-
}
1013+
tag.attrs["no-css"] = ""
9911014
return tag
9921015
}
9931016
}
9941017
}
995-
// Compile for comment
996-
if (tag["type"] == "Comment") {
997-
if (!tag["text"].match(/\n/g)) {
998-
code += `\n// ${tag["text"]}`
999-
} else {
1000-
code += `\n/*\n${tag["text"]}\n*/`
1001-
}
1002-
}
10031018
}
10041019
// Function that validated all attributes and css
10051020
function validate(attributeCss, finalCss, mapping) {
@@ -1141,7 +1156,9 @@ module.exports = async function htmlWidget(input, debug, addons) {
11411156
22: "Unknown attribute: `{}`",
11421157
23: "`{}` attribute must be a {} type: `{}`",
11431158
24: "`{}` property must be a {} type: `{}`",
1144-
26: "`{}` {} must be a valid url or base encoded data link: `{}`"
1159+
26: "`{}` {} must be a valid url or base encoded data link: `{}`",
1160+
14: "A parse error occurred, ensure your widget is formatted properly.",
1161+
25: "A parse error occurred, ensure all self closing tages are closed: <{}>"
11451162
}
11461163
let error = errors[number]
11471164
let params = [...arguments]

0 commit comments

Comments
 (0)