1
- //HTML Widget Version 5.11
1
+ //HTML Widget Version 6.00
2
2
//https://github.com/Normal-Tangerine8609/Scriptable-HTML-Widget
3
3
4
4
module . exports = async function htmlWidget ( input , debug , addons ) {
5
+
5
6
// Primitive types for adding and validating
6
7
const types = {
7
8
colour : {
@@ -436,22 +437,64 @@ module.exports = async function htmlWidget(input, debug, addons) {
436
437
}
437
438
}
438
439
}
440
+
441
+ // Function to parse a HTML
442
+ function parseHTML ( string ) {
443
+ // Declare the parser
444
+ let parser = new XMLParser ( string )
439
445
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
449
469
}
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 )
450
489
}
490
+ if ( ! main . isRoot ) {
491
+ error ( 25 , main . name )
492
+ }
493
+
494
+ // Parse and return the root
495
+ parser . parse ( )
496
+ return main
451
497
}
452
- // https://github.com/henryluki/html-parser
453
- // Added comment support
454
- const STARTTAG_REX = / ^ < ( [ - A - Z a - z 0 - 9 _ ] + ) ( (?: \s + [ a - z A - Z _ : ] [ - a - z A - Z 0 - 9 _ : . ] * (?: \s * = \s * (?: (?: " [ ^ " ] * " ) | (?: ' [ ^ ' ] * ' ) | [ ^ > \s ] + ) ) ? ) * ) \s * ( \/ ? ) > / , ENDTAG_REX = / ^ < \/ ( [ - A - Z a - z 0 - 9 _ ] + ) [ ^ > ] * > / , ATTR_REX = / ( [ a - z A - Z _ : ] [ - a - z A - Z 0 - 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 ) ) }
455
498
456
499
// Set base variables
457
500
let currentStack ,
@@ -460,8 +503,8 @@ module.exports = async function htmlWidget(input, debug, addons) {
460
503
gradientNumber = - 1
461
504
462
505
// 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" ) {
465
508
return element
466
509
}
467
510
} ) [ 0 ]
@@ -470,15 +513,10 @@ module.exports = async function htmlWidget(input, debug, addons) {
470
513
error ( 17 )
471
514
}
472
515
// 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" )
474
517
let cssTexts = ""
475
518
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
482
520
}
483
521
let mainCss = [ ]
484
522
let rules = cssTexts . match ( / [ \s \S ] + ?{ [ \s \S ] * ?} / g) || [ ]
@@ -565,13 +603,13 @@ module.exports = async function htmlWidget(input, debug, addons) {
565
603
}
566
604
return root
567
605
}
568
-
606
+
569
607
// repeat with all rules on all tags and see if they fit the criteria
570
608
for ( let rule of mainCss ) {
571
609
applyCss ( widgetBody , rule )
572
610
}
573
611
function applyCss ( tag , rule ) {
574
- if ( tag . type == "Text" || tag . tagName == "style" ) {
612
+ if ( tag . name == "style" ) {
575
613
return
576
614
}
577
615
addCss ( tag , rule , 0 )
@@ -582,21 +620,21 @@ module.exports = async function htmlWidget(input, debug, addons) {
582
620
}
583
621
}
584
622
function addCss ( tag , rule , index ) {
585
- if ( tag . type == "Text" || tag . tagName == "style" ) {
623
+ if ( tag . name == "style" ) {
586
624
return
587
625
}
588
626
for ( let cssClass of rule . selector [ index ] . classes ) {
589
- if ( ! tag . attributes . class ) {
627
+ if ( ! tag . attrs . class ) {
590
628
return
591
629
}
592
- if ( ! tag . attributes . class . split ( " " ) . includes ( cssClass ) ) {
630
+ if ( ! tag . attrs . class . split ( " " ) . includes ( cssClass ) ) {
593
631
return
594
632
}
595
633
}
596
634
if (
597
635
rule . selector [ index ] . tag &&
598
636
rule . selector [ index ] . tag !== "*" &&
599
- rule . selector [ index ] . tag !== tag . tagName
637
+ rule . selector [ index ] . tag !== tag . name
600
638
) {
601
639
return
602
640
}
@@ -626,40 +664,31 @@ module.exports = async function htmlWidget(input, debug, addons) {
626
664
// Compile function
627
665
async function compile ( tag ) {
628
666
// Do nothing when compile normal text or css
629
- if ( tag . type == "Text" || tag . tagName == "style" ) {
667
+ if ( tag . type == "Text" || tag . name == "style" ) {
630
668
return
631
669
}
632
670
// Throw an error if there is a nestled widget tag
633
- if ( tag . tagName == "widget" && code ) {
671
+ if ( tag . name == "widget" && code ) {
634
672
error ( 19 )
635
673
}
636
674
// 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 ] ++
639
677
} else {
640
- incrementors [ tag . tagName ] = 0
678
+ incrementors [ tag . name ] = 0
641
679
}
642
- let incrementor = incrementors [ tag . tagName ]
680
+ let incrementor = incrementors [ tag . name ]
643
681
// 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
653
683
. replace ( / & l t ; / g, "<" )
654
684
. replace ( / & g t / g, ">" )
655
685
. replace ( / & a m p ; / g, "&" )
656
686
. replace ( / \n \s + / g, "\\n" )
657
-
658
687
let attributeCss = { }
659
688
660
689
// 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 ( )
663
692
if ( key !== "class" ) {
664
693
attributeCss [ key ] = value
665
694
}
@@ -668,7 +697,7 @@ module.exports = async function htmlWidget(input, debug, addons) {
668
697
let finalCss = { }
669
698
if ( tag . css ) {
670
699
for ( let rule of tag . css ) {
671
- if ( ! Object . keys ( tag . attributes ) . includes ( "no-css" ) ) {
700
+ if ( ! Object . keys ( tag . attrs ) . includes ( "no-css" ) ) {
672
701
for ( let key of Object . keys ( rule . css ) ) {
673
702
delete finalCss [ key ]
674
703
finalCss [ key ] = rule . css [ key ]
@@ -684,7 +713,7 @@ module.exports = async function htmlWidget(input, debug, addons) {
684
713
685
714
// Switch for each tag name
686
715
let mapping , linesBefore , codeLines
687
- switch ( tag . tagName ) {
716
+ switch ( tag . name ) {
688
717
case "spacer" :
689
718
// Add the spacer to the code and validate for the space attribute
690
719
code += `\nlet spacer${ incrementor } = ${ currentStack } .addSpacer(${
@@ -929,25 +958,25 @@ module.exports = async function htmlWidget(input, debug, addons) {
929
958
break
930
959
default :
931
960
// 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 " ] )
934
963
}
935
- code += `\n// <${ tag . tagName } >`
964
+ code += `\n// <${ tag . name } >`
936
965
// Run the addon
937
- await addons [ tag [ "tagName" ] ] (
966
+ await addons [ tag . name ] (
938
967
validate ,
939
968
template ,
940
969
update ,
941
970
finalCss ,
942
971
attributeCss ,
943
972
innerText
944
973
)
945
- code += `\n// </${ tag . tagName } >`
974
+ code += `\n// </${ tag . name } >`
946
975
return
947
976
// Function to add the raw html of the addon to the widget
948
977
async function template ( input ) {
949
978
// Parse the template
950
- let parsedInput = htmlParser ( input )
979
+ let parsedInput = parseHTML ( input )
951
980
// Run through all children to determine where to put the tag children and add the no-css attribute
952
981
parsedInput . children = parsedInput . children . map ( ( e ) =>
953
982
putChildren ( e , tag . children )
@@ -971,35 +1000,21 @@ module.exports = async function htmlWidget(input, debug, addons) {
971
1000
code = codeLines . join ( "\n" )
972
1001
// Function to add the no-css attribute to all children and put the tag children into the template
973
1002
function putChildren ( tag , children ) {
974
-
975
1003
if ( tag . children ) {
976
1004
tag . children . map ( ( e ) => {
977
1005
putChildren ( e , children )
978
1006
} )
979
1007
}
980
- if (
981
- tag . attributes &&
982
- Object . keys ( tag . attributes ) . includes ( "children" )
983
- ) {
1008
+ if ( Object . keys ( tag . attrs ) . includes ( "children" ) ) {
984
1009
for ( let item of children ) {
985
1010
tag . children . push ( item )
986
1011
}
987
1012
}
988
- if ( tag . attributes ) {
989
- tag . attributes [ "no-css" ] = ""
990
- }
1013
+ tag . attrs [ "no-css" ] = ""
991
1014
return tag
992
1015
}
993
1016
}
994
1017
}
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
- }
1003
1018
}
1004
1019
// Function that validated all attributes and css
1005
1020
function validate ( attributeCss , finalCss , mapping ) {
@@ -1141,7 +1156,9 @@ module.exports = async function htmlWidget(input, debug, addons) {
1141
1156
22 : "Unknown attribute: `{}`" ,
1142
1157
23 : "`{}` attribute must be a {} type: `{}`" ,
1143
1158
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: <{}>"
1145
1162
}
1146
1163
let error = errors [ number ]
1147
1164
let params = [ ...arguments ]
0 commit comments