├── project
├── build.properties
└── plugins.sbt
├── .gitignore
├── .github
└── FUNDING.yml
├── README.md
├── src
└── main
│ ├── scala
│ └── simer
│ │ └── html
│ │ └── converter
│ │ ├── utils
│ │ └── ConverterUtil.scala
│ │ ├── tyrian
│ │ ├── TyrianConverter.scala
│ │ ├── TyrianTags.scala
│ │ └── TyrianAttributes.scala
│ │ ├── HTMLTemplate.scala
│ │ ├── ConverterType.scala
│ │ ├── AttributeType.scala
│ │ └── HtmlToScalaConverter.scala
│ └── resources
│ ├── index-opt.html
│ ├── index-dev.html
│ └── style.css
├── LICENSE
└── docs
├── index.html
└── style.css
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.5.5
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1")
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea
3 | .cache
4 | .classpath
5 | .project
6 | .settings/
7 | .bsp
8 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [simerplaha]
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTML converter for Scalatags, Scalajs-react, Laminar, Outwatch & Tyrian
2 |
3 | Online converter: https://simerplaha.github.io/html-to-scala-converter/
4 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/utils/ConverterUtil.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter.utils
2 |
3 | object ConverterUtil {
4 |
5 | def tripleQuote(string: String): String =
6 | string.trim match {
7 | case string if string.contains("\"") || string.contains("\n") || string.contains("\\") =>
8 | s"""\"\"\"$string\"\"\""""
9 |
10 | case string =>
11 | s""""$string""""
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/index-opt.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML to Scala converter
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/resources/index-dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML to Scala converter
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Simer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML to Scala converter
5 |
6 |
7 |
8 |
9 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/tyrian/TyrianConverter.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter.tyrian
2 |
3 | import org.scalajs.dom.ext._
4 | import org.scalajs.dom.raw.Node
5 | import simer.html.converter.ConverterType
6 | import simer.html.converter.tyrian.TyrianAttributes.{Normal, NoValue}
7 | import simer.html.converter.tyrian.TyrianTags.{NoChildren, OptionalChildren}
8 | import simer.html.converter.utils.ConverterUtil._
9 |
10 | import scala.scalajs.js
11 |
12 | object TyrianConverter {
13 | def toScala(node: Node,
14 | converterType: ConverterType,
15 | childrenWithoutGarbageNodes: collection.Seq[Node],
16 | children: String): String = {
17 |
18 | node.nodeName match {
19 | case "#text" =>
20 | tripleQuote(node.nodeValue)
21 | case _ =>
22 |
23 | val attrString = if (js.isUndefined(node.attributes) || node.attributes.isEmpty) {
24 | ""
25 | } else {
26 | val (separatorStart, separator, separatorEnd) = if (!converterType.newLineAttributes) {
27 | ("(\n", ",\n", "\n)")
28 | } else {
29 | ("(", ", ", ")")
30 | }
31 | node.attributes.map { case (attributeKey, attributeValue) =>
32 | val attributeType = TyrianAttributes.attrs.find(a => a.attrName.getOrElse(a.name) == attributeKey)
33 |
34 | attributeType match {
35 | case Some(attr: NoValue) if !converterType.isBooleanTypeConversionDisabled => attr.name
36 | case Some(attr: Normal) => s"${attr.name} := ${tripleQuote(attributeValue.value)}"
37 | case _ => s"Attribute(\"$attributeKey\", ${tripleQuote(attributeValue.value)})"
38 | }
39 | }.mkString(separatorStart, separator, separatorEnd)
40 |
41 | }
42 |
43 | val tagType = TyrianTags.tags.find(t => t.tag.getOrElse(t.name) == node.nodeName.toLowerCase)
44 |
45 | val innerString = tagType match {
46 | case Some(_: NoChildren) | Some(_: OptionalChildren) => ""
47 | case _ if childrenWithoutGarbageNodes.nonEmpty => s"(\n$children)"
48 | case _ => "()"
49 | }
50 |
51 | tagType match {
52 | case Some(tag) => s"${tag.name}$attrString$innerString"
53 | case None => s"tag(\"${node.nodeName.toLowerCase}\")$attrString$innerString"
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/docs/style.css:
--------------------------------------------------------------------------------
1 | ul {
2 | list-style-type: none;
3 | margin: 0;
4 | padding: 0;
5 | overflow: hidden;
6 | background-color: #000000;
7 | box-shadow: 3px 3px 4px #292323;
8 | }
9 |
10 | li {
11 | float: left;
12 | }
13 |
14 | li a {
15 | display: block;
16 | color: white;
17 | text-align: center;
18 | padding: 14px 16px;
19 | text-decoration: none;
20 | }
21 |
22 | li a:hover {
23 | background-color: #000000;
24 | }
25 |
26 | html, body {
27 | width: 100%;
28 | height: 100%;
29 | margin: 0;
30 | padding: 0;
31 | }
32 |
33 | #section {
34 | width: 100%;
35 | min-height: 100%;
36 | display: table;
37 | height: inherit;
38 | }
39 |
40 | .row {
41 | width: 100%;
42 | height: 100%;
43 | display: table-row;
44 | }
45 |
46 | .col-left, .col-right {
47 | width: 50%;
48 | display: table-cell;
49 | vertical-align: middle;
50 | }
51 |
52 | .col-right {
53 | text-align: center;
54 | }
55 |
56 | .content {
57 | /*border: 1px dashed red;*/
58 | }
59 |
60 | h1 {
61 | font-family: sans-serif;
62 | }
63 |
64 | .boxsizingBorder {
65 | -webkit-box-sizing: border-box;
66 | -moz-box-sizing: border-box;
67 | box-sizing: border-box;
68 | }
69 |
70 | .button {
71 | overflow: hidden;
72 | margin: 10px;
73 | padding: 12px 12px;
74 | cursor: pointer;
75 | -webkit-user-select: none;
76 | -moz-user-select: none;
77 | -ms-user-select: none;
78 | user-select: none;
79 | -webkit-transition: all 60ms ease-in-out;
80 | transition: all 60ms ease-in-out;
81 | text-align: center;
82 | white-space: nowrap;
83 | text-decoration: none !important;
84 | text-transform: none;
85 | text-transform: capitalize;
86 | color: #fff;
87 | border: 0 none;
88 | border-radius: 4px;
89 | font-size: 13px;
90 | font-weight: 500;
91 | line-height: 1.3;
92 | -webkit-appearance: none;
93 | -moz-appearance: none;
94 | appearance: none;
95 | -webkit-box-pack: center;
96 | -ms-flex-pack: center;
97 | justify-content: center;
98 | -webkit-box-align: center;
99 | -ms-flex-align: center;
100 | align-items: center;
101 | -webkit-box-flex: 0;
102 | -ms-flex: 0 0 160px;
103 | flex: 0 0 160px;
104 | box-shadow: 2px 5px 10px rgba(22, 22, 22, 0.1);
105 | }
106 | .button:hover {
107 | -webkit-transition: all 60ms ease;
108 | transition: all 60ms ease;
109 | opacity: .85;
110 | }
111 | .button:active {
112 | -webkit-transition: all 60ms ease;
113 | transition: all 60ms ease;
114 | opacity: .75;
115 | }
116 | .button:focus {
117 | outline: 1px dotted #959595;
118 | outline-offset: -4px;
119 | }
120 |
121 | .button.-salmon {
122 | color: #FFFFFF;
123 | background: #F32C52;
124 | }
125 |
126 | .button.scalajs-react {
127 | color: black;
128 | background: #61dafb;
129 | }
130 |
131 | .button.laminar {
132 | color: #FFFFFF;
133 | background: #dc322f;
134 | }
135 |
136 | .button.outwatch {
137 | color: #FFFFFF;
138 | background: #8278fb;
139 | }
140 |
141 | .button.tyrian {
142 | color: #FFFFFF;
143 | background: #4f46e5;
144 | }
145 |
146 | .button.scalatags {
147 | color: #FFFFFF;
148 | background: rgb(31, 141, 214);
149 | }
150 |
151 | textarea {
152 | margin-top: 10px;
153 | margin-left: 5px;
154 | -moz-border-bottom-colors: none;
155 | -moz-border-left-colors: none;
156 | -moz-border-right-colors: none;
157 | -moz-border-top-colors: none;
158 | background: none repeat scroll 0 0 #fbfbfb;
159 | border-image: none;
160 | border-radius: 6px 6px 6px 6px;
161 | border-style: none solid solid none;
162 | border-width: medium 1px 1px medium;
163 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12) inset;
164 | color: #555555;
165 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
166 | font-size: 14px;
167 | line-height: 1.4em;
168 | padding: 5px 8px;
169 | transition: background-color 0.1s ease 0s;
170 | }
171 |
172 |
173 | textarea:focus {
174 | background: none repeat scroll 0 0 #FFFFFF;
175 | outline-width: 0;
176 | }
177 |
178 |
179 | h1 {
180 | text-align: center;
181 | -webkit-transition: opacity 180ms ease;
182 | transition: opacity 180ms ease;
183 | color: #161616;
184 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
185 | font-size: 32px;
186 | font-weight: 700;
187 | }
188 | h1 span {
189 | font-weight: 400;
190 | }/*Checkboxes styles*/
191 | input[type="checkbox"] { display: none; }
192 |
193 | input[type="checkbox"] + label {
194 | position: relative;
195 | padding-left: 35px;
196 | margin-bottom: 20px;
197 | padding-top: 2px;
198 | font: 14px/20px 'Open Sans', Arial, sans-serif;
199 |
200 | cursor: pointer;
201 | -webkit-user-select: none;
202 | -moz-user-select: none;
203 | -ms-user-select: none;
204 | }
205 |
206 | input[type="checkbox"] + label:last-child { margin-bottom: 0; }
207 |
208 | input[type="checkbox"] + label:before {
209 | content: '';
210 | width: 20px;
211 | height: 20px;
212 | border: 1px solid #000000;
213 | position: absolute;
214 | left: 0;
215 | top: 0;
216 | opacity: .6;
217 | -webkit-transition: all .12s, border-color .08s;
218 | transition: all .12s, border-color .08s;
219 | }
220 |
221 | input[type="checkbox"]:checked + label:before {
222 | width: 10px;
223 | top: -5px;
224 | left: 5px;
225 | border-radius: 0;
226 | opacity: 1;
227 | border-top-color: transparent;
228 | border-left-color: transparent;
229 | -webkit-transform: rotate(45deg);
230 | transform: rotate(45deg);
231 | }
232 |
233 | .github-button {
234 | padding-top: 10px;
235 | padding-left: 4px;
236 | }
237 |
--------------------------------------------------------------------------------
/src/main/resources/style.css:
--------------------------------------------------------------------------------
1 | ul {
2 | list-style-type: none;
3 | margin: 0;
4 | padding: 0;
5 | overflow: hidden;
6 | background-color: #000000;
7 | box-shadow: 3px 3px 4px #292323;
8 | }
9 |
10 | li {
11 | float: left;
12 | }
13 |
14 | li a {
15 | display: block;
16 | color: white;
17 | text-align: center;
18 | padding: 14px 16px;
19 | text-decoration: none;
20 | }
21 |
22 | li a:hover {
23 | background-color: #000000;
24 | }
25 |
26 | html, body {
27 | width: 100%;
28 | height: 100%;
29 | margin: 0;
30 | padding: 0;
31 | }
32 |
33 | #section {
34 | width: 100%;
35 | min-height: 100%;
36 | display: table;
37 | height: inherit;
38 | }
39 |
40 | .row {
41 | width: 100%;
42 | height: 100%;
43 | display: table-row;
44 | }
45 |
46 | .col-left, .col-right {
47 | width: 50%;
48 | display: table-cell;
49 | vertical-align: middle;
50 | }
51 |
52 | .col-right {
53 | text-align: center;
54 | }
55 |
56 | .content {
57 | /*border: 1px dashed red;*/
58 | }
59 |
60 | h1 {
61 | font-family: sans-serif;
62 | }
63 |
64 | .boxsizingBorder {
65 | -webkit-box-sizing: border-box;
66 | -moz-box-sizing: border-box;
67 | box-sizing: border-box;
68 | }
69 |
70 | .button {
71 | overflow: hidden;
72 | margin: 10px;
73 | padding: 12px 12px;
74 | cursor: pointer;
75 | -webkit-user-select: none;
76 | -moz-user-select: none;
77 | -ms-user-select: none;
78 | user-select: none;
79 | -webkit-transition: all 60ms ease-in-out;
80 | transition: all 60ms ease-in-out;
81 | text-align: center;
82 | white-space: nowrap;
83 | text-decoration: none !important;
84 | text-transform: none;
85 | text-transform: capitalize;
86 | color: #fff;
87 | border: 0 none;
88 | border-radius: 4px;
89 | font-size: 13px;
90 | font-weight: 500;
91 | line-height: 1.3;
92 | -webkit-appearance: none;
93 | -moz-appearance: none;
94 | appearance: none;
95 | -webkit-box-pack: center;
96 | -ms-flex-pack: center;
97 | justify-content: center;
98 | -webkit-box-align: center;
99 | -ms-flex-align: center;
100 | align-items: center;
101 | -webkit-box-flex: 0;
102 | -ms-flex: 0 0 160px;
103 | flex: 0 0 160px;
104 | box-shadow: 2px 5px 10px rgba(22, 22, 22, 0.1);
105 | }
106 | .button:hover {
107 | -webkit-transition: all 60ms ease;
108 | transition: all 60ms ease;
109 | opacity: .85;
110 | }
111 | .button:active {
112 | -webkit-transition: all 60ms ease;
113 | transition: all 60ms ease;
114 | opacity: .75;
115 | }
116 | .button:focus {
117 | outline: 1px dotted #959595;
118 | outline-offset: -4px;
119 | }
120 |
121 | .button.-salmon {
122 | color: #FFFFFF;
123 | background: #F32C52;
124 | }
125 |
126 | .button.scalajs-react {
127 | color: black;
128 | background: #61dafb;
129 | }
130 |
131 | .button.laminar {
132 | color: #FFFFFF;
133 | background: #dc322f;
134 | }
135 |
136 | .button.outwatch {
137 | color: #FFFFFF;
138 | background: #8278fb;
139 | }
140 |
141 | .button.tyrian {
142 | color: #FFFFFF;
143 | background: #4f46e5;
144 | }
145 |
146 | .button.scalatags {
147 | color: #FFFFFF;
148 | background: rgb(31, 141, 214);
149 | }
150 |
151 | textarea {
152 | margin-top: 10px;
153 | margin-left: 5px;
154 | -moz-border-bottom-colors: none;
155 | -moz-border-left-colors: none;
156 | -moz-border-right-colors: none;
157 | -moz-border-top-colors: none;
158 | background: none repeat scroll 0 0 #fbfbfb;
159 | border-image: none;
160 | border-radius: 6px 6px 6px 6px;
161 | border-style: none solid solid none;
162 | border-width: medium 1px 1px medium;
163 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12) inset;
164 | color: #555555;
165 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
166 | font-size: 14px;
167 | line-height: 1.4em;
168 | padding: 5px 8px;
169 | transition: background-color 0.1s ease 0s;
170 | }
171 |
172 |
173 | textarea:focus {
174 | background: none repeat scroll 0 0 #FFFFFF;
175 | outline-width: 0;
176 | }
177 |
178 |
179 | h1 {
180 | text-align: center;
181 | -webkit-transition: opacity 180ms ease;
182 | transition: opacity 180ms ease;
183 | color: #161616;
184 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
185 | font-size: 32px;
186 | font-weight: 700;
187 | }
188 | h1 span {
189 | font-weight: 400;
190 | }/*Checkboxes styles*/
191 | input[type="checkbox"] { display: none; }
192 |
193 | input[type="checkbox"] + label {
194 | position: relative;
195 | padding-left: 35px;
196 | margin-bottom: 20px;
197 | padding-top: 2px;
198 | font: 14px/20px 'Open Sans', Arial, sans-serif;
199 |
200 | cursor: pointer;
201 | -webkit-user-select: none;
202 | -moz-user-select: none;
203 | -ms-user-select: none;
204 | }
205 |
206 | input[type="checkbox"] + label:last-child { margin-bottom: 0; }
207 |
208 | input[type="checkbox"] + label:before {
209 | content: '';
210 | width: 20px;
211 | height: 20px;
212 | border: 1px solid #000000;
213 | position: absolute;
214 | left: 0;
215 | top: 0;
216 | opacity: .6;
217 | -webkit-transition: all .12s, border-color .08s;
218 | transition: all .12s, border-color .08s;
219 | }
220 |
221 | input[type="checkbox"]:checked + label:before {
222 | width: 10px;
223 | top: -5px;
224 | left: 5px;
225 | border-radius: 0;
226 | opacity: 1;
227 | border-top-color: transparent;
228 | border-left-color: transparent;
229 | -webkit-transform: rotate(45deg);
230 | transform: rotate(45deg);
231 | }
232 |
233 | .github-button {
234 | padding-top: 10px;
235 | padding-left: 4px;
236 | }
237 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/HTMLTemplate.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter
2 |
3 | import org.scalajs.dom
4 | import org.scalajs.dom.html.Input
5 | import scalatags.JsDom.all._
6 | import simer.html.converter.ConverterType.{Outwatch, Laminar, ScalaJSReact, ScalaTags, Tyrian}
7 |
8 | object HTMLTemplate {
9 |
10 | def isNewLineAttributes = !dom.document.getElementById("newlineAttributes").asInstanceOf[Input].checked
11 |
12 | def isBooleanTypeConversionDisabled = dom.document.getElementById("disableBooleanTypeConversion").asInstanceOf[Input].checked
13 |
14 | def template(onConvertClicked: ConverterType => Unit) =
15 | div(
16 | ul(
17 | li(
18 | a(href := "#")("HTML TO SCALA CONVERTER")
19 | ),
20 | li(float := "right", paddingTop := 10.px, paddingRight := 10.px,
21 | u(
22 | a(
23 | cls := "github-button",
24 | href := "https://github.com/simerplaha/html-to-scala-converter",
25 | attr("data-size") := "large",
26 | attr("data-show-count") := "true",
27 | attr("aria-label") := "Star simerplaha/html-to-scala-converter on GitHub",
28 | "Star"
29 | )
30 | )
31 | )
32 | ),
33 | table(width := "100%")(
34 | tr(width := "100%")(
35 | th(width := "50%")(
36 | h4("HTML")
37 | ),
38 | th(width := "50%")(
39 | h4("Scala")
40 | )
41 | ),
42 | tr(width := "100%")(
43 | td(width := "50%")(
44 | textarea(id := "htmlCode", cls := "boxsizingBorder", width := "100%", rows := 26, placeholder := "Enter your HTML code here.")(
45 | """
46 | |
47 | | Button
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | | Some link
55 | |
56 | |
64 | |
65 | |
66 | |
67 | |
70 | |
""".stripMargin
71 | )
72 | ),
73 | td(width := "50%")(
74 | textarea(id := "scalaCode", cls := "boxsizingBorder", width := "100%", rows := 26, placeholder := "Scala code will be generated here.")
75 | )
76 | ),
77 | tr(width := "50%")(
78 | td(colspan := "1", textAlign := "right", paddingBottom := "10px", paddingRight := "25px")(
79 | input(`type` := "checkbox", id := "newlineAttributes"),
80 | label(`for` := "newlineAttributes", "Add attributes on newline"),
81 | ),
82 | td(colspan := "1", textAlign := "left", paddingBottom := "10px", paddingLeft := "10px")(
83 | input(`type` := "checkbox", id := "disableBooleanTypeConversion"),
84 | label(`for` := "disableBooleanTypeConversion", "Disable Boolean type conversion"),
85 | )
86 | ),
87 | tr(width := "100%")(
88 | td(colspan := "3", textAlign := "center")(
89 | button(cls := "button scalajs-react center", onclick := { () => onConvertClicked(ScalaJSReact(isNewLineAttributes, isBooleanTypeConversionDisabled)) })("ScalaJS-React (1.7.7)"),
90 | span(" "),
91 | button(cls := "button scalatags center", onclick := { () => onConvertClicked(ScalaTags(isNewLineAttributes, isBooleanTypeConversionDisabled)) })("ScalaTags (0.9.4)"),
92 | span(" "),
93 | button(cls := "button laminar center", onclick := { () => onConvertClicked(Laminar(isNewLineAttributes, isBooleanTypeConversionDisabled)) })("Laminar (0.13.1)"),
94 | span(" "),
95 | button(cls := "button outwatch center", onclick := { () => onConvertClicked(Outwatch(isNewLineAttributes, isBooleanTypeConversionDisabled)) })("Outwatch (1.0.0-RC7)"),
96 | span(" "),
97 | button(cls := "button tyrian center", onclick := { () => onConvertClicked(Tyrian(isNewLineAttributes, isBooleanTypeConversionDisabled)) })("Tyrian (0.5.1)")
98 |
99 | ),
100 | ),
101 | tr(width := "100%")(
102 | td(colspan := "3", textAlign := "center")(
103 | p(
104 | strong(""""Add attributes on newline""""),
105 | " inserts a new line after each tag attribute/property.",
106 | ),
107 | p(
108 | strong(""""Disable Boolean type conversion""""),
109 | " converts ",
110 | span(backgroundColor := "#dddcdc" ,"""checked = "blah!""""),
111 | " to ",
112 | span(backgroundColor := "#dddcdc" ,"""checked := "blah!""""),
113 | " instead of ",
114 | span(backgroundColor := "#dddcdc" ,"""checked := true"""),
115 | ),
116 | p(
117 | "Type conversions are enabled for ScalaJS-React, Laminar & Outwatch. Please report any missing types."
118 | )
119 | )
120 | )
121 | )
122 | )
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/tyrian/TyrianTags.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter.tyrian
2 |
3 | object TyrianTags {
4 |
5 | def tags: List[TagType] =
6 | List(
7 | HasChildren("a"),
8 | HasChildren("abbr"),
9 | HasChildren("address"),
10 | NoChildren("area"),
11 | HasChildren("article"),
12 | HasChildren("aside"),
13 | HasChildren("audio"),
14 | HasChildren("b"),
15 | NoChildren("base"),
16 | HasChildren("bdi"),
17 | HasChildren("bdo"),
18 | HasChildren("blockquote"),
19 | HasChildren("body"),
20 | NoChildren("br"),
21 | OptionalChildren("button"),
22 | HasChildren("canvas"),
23 | HasChildren("caption"),
24 | HasChildren("cite"),
25 | HasChildren("code"),
26 | NoChildren("col"),
27 | HasChildren("colgroup"),
28 | HasChildren("data"),
29 | HasChildren("datalist"),
30 | HasChildren("dd"),
31 | HasChildren("del"),
32 | HasChildren("details"),
33 | HasChildren("dfn"),
34 | HasChildren("dialog"),
35 | HasChildren("div"),
36 | HasChildren("dl"),
37 | HasChildren("dt"),
38 | HasChildren("em"),
39 | HasChildren("embed"),
40 | HasChildren("fieldset"),
41 | HasChildren("figcaption"),
42 | HasChildren("figure"),
43 | HasChildren("footer"),
44 | HasChildren("form"),
45 | HasChildren("h1"),
46 | HasChildren("h2"),
47 | HasChildren("h3"),
48 | HasChildren("h4"),
49 | HasChildren("h5"),
50 | HasChildren("h6"),
51 | HasChildren("head"),
52 | HasChildren("header"),
53 | NoChildren("hr"),
54 | HasChildren("html"),
55 | HasChildren("i"),
56 | HasChildren("iframe"),
57 | NoChildren("img"),
58 | NoChildren("input"),
59 | HasChildren("ins"),
60 | HasChildren("kbd"),
61 | HasChildren("label"),
62 | HasChildren("legend"),
63 | HasChildren("li"),
64 | NoChildren("link"),
65 | HasChildren("main"),
66 | HasChildren("map"),
67 | HasChildren("mark"),
68 | NoChildren("meta"),
69 | HasChildren("meter"),
70 | HasChildren("nav"),
71 | HasChildren("noscript"),
72 | HasChildren("`object`", "object"),
73 | HasChildren("objectTag", "object"),
74 | HasChildren("ol"),
75 | HasChildren("optgroup"),
76 | HasChildren("option"),
77 | HasChildren("output"),
78 | HasChildren("p"),
79 | NoChildren("param"),
80 | HasChildren("picture"),
81 | HasChildren("pre"),
82 | HasChildren("progress"),
83 | HasChildren("q"),
84 | HasChildren("rp"),
85 | HasChildren("rt"),
86 | HasChildren("ruby"),
87 | HasChildren("s"),
88 | HasChildren("samp"),
89 | HasChildren("script"),
90 | HasChildren("section"),
91 | HasChildren("select"),
92 | HasChildren("small"),
93 | NoChildren("source"),
94 | HasChildren("span"),
95 | HasChildren("strong"),
96 | HasChildren("style"),
97 | HasChildren("sub"),
98 | HasChildren("summary"),
99 | HasChildren("sup"),
100 | HasChildren("svg"),
101 | HasChildren("table"),
102 | HasChildren("tbody"),
103 | HasChildren("td"),
104 | HasChildren("template"),
105 | HasChildren("textarea"),
106 | HasChildren("tfoot"),
107 | HasChildren("th"),
108 | HasChildren("thead"),
109 | HasChildren("time"),
110 | HasChildren("title"),
111 | HasChildren("tr"),
112 | NoChildren("track"),
113 | HasChildren("u"),
114 | HasChildren("ul"),
115 | HasChildren("`var`", "var"),
116 | HasChildren("varTag", "var"),
117 | HasChildren("video"),
118 | HasChildren("wbr"),
119 | NoChildren("animate"),
120 | NoChildren("animateColor"),
121 | NoChildren("animateMotion"),
122 | NoChildren("animateTransform"),
123 | NoChildren("circle"),
124 | HasChildren("clipPath"),
125 | HasChildren("defs"),
126 | HasChildren("desc"),
127 | NoChildren("ellipse"),
128 | NoChildren("feBlend"),
129 | NoChildren("feColorMatrix"),
130 | HasChildren("feComponentTransfer"),
131 | NoChildren("feComposite"),
132 | NoChildren("feConvolveMatrix"),
133 | HasChildren("feDiffuseLighting"),
134 | NoChildren("feDisplacementMap"),
135 | NoChildren("feDistantLight"),
136 | NoChildren("feFlood"),
137 | NoChildren("feFuncA"),
138 | NoChildren("feFuncB"),
139 | NoChildren("feFuncG"),
140 | NoChildren("feFuncR"),
141 | NoChildren("feGaussianBlur"),
142 | NoChildren("feImage"),
143 | HasChildren("feMerge"),
144 | NoChildren("feMergeNode"),
145 | NoChildren("feMorphology"),
146 | NoChildren("feOffset"),
147 | NoChildren("fePointLight"),
148 | HasChildren("feSpecularLighting"),
149 | NoChildren("feSpotLight"),
150 | HasChildren("feTile"),
151 | NoChildren("feTurbulence"),
152 | HasChildren("filter"),
153 | HasChildren("foreignObject"),
154 | HasChildren("g"),
155 | NoChildren("image"),
156 | NoChildren("line"),
157 | HasChildren("linearGradient"),
158 | HasChildren("marker"),
159 | HasChildren("mask"),
160 | HasChildren("metadata"),
161 | NoChildren("mpath"),
162 | NoChildren("path"),
163 | HasChildren("pattern"),
164 | NoChildren("polygon"),
165 | NoChildren("polyline"),
166 | HasChildren("radialGradient"),
167 | NoChildren("rect"),
168 | NoChildren("set"),
169 | NoChildren("stop"),
170 | HasChildren("switch"),
171 | HasChildren("symbol"),
172 | HasChildren("textTag", "text"),
173 | HasChildren("textPath"),
174 | HasChildren("tspan"),
175 | NoChildren("use"),
176 | NoChildren("view")
177 | )
178 |
179 | sealed trait TagType {
180 | def name: String
181 |
182 | def tag: Option[String]
183 | }
184 |
185 | final case class HasChildren(name: String, tag: Option[String]) extends TagType
186 |
187 | object HasChildren {
188 | def apply(name: String): HasChildren = HasChildren(name, None)
189 |
190 | def apply(name: String, tag: String): HasChildren = HasChildren(name, Some(tag))
191 | }
192 |
193 | final case class NoChildren(name: String, tag: Option[String]) extends TagType
194 |
195 | object NoChildren {
196 | def apply(name: String): NoChildren = NoChildren(name, None)
197 |
198 | def apply(name: String, tag: String): NoChildren = NoChildren(name, Some(tag))
199 | }
200 |
201 | final case class OptionalChildren(name: String, tag: Option[String]) extends TagType
202 |
203 | object OptionalChildren {
204 | def apply(name: String): OptionalChildren = OptionalChildren(name, None)
205 |
206 | def apply(name: String, tag: String): OptionalChildren = OptionalChildren(name, Some(tag))
207 | }
208 | }
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/tyrian/TyrianAttributes.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter.tyrian
2 |
3 | object TyrianAttributes {
4 | def attrs: List[AttributeType] =
5 | List(
6 | Normal("accept"),
7 | Normal("accessKey"),
8 | Normal("action"),
9 | Normal("alt"),
10 | NoValue("async"),
11 | Normal("autoComplete"),
12 | NoValue("autoFocus"),
13 | NoValue("autofocus"),
14 | NoValue("autoPlay"),
15 | NoValue("autoplay"),
16 | Normal("charset"),
17 | NoValue("checked"),
18 | Normal("cite"),
19 | Normal("`class`", "class"),
20 | Normal("cls", "class"),
21 | Normal("className", "class"),
22 | Normal("classname", "class"),
23 | Normal("_class", "class"),
24 | Normal("cols").withTypes("String", "Int"),
25 | Normal("colSpan").withTypes("String", "Int"),
26 | Normal("colspan").withTypes("String", "Int"),
27 | Normal("content"),
28 | Normal("contentEditable").withTypes("String", "Boolean"),
29 | Normal("contenteditable").withTypes("String", "Boolean"),
30 | NoValue("controls"),
31 | Normal("coords"),
32 | Normal("data"),
33 | Normal("dateTime"),
34 | Normal("datetime"),
35 | NoValue("default"),
36 | NoValue("defer"),
37 | Normal("dir"),
38 | Normal("dirname"),
39 | NoValue("disabled"),
40 | NoValue("download"),
41 | Normal("draggable").withTypes("String", "Boolean"),
42 | Normal("encType"),
43 | Normal("enctype"),
44 | Normal("_for", "for"),
45 | Normal("forId", "for"),
46 | Normal("htmlFor", "for"),
47 | Normal("form"),
48 | Normal("formAction"),
49 | Normal("formaction"),
50 | Normal("headers"),
51 | Normal("height").withTypes("String", "Int"),
52 | NoValue("hidden"),
53 | Normal("high").withTypes("String", "Double"),
54 | Normal("href"),
55 | Normal("hrefLang"),
56 | Normal("hreflang"),
57 | Normal("http"),
58 | Normal("id"),
59 | NoValue("isMap"),
60 | NoValue("ismap"),
61 | Normal("kind"),
62 | Normal("label"),
63 | Normal("lang"),
64 | Normal("list"),
65 | NoValue("loop"),
66 | Normal("low").withTypes("String", "Double"),
67 | Normal("max").withTypes("String", "Int"),
68 | Normal("maxLength").withTypes("String", "Int"),
69 | Normal("maxlength").withTypes("String", "Int"),
70 | Normal("media"),
71 | Normal("method"),
72 | Normal("min").withTypes("String", "Int"),
73 | Normal("multiple"),
74 | Normal("muted"),
75 | Normal("name"),
76 | NoValue("noValidate"),
77 | NoValue("novalidate"),
78 | EventEmitting("onAbort"),
79 | EventEmitting("onAfterPrint"),
80 | EventEmitting("onBeforePrint"),
81 | EventEmitting("onBeforeUnload"),
82 | EventEmitting("onBlur"),
83 | EventEmitting("onCanPlay"),
84 | EventEmitting("onCanPlayThrough"),
85 | EventEmitting("onChange", "change"),
86 | EventEmitting("onClick", "click"),
87 | EventEmitting("onContextMenu"),
88 | EventEmitting("onCopy"),
89 | EventEmitting("onCueChange"),
90 | EventEmitting("onCut"),
91 | EventEmitting("onDblClick"),
92 | EventEmitting("onDoubleClick", "dblclick"),
93 | EventEmitting("onDrag"),
94 | EventEmitting("onDragEnd"),
95 | EventEmitting("onDragEnter"),
96 | EventEmitting("onDragLeave"),
97 | EventEmitting("onDragOver"),
98 | EventEmitting("onDragStart"),
99 | EventEmitting("onDrop"),
100 | EventEmitting("onDurationChange"),
101 | EventEmitting("onEmptied"),
102 | EventEmitting("onEnded"),
103 | EventEmitting("onError"),
104 | EventEmitting("onFocus"),
105 | EventEmitting("onHashChange"),
106 | EventEmitting("onInput"),
107 | EventEmitting("onInvalid"),
108 | EventEmitting("onKeyDown"),
109 | EventEmitting("onKeyPress"),
110 | EventEmitting("onKeyUp"),
111 | EventEmitting("onLoad"),
112 | EventEmitting("onLoadedData"),
113 | EventEmitting("onLoadedMetadata"),
114 | EventEmitting("onLoadStart"),
115 | EventEmitting("onMouseDown"),
116 | EventEmitting("onMousemove"),
117 | EventEmitting("onMouseOut"),
118 | EventEmitting("onMouseOver"),
119 | EventEmitting("onMouseUp"),
120 | EventEmitting("onMouseWheel"),
121 | EventEmitting("onOffline"),
122 | EventEmitting("onOnline"),
123 | EventEmitting("onPageHide"),
124 | EventEmitting("onPageShow"),
125 | EventEmitting("onPaste"),
126 | EventEmitting("onPause"),
127 | EventEmitting("onPlay"),
128 | EventEmitting("onPlaying"),
129 | EventEmitting("onPopState"),
130 | EventEmitting("onProgress"),
131 | EventEmitting("onRateChange"),
132 | EventEmitting("onReset"),
133 | EventEmitting("onResize"),
134 | EventEmitting("onScroll"),
135 | EventEmitting("onSearch"),
136 | EventEmitting("onSeeked"),
137 | EventEmitting("onSeeking"),
138 | EventEmitting("onSelect"),
139 | EventEmitting("onStalled"),
140 | EventEmitting("onStorage"),
141 | EventEmitting("onSubmit"),
142 | EventEmitting("onSuspend"),
143 | EventEmitting("onTimeUpdate"),
144 | EventEmitting("onToggle"),
145 | EventEmitting("onUnload"),
146 | EventEmitting("onVolumeChange"),
147 | EventEmitting("onWaiting"),
148 | EventEmitting("onWheel"),
149 | NoValue("open"),
150 | Normal("optimum").withTypes("String", "Double"),
151 | Normal("pattern"),
152 | Normal("placeholder"),
153 | Normal("poster"),
154 | Normal("preload"),
155 | NoValue("readOnly"),
156 | NoValue("readonly"),
157 | Normal("rel"),
158 | NoValue("required"),
159 | NoValue("reversed"),
160 | Normal("rows").withTypes("String", "Int"),
161 | Normal("rowSpan").withTypes("String", "Int"),
162 | Normal("rowspan").withTypes("String", "Int"),
163 | NoValue("sandbox"),
164 | Normal("scope"),
165 | NoValue("selected"),
166 | Normal("shape"),
167 | Normal("size").withTypes("String", "Int"),
168 | Normal("sizes"),
169 | Normal("span").withTypes("String", "Int"),
170 | Normal("spellCheck").withTypes("String", "Boolean"),
171 | Normal("spellcheck").withTypes("String", "Boolean"),
172 | Normal("src"),
173 | Normal("srcDoc"),
174 | Normal("srcdoc"),
175 | Normal("srcLang"),
176 | Normal("srclang"),
177 | Normal("srcSet"),
178 | Normal("srcset"),
179 | Normal("start").withTypes("String", "Int"),
180 | Normal("step").withTypes("String", "Int"),
181 | Normal("style"),
182 | Normal("tabIndex").withTypes("String", "Int"),
183 | Normal("tabindex").withTypes("String", "Int"),
184 | Normal("target"),
185 | Normal("title"),
186 | Normal("translate"),
187 | Normal("`type`", "type"),
188 | Normal("_type", "type"),
189 | Normal("typ", "type"),
190 | Normal("tpe", "type"),
191 | Normal("useMap"),
192 | Normal("usemap"),
193 | Normal("width").withTypes("String", "Int"),
194 | Normal("wrap"),
195 | Normal("value").withTypes("String", "Double", "Boolean"),
196 | Normal("cx"),
197 | Normal("cy"),
198 | Normal("d"),
199 | Normal("fill"),
200 | Normal("pathLength", "pathLength"), // svg attributes are case sensitive
201 | Normal("points"),
202 | Normal("r"),
203 | Normal("rx"),
204 | Normal("ry"),
205 | Normal("stroke"),
206 | Normal("viewBox", "viewBox"), // svg attributes are case sensitive
207 | Normal("xmlns"),
208 | Normal("x"),
209 | Normal("x1"),
210 | Normal("x2"),
211 | Normal("y"),
212 | Normal("y1"),
213 | Normal("y2")
214 | )
215 |
216 | sealed trait AttributeType {
217 | def name: String
218 |
219 | def attrName: Option[String]
220 | }
221 |
222 | final case class Normal(name: String, attrName: Option[String], types: List[String]) extends AttributeType {
223 | def withTypes(newTypes: String*): Normal =
224 | Normal(name, attrName, newTypes.toList)
225 | }
226 |
227 | object Normal {
228 | def apply(name: String): Normal = Normal(name, None, List("String"))
229 |
230 | def apply(name: String, attrName: String): Normal = Normal(name, Some(attrName), List("String"))
231 | }
232 |
233 | final case class NoValue(name: String, attrName: Option[String]) extends AttributeType
234 |
235 | object NoValue {
236 | def apply(name: String): NoValue = NoValue(name, None)
237 |
238 | def apply(name: String, attrName: String): NoValue = NoValue(name, Some(attrName))
239 | }
240 |
241 | final case class EventEmitting(name: String, attrName: Option[String]) extends AttributeType
242 |
243 | object EventEmitting {
244 | def apply(name: String): EventEmitting = EventEmitting(name, None)
245 |
246 | def apply(name: String, attrName: String): EventEmitting = EventEmitting(name, Some(attrName))
247 | }
248 |
249 |
250 | }
251 |
252 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/ConverterType.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter
2 |
3 | import simer.html.converter.AttributeType.{BooleanAttribute, EventAttribute, IntAttribute, StringAttribute}
4 |
5 | sealed trait ConverterType {
6 | val attributePrefix: String
7 | val nodePrefix: String
8 | val customAttributeFunctionName: String
9 | val newLineAttributes: Boolean
10 | val isBooleanTypeConversionDisabled: Boolean
11 | //stores name conversions for html attribute names to scala names
12 | val attributeNameMap: Map[String, AttributeType]
13 | val tagNameMap: Map[String, String]
14 | }
15 |
16 | object ConverterType {
17 |
18 | object ScalaJSReact {
19 | //attributes naming and type convention found in ScalaJS-React
20 | val dataAttributes =
21 | Map(
22 | "class" -> StringAttribute("className"),
23 | "for" -> StringAttribute("`for`"),
24 | "type" -> StringAttribute("`type`"),
25 | "content" -> StringAttribute("contentAttr"),
26 | "hreflang" -> StringAttribute("hrefLang"),
27 | "tabindex" -> IntAttribute("tabIndex"),
28 | "styleTag" -> StringAttribute("styleTag"),
29 | "autocomplete" -> StringAttribute("autoComplete"),
30 | "allowfullscreen" -> BooleanAttribute("allowFullScreen"),
31 | "allowtransparency" -> BooleanAttribute("allowTransparency"),
32 | "async" -> BooleanAttribute("async"),
33 | "autocorrect" -> BooleanAttribute("autoCorrect"),
34 | "autofocus" -> BooleanAttribute("autoFocus"),
35 | "autoplay" -> BooleanAttribute("autoPlay"),
36 | "autosave" -> BooleanAttribute("autoSave"),
37 | "checked" -> BooleanAttribute("checked"),
38 | "colspan" -> IntAttribute("colSpan"),
39 | "controls" -> BooleanAttribute("controls"),
40 | "default" -> BooleanAttribute("default"),
41 | "defer" -> BooleanAttribute("defer"),
42 | "disablepictureinpicture" -> BooleanAttribute("disablePictureInPicture"),
43 | "disabled" -> BooleanAttribute("disabled"),
44 | "draggable" -> BooleanAttribute("draggable"),
45 | "formnovalidate" -> BooleanAttribute("formNoValidate"),
46 | "hidden" -> BooleanAttribute("hidden"),
47 | "itemscope" -> BooleanAttribute("itemScope"),
48 | "loop" -> BooleanAttribute("loop"),
49 | "maxlength" -> IntAttribute("maxLength"),
50 | "minlength" -> IntAttribute("minLength"),
51 | "multiple" -> BooleanAttribute("multiple"),
52 | "muted" -> BooleanAttribute("muted"),
53 | "nomodule" -> BooleanAttribute("noModule"),
54 | "novalidate" -> BooleanAttribute("noValidate"),
55 | "open" -> BooleanAttribute("open"),
56 | "playsinline" -> BooleanAttribute("playsInline"),
57 | "radiogroup" -> StringAttribute("radioGroup"),
58 | "readonly" -> BooleanAttribute("readOnly"),
59 | "required" -> BooleanAttribute("required"),
60 | "reversed" -> BooleanAttribute("reversed"),
61 | "rowspan" -> IntAttribute("rowSpan"),
62 | "rows" -> IntAttribute("rows"),
63 | "scoped" -> BooleanAttribute("scoped"),
64 | "seamless" -> BooleanAttribute("seamless"),
65 | "selected" -> BooleanAttribute("selected"),
66 | "size" -> IntAttribute("size"),
67 | "spellcheck" -> BooleanAttribute("spellCheck"),
68 | "contextmenu" -> StringAttribute("contextMenu"),
69 | "autocapitalize" -> StringAttribute("autoCapitalize"),
70 | "cellpadding" -> StringAttribute("cellPadding"),
71 | "cellspacing" -> StringAttribute("cellSpacing"),
72 | "classid" -> StringAttribute("classID"),
73 | )
74 | }
75 |
76 | case class ScalaJSReact(newLineAttributes: Boolean,
77 | isBooleanTypeConversionDisabled: Boolean) extends ConverterType {
78 | val attributePrefix: String = "^."
79 | val nodePrefix: String = "<."
80 | val customAttributeFunctionName: String = "VdomAttr"
81 |
82 | override val tagNameMap: Map[String, String] =
83 | Map(
84 | "title" -> "titleTag",
85 | "style" -> "styleTag"
86 | )
87 |
88 | override val attributeNameMap: Map[String, AttributeType] =
89 | ScalaJSReact.dataAttributes ++ EventAttribute.reactAndLaminarAndOutwatchEventAttributes
90 | }
91 |
92 | case class ScalaTags(newLineAttributes: Boolean,
93 | isBooleanTypeConversionDisabled: Boolean) extends ConverterType {
94 | val attributePrefix: String = ""
95 | val nodePrefix: String = ""
96 | val customAttributeFunctionName: String = "attr"
97 |
98 | override val tagNameMap: Map[String, String] =
99 | Map.empty
100 |
101 | override val attributeNameMap: Map[String, AttributeType] =
102 | Map(
103 | "class" -> StringAttribute("cls"),
104 | "for" -> StringAttribute("`for`"),
105 | "type" -> StringAttribute("`type`")
106 | )
107 | }
108 |
109 | case class Laminar(newLineAttributes: Boolean,
110 | isBooleanTypeConversionDisabled: Boolean) extends ConverterType {
111 | val attributePrefix: String = ""
112 | val nodePrefix: String = ""
113 | val customAttributeFunctionName: String = "dataAttr"
114 |
115 | override val tagNameMap: Map[String, String] =
116 | Map(
117 | "style" -> "styleTag",
118 | "link" -> "linkTag",
119 | "param" -> "paramTag",
120 | "map" -> "mapTag",
121 | "title" -> "titleTag",
122 | "object" -> "objectTag",
123 | "noscript" -> "noScript",
124 | "textarea" -> "textArea",
125 | "optgroup" -> "optGroup",
126 | "fieldset" -> "fieldSet"
127 | )
128 |
129 | override val attributeNameMap: Map[String, AttributeType] =
130 | ScalaJSReact.dataAttributes ++ Map(
131 | "class" -> StringAttribute("cls"),
132 | "for" -> StringAttribute("forId"),
133 | "type" -> StringAttribute("tpe"),
134 | "value" -> StringAttribute("defaultValue"),
135 | "checked" -> BooleanAttribute("defaultChecked"),
136 | "selected" -> StringAttribute("defaultSelected"),
137 | "for" -> StringAttribute("forId"),
138 | "id" -> StringAttribute("idAttr"),
139 | "max" -> StringAttribute("maxAttr"),
140 | "min" -> StringAttribute("minAttr"),
141 | "step" -> StringAttribute("stepAttr"),
142 | "offset" -> StringAttribute("offsetAttr"),
143 | "result" -> StringAttribute("resultAttr"),
144 | "loading" -> StringAttribute("loadingAttr"),
145 | "style" -> StringAttribute("styleAttr"),
146 | "content" -> StringAttribute("contentAttr"),
147 | "form" -> StringAttribute("formId"),
148 | "height" -> IntAttribute("heightAttr"),
149 | "width" -> IntAttribute("widthAttr"),
150 | "list" -> StringAttribute("listId"),
151 | "contextmenu" -> StringAttribute("contextMenuId"),
152 | ) ++ EventAttribute.reactAndLaminarAndOutwatchEventAttributes
153 | }
154 |
155 | case class Outwatch(newLineAttributes: Boolean,
156 | isBooleanTypeConversionDisabled: Boolean) extends ConverterType {
157 | val attributePrefix: String = ""
158 | val nodePrefix: String = ""
159 | val customAttributeFunctionName: String = "VModifier.attr"
160 |
161 | override val tagNameMap: Map[String, String] =
162 | Map(
163 | "style" -> "styleTag",
164 | "link" -> "linkTag",
165 | "param" -> "paramTag",
166 | "map" -> "mapTag",
167 | "title" -> "titleTag",
168 | "object" -> "objectTag",
169 | "noscript" -> "noScript",
170 | "textarea" -> "textArea",
171 | "optgroup" -> "optGroup",
172 | "fieldset" -> "fieldSet"
173 | )
174 |
175 | override val attributeNameMap: Map[String, AttributeType] =
176 | ScalaJSReact.dataAttributes ++ Map(
177 | "class" -> StringAttribute("cls"),
178 | "for" -> StringAttribute("forId"),
179 | "type" -> StringAttribute("tpe"),
180 | "value" -> StringAttribute("value"),
181 | "checked" -> BooleanAttribute("checked"),
182 | "selected" -> StringAttribute("selected"),
183 | "for" -> StringAttribute("forId"),
184 | "id" -> StringAttribute("idAttr"),
185 | "max" -> StringAttribute("maxAttr"),
186 | "min" -> StringAttribute("minAttr"),
187 | "step" -> StringAttribute("stepAttr"),
188 | "offset" -> StringAttribute("offsetAttr"),
189 | "result" -> StringAttribute("resultAttr"),
190 | "loading" -> StringAttribute("loadingAttr"),
191 | "style" -> StringAttribute("styleAttr"),
192 | "content" -> StringAttribute("contentAttr"),
193 | "form" -> StringAttribute("formId"),
194 | "height" -> IntAttribute("heightAttr"),
195 | "width" -> IntAttribute("widthAttr"),
196 | "list" -> StringAttribute("listId"),
197 | "contextmenu" -> StringAttribute("contextMenuId"),
198 | ) ++ EventAttribute.reactAndLaminarAndOutwatchEventAttributes.map { case (key, attr) =>
199 | (key, attr.copy(function = "foreach"))
200 | }
201 | }
202 |
203 | case class Tyrian(newLineAttributes: Boolean,
204 | isBooleanTypeConversionDisabled: Boolean) extends ConverterType {
205 | override val attributePrefix: String = ""
206 | override val nodePrefix: String = ""
207 | override val customAttributeFunctionName: String = "Attribute"
208 | override val attributeNameMap: Map[String, AttributeType] = Map.empty
209 | override val tagNameMap: Map[String, String] = Map.empty
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/AttributeType.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter
2 |
3 | /**
4 | * Defines mapping for HTML attribute key names to target Scala's key and function name
5 | *
6 | * For example to convert `onclick = "alert("");"` to `onClick <-- "alert("");"`
7 | * this map entry should be
8 | * {{{
9 | * map.put("onclick", EventAttribute("onClick", "<--"))
10 | * }}}
11 | */
12 |
13 | sealed trait AttributeType {
14 | def key: String
15 | def function: String
16 | }
17 |
18 | object AttributeType {
19 |
20 | case class StringAttribute(key: String,
21 | function: String = ":=") extends AttributeType
22 |
23 | case class IntAttribute(key: String,
24 | function: String = ":=") extends AttributeType
25 |
26 | case class DoubleAttribute(key: String,
27 | function: String = ":=") extends AttributeType
28 |
29 | case class BooleanAttribute(key: String,
30 | function: String = ":=") extends AttributeType
31 |
32 | object EventAttribute {
33 | //Event attributes shared by ScalaJS-react, Laminar and Outwatch
34 | val reactAndLaminarAndOutwatchEventAttributes =
35 | Map(
36 | s"onchange" -> EventAttribute("onChange"),
37 | s"onclick" -> EventAttribute("onClick"),
38 | s"onclickcapture" -> EventAttribute("onClickCapture"),
39 | s"onetimecode" -> EventAttribute("oneTimeCode"),
40 | s"onabort" -> EventAttribute("onAbort"),
41 | s"onabortcapture" -> EventAttribute("onAbortCapture"),
42 | s"onanimationend" -> EventAttribute("onAnimationEnd"),
43 | s"onanimationendcapture" -> EventAttribute("onAnimationEndCapture"),
44 | s"onanimationiteration" -> EventAttribute("onAnimationIteration"),
45 | s"onanimationiterationcapture" -> EventAttribute("onAnimationIterationCapture"),
46 | s"onanimationstart" -> EventAttribute("onAnimationStart"),
47 | s"onanimationstartcapture" -> EventAttribute("onAnimationStartCapture"),
48 | s"onauxclick" -> EventAttribute("onAuxClick"),
49 | s"onauxclickcapture" -> EventAttribute("onAuxClickCapture"),
50 | s"onbeforeinput" -> EventAttribute("onBeforeInput"),
51 | s"onblur" -> EventAttribute("onBlur"),
52 | s"onblurcapture" -> EventAttribute("onBlurCapture"),
53 | s"oncanplay" -> EventAttribute("onCanPlay"),
54 | s"oncanplaycapture" -> EventAttribute("onCanPlayCapture"),
55 | s"oncanplaythrough" -> EventAttribute("onCanPlayThrough"),
56 | s"oncompositionend" -> EventAttribute("onCompositionEnd"),
57 | s"oncompositionstart" -> EventAttribute("onCompositionStart"),
58 | s"oncompositionupdate" -> EventAttribute("onCompositionUpdate"),
59 | s"oncontextmenu" -> EventAttribute("onContextMenu"),
60 | s"oncontextmenucapture" -> EventAttribute("onContextMenuCapture"),
61 | s"oncopy" -> EventAttribute("onCopy"),
62 | s"oncopycapture" -> EventAttribute("onCopyCapture"),
63 | s"oncut" -> EventAttribute("onCut"),
64 | s"oncutcapture" -> EventAttribute("onCutCapture"),
65 | s"ondblclick" -> EventAttribute("onDblClick"),
66 | s"ondoubleclick" -> EventAttribute("onDoubleClick"),
67 | s"ondoubleclickcapture" -> EventAttribute("onDoubleClickCapture"),
68 | s"ondrag" -> EventAttribute("onDrag"),
69 | s"ondragcapture" -> EventAttribute("onDragCapture"),
70 | s"ondragend" -> EventAttribute("onDragEnd"),
71 | s"ondragendcapture" -> EventAttribute("onDragEndCapture"),
72 | s"ondragenter" -> EventAttribute("onDragEnter"),
73 | s"ondragentercapture" -> EventAttribute("onDragEnterCapture"),
74 | s"ondragexit" -> EventAttribute("onDragExit"),
75 | s"ondragexitcapture" -> EventAttribute("onDragExitCapture"),
76 | s"ondragleave" -> EventAttribute("onDragLeave"),
77 | s"ondragleavecapture" -> EventAttribute("onDragLeaveCapture"),
78 | s"ondragover" -> EventAttribute("onDragOver"),
79 | s"ondragovercapture" -> EventAttribute("onDragOverCapture"),
80 | s"ondragstart" -> EventAttribute("onDragStart"),
81 | s"ondragstartcapture" -> EventAttribute("onDragStartCapture"),
82 | s"ondrop" -> EventAttribute("onDrop"),
83 | s"ondropcapture" -> EventAttribute("onDropCapture"),
84 | s"ondurationchange" -> EventAttribute("onDurationChange"),
85 | s"ondurationchangecapture" -> EventAttribute("onDurationChangeCapture"),
86 | s"onemptied" -> EventAttribute("onEmptied"),
87 | s"onemptiedcapture" -> EventAttribute("onEmptiedCapture"),
88 | s"onencrypted" -> EventAttribute("onEncrypted"),
89 | s"onencryptedcapture" -> EventAttribute("onEncryptedCapture"),
90 | s"onended" -> EventAttribute("onEnded"),
91 | s"onendedcapture" -> EventAttribute("onEndedCapture"),
92 | s"onerror" -> EventAttribute("onError"),
93 | s"onerrorcapture" -> EventAttribute("onErrorCapture"),
94 | s"onfocus" -> EventAttribute("onFocus"),
95 | s"onfocuscapture" -> EventAttribute("onFocusCapture"),
96 | s"oninput" -> EventAttribute("onInput"),
97 | s"oninputcapture" -> EventAttribute("onInputCapture"),
98 | s"oninvalid" -> EventAttribute("onInvalid"),
99 | s"oninvalidcapture" -> EventAttribute("onInvalidCapture"),
100 | s"onkeydown" -> EventAttribute("onKeyDown"),
101 | s"onkeydowncapture" -> EventAttribute("onKeyDownCapture"),
102 | s"onkeypress" -> EventAttribute("onKeyPress"),
103 | s"onkeypresscapture" -> EventAttribute("onKeyPressCapture"),
104 | s"onkeyup" -> EventAttribute("onKeyUp"),
105 | s"onkeyupcapture" -> EventAttribute("onKeyUpCapture"),
106 | s"onload" -> EventAttribute("onLoad"),
107 | s"onloadcapture" -> EventAttribute("onLoadCapture"),
108 | s"onloadstart" -> EventAttribute("onLoadStart"),
109 | s"onloadstartcapture" -> EventAttribute("onLoadStartCapture"),
110 | s"onloadeddata" -> EventAttribute("onLoadedData"),
111 | s"onloadeddatacapture" -> EventAttribute("onLoadedDataCapture"),
112 | s"onloadedmetadata" -> EventAttribute("onLoadedMetadata"),
113 | s"onloadedmetadatacapture" -> EventAttribute("onLoadedMetadataCapture"),
114 | s"onmousedown" -> EventAttribute("onMouseDown"),
115 | s"onmousedowncapture" -> EventAttribute("onMouseDownCapture"),
116 | s"onmouseenter" -> EventAttribute("onMouseEnter"),
117 | s"onmouseleave" -> EventAttribute("onMouseLeave"),
118 | s"onmousemove" -> EventAttribute("onMouseMove"),
119 | s"onmousemovecapture" -> EventAttribute("onMouseMoveCapture"),
120 | s"onmouseout" -> EventAttribute("onMouseOut"),
121 | s"onmouseoutcapture" -> EventAttribute("onMouseOutCapture"),
122 | s"onmouseover" -> EventAttribute("onMouseOver"),
123 | s"onmouseovercapture" -> EventAttribute("onMouseOverCapture"),
124 | s"onmouseup" -> EventAttribute("onMouseUp"),
125 | s"onmouseupcapture" -> EventAttribute("onMouseUpCapture"),
126 | s"onpaste" -> EventAttribute("onPaste"),
127 | s"onpastecapture" -> EventAttribute("onPasteCapture"),
128 | s"onpause" -> EventAttribute("onPause"),
129 | s"onpausecapture" -> EventAttribute("onPauseCapture"),
130 | s"onplay" -> EventAttribute("onPlay"),
131 | s"onplaycapture" -> EventAttribute("onPlayCapture"),
132 | s"onplaying" -> EventAttribute("onPlaying"),
133 | s"onplayingcapture" -> EventAttribute("onPlayingCapture"),
134 | s"ongotpointercapture" -> EventAttribute("onGotPointerCapture"),
135 | s"onlostpointercapture" -> EventAttribute("onLostPointerCapture"),
136 | s"onpointercancel" -> EventAttribute("onPointerCancel"),
137 | s"onpointerdown" -> EventAttribute("onPointerDown"),
138 | s"onpointerenter" -> EventAttribute("onPointerEnter"),
139 | s"onpointerleave" -> EventAttribute("onPointerLeave"),
140 | s"onpointermove" -> EventAttribute("onPointerMove"),
141 | s"onpointerout" -> EventAttribute("onPointerOut"),
142 | s"onpointerover" -> EventAttribute("onPointerOver"),
143 | s"onpointerup" -> EventAttribute("onPointerUp"),
144 | s"onprogress" -> EventAttribute("onProgress"),
145 | s"onprogresscapture" -> EventAttribute("onProgressCapture"),
146 | s"onratechange" -> EventAttribute("onRateChange"),
147 | s"onratechangecapture" -> EventAttribute("onRateChangeCapture"),
148 | s"onreset" -> EventAttribute("onReset"),
149 | s"onresetcapture" -> EventAttribute("onResetCapture"),
150 | s"onscroll" -> EventAttribute("onScroll"),
151 | s"onscrollcapture" -> EventAttribute("onScrollCapture"),
152 | s"onseeked" -> EventAttribute("onSeeked"),
153 | s"onseekedcapture" -> EventAttribute("onSeekedCapture"),
154 | s"onseeking" -> EventAttribute("onSeeking"),
155 | s"onseekingcapture" -> EventAttribute("onSeekingCapture"),
156 | s"onselect" -> EventAttribute("onSelect"),
157 | s"onstalled" -> EventAttribute("onStalled"),
158 | s"onstalledcapture" -> EventAttribute("onStalledCapture"),
159 | s"onsubmit" -> EventAttribute("onSubmit"),
160 | s"onsubmitcapture" -> EventAttribute("onSubmitCapture"),
161 | s"onsuspend" -> EventAttribute("onSuspend"),
162 | s"onsuspendcapture" -> EventAttribute("onSuspendCapture"),
163 | s"ontimeupdate" -> EventAttribute("onTimeUpdate"),
164 | s"ontimeupdatecapture" -> EventAttribute("onTimeUpdateCapture"),
165 | s"ontouchcancel" -> EventAttribute("onTouchCancel"),
166 | s"ontouchcancelcapture" -> EventAttribute("onTouchCancelCapture"),
167 | s"ontouchend" -> EventAttribute("onTouchEnd"),
168 | s"ontouchendcapture" -> EventAttribute("onTouchEndCapture"),
169 | s"ontouchmove" -> EventAttribute("onTouchMove"),
170 | s"ontouchmovecapture" -> EventAttribute("onTouchMoveCapture"),
171 | s"ontouchstart" -> EventAttribute("onTouchStart"),
172 | s"ontouchstartcapture" -> EventAttribute("onTouchStartCapture"),
173 | s"ontransitionend" -> EventAttribute("onTransitionEnd"),
174 | s"ontransitionendcapture" -> EventAttribute("onTransitionEndCapture"),
175 | s"onvolumechange" -> EventAttribute("onVolumeChange"),
176 | s"onvolumechangecapture" -> EventAttribute("onVolumeChangeCapture"),
177 | s"onwaiting" -> EventAttribute("onWaiting"),
178 | s"onwaitingcapture" -> EventAttribute("onWaitingCapture"),
179 | s"onwheel" -> EventAttribute("onWheel"),
180 | s"onwheelcapture" -> EventAttribute("onWheelCapture")
181 | )
182 | }
183 | case class EventAttribute(key: String,
184 | function: String = "-->") extends AttributeType
185 | }
186 |
--------------------------------------------------------------------------------
/src/main/scala/simer/html/converter/HtmlToScalaConverter.scala:
--------------------------------------------------------------------------------
1 | package simer.html.converter
2 |
3 | import org.scalajs.dom
4 | import org.scalajs.dom.ext._
5 | import org.scalajs.dom.html.TextArea
6 | import org.scalajs.dom.raw.{DOMParser, NamedNodeMap, Node}
7 | import simer.html.converter.ConverterType._
8 | import simer.html.converter.tyrian.TyrianConverter
9 | import simer.html.converter.utils.ConverterUtil._
10 |
11 | import scala.scalajs.js
12 | import scala.util.Try
13 |
14 | object HtmlToScalaConverter {
15 |
16 | def main(args: Array[String]): Unit = {
17 | val template = HTMLTemplate.template(runConverter)
18 | dom.document.getElementById("content").appendChild(template.render)
19 | }
20 |
21 | def runConverter(converterType: ConverterType): Unit = {
22 | val htmlCode = dom.document.getElementById("htmlCode").asInstanceOf[TextArea].value
23 | val parsedHtml = new DOMParser().parseFromString(htmlCode, "text/html")
24 | val scalaCodeTextArea = dom.document.getElementById("scalaCode").asInstanceOf[TextArea]
25 | val rootChildNodes = removeGarbageChildNodes(parsedHtml)
26 | //having more then one HTML tree causes the DOMParser to generate an incorrect tree.
27 | val scalaCodes = rootChildNodes.map(toScala(_, converterType))
28 | val scalaCode =
29 | if (scalaCodes.size > 1) {
30 | val fixMe = s"""//FIXME - MULTIPLE HTML TREES PASSED TO THE CONVERTER. THIS MIGHT GENERATE UNEXPECTED CODE. Check is not in the input HTML."""
31 | fixMe + "\n" + scalaCodes.mkString(", ")
32 | } else
33 | scalaCodes.mkString(", ")
34 |
35 | val scalaCodeWithoutParserAddedTags = removeTagsFromScalaCode(htmlCode, scalaCode, "html", "head", "body")
36 | scalaCodeTextArea.value = scalaCodeWithoutParserAddedTags.trim
37 | }
38 |
39 | def removeGarbageChildNodes(node: Node): collection.Seq[Node] =
40 | node.childNodes.filterNot(isGarbageNode)
41 |
42 | def isGarbageNode(node: Node): Boolean =
43 | js.isUndefined(node) || node.nodeName == "#comment" || (node.nodeName == "#text" && node.nodeValue.trim.isEmpty)
44 |
45 | /**
46 | * Recursively generates the output Scala code for each HTML node and it's children.
47 | *
48 | * Filters out comments and empty text's nodes (garbage nodes :D) from the input HTML before converting to Scala.
49 | */
50 | def toScala(node: Node, converterType: ConverterType): String = {
51 | //removes all comment and empty text nodes.
52 | val childrenWithoutGarbageNodes: collection.Seq[Node] = removeGarbageChildNodes(node)
53 |
54 | val children =
55 | childrenWithoutGarbageNodes
56 | .map(toScala(_, converterType))
57 | .mkString(",\n")
58 |
59 | toScala(node, converterType, childrenWithoutGarbageNodes, children)
60 | }
61 |
62 | /**
63 | * Converts a single HTML node, given it's child nodes's (@param children) already converted.
64 | */
65 | private def toScala(node: Node,
66 | converterType: ConverterType,
67 | childrenWithoutGarbageNodes: collection.Seq[Node],
68 | children: String): String = {
69 |
70 | converterType match {
71 | case _: Tyrian =>
72 | TyrianConverter.toScala(
73 | node = node,
74 | converterType = converterType,
75 | childrenWithoutGarbageNodes = childrenWithoutGarbageNodes,
76 | children = children
77 | )
78 |
79 | case _ =>
80 | node.nodeName match {
81 | case "#text" =>
82 | tripleQuote(node.nodeValue)
83 |
84 | case _ =>
85 | val scalaAttrList =
86 | toScalaAttributes(
87 | attributes = node.attributes,
88 | converterType = converterType
89 | )
90 |
91 | val nodeNameLowerCase = node.nodeName.toLowerCase
92 | //fetch the node/tag name supplied by the ConverterType
93 | val replacedNodeName = converterType.tagNameMap.getOrElse(nodeNameLowerCase, nodeNameLowerCase)
94 | val nodeString = s"${converterType.nodePrefix}$replacedNodeName"
95 |
96 | if (scalaAttrList.isEmpty && children.isEmpty) {
97 | converterType match {
98 | case _: Laminar =>
99 | s"$nodeString()" //laminar requires nodes to be closed eg: br()
100 |
101 | case _: ScalaJSReact | _: ScalaTags | _: Outwatch | _: Tyrian =>
102 | nodeString
103 |
104 | }
105 | } else { //this node/tag has attributes or has children
106 | val scalaAttrString =
107 | if (scalaAttrList.isEmpty)
108 | ""
109 | else if (!converterType.newLineAttributes)
110 | scalaAttrList.mkString("\n", ",\n", "")
111 | else
112 | scalaAttrList.mkString(", ")
113 |
114 | val childrenString =
115 | if (children.isEmpty) {
116 | ""
117 | } else {
118 | //text child nodes can be a part of the same List as the attribute List. They don't have to go to a new line.
119 | val isChildNodeATextNode = childrenWithoutGarbageNodes.headOption.exists(_.nodeName == "#text")
120 | val commaMayBe = if (scalaAttrString.isEmpty) "" else ","
121 | val startNewLineMayBe = if (isChildNodeATextNode && (converterType.newLineAttributes || scalaAttrString.isEmpty)) "" else "\n"
122 | //add a newLine at the end if this node has more then one child nodes
123 | val endNewLineMayBe = if (isChildNodeATextNode && childrenWithoutGarbageNodes.size <= 1) "" else "\n"
124 | s"$commaMayBe$startNewLineMayBe$children$endNewLineMayBe"
125 | }
126 |
127 | converterType match {
128 | case _: Tyrian =>
129 | s"$nodeString($scalaAttrString)($childrenString)"
130 |
131 | case _ =>
132 | s"$nodeString($scalaAttrString$childrenString)"
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
139 | /**
140 | * Converts input string of format "list-style: none; padding: 0;"
141 | * to ("list-style" -> "none", "padding" -> "0")
142 | */
143 | def splitAttrValueToTuples(attrValueString: String) =
144 | attrValueString.split(";").map {
145 | string =>
146 | val styleKeyValue = string.split(":")
147 | s""""${styleKeyValue.head.trim}" -> "${styleKeyValue.last.trim}""""
148 | }.mkString(", ")
149 |
150 | /**
151 | * Converts HTML node attributes to Scala attributes
152 | */
153 | def toScalaAttributes(attributes: NamedNodeMap,
154 | converterType: ConverterType): Iterable[String] =
155 | if (js.isUndefined(attributes) || attributes.isEmpty)
156 | List.empty
157 | else
158 | attributes map {
159 | case (attrKey, attrValue) =>
160 | val attrValueString = attrValue.value
161 | val escapedAttrValue = tripleQuote(attrValueString)
162 | val attributeNameMapOption = converterType.attributeNameMap.get(attrKey)
163 |
164 | if (attrKey == "style") {
165 | val styleValuesDictionary =
166 | converterType match {
167 | case _: ScalaJSReact | _: ScalaTags =>
168 | //if scalatags or scalajs-react convert the style value to dictionary
169 | s"js.Dictionary(${splitAttrValueToTuples(attrValueString)})"
170 |
171 | case _: Laminar | _: Outwatch | _: Tyrian =>
172 | //for laminar/outwatch do not split. Simply return the javascript string value.
173 | escapedAttrValue
174 | }
175 |
176 | attributeNameMapOption match {
177 | case Some(attrNameMap) =>
178 | s"""${converterType.attributePrefix}${attrNameMap.key} ${attrNameMap.function} $styleValuesDictionary"""
179 |
180 | case None =>
181 | s"""${converterType.attributePrefix}$attrKey := $styleValuesDictionary"""
182 | }
183 | } else {
184 | attributeNameMapOption match {
185 | case Some(attrNameMap) =>
186 |
187 | val typedValue =
188 | attrNameMap match {
189 | case _: AttributeType.StringAttribute =>
190 | escapedAttrValue
191 |
192 | case _: AttributeType.IntAttribute =>
193 | //if unable to convert to int or else revert back to string
194 | Try(attrValueString.toInt) getOrElse escapedAttrValue
195 |
196 | case _: AttributeType.DoubleAttribute =>
197 | //if unable to convert to double or else revert back to string
198 | Try(attrValueString.toDouble) getOrElse escapedAttrValue
199 |
200 | case _: AttributeType.BooleanAttribute =>
201 | //if unable to convert to boolean or else revert back to string
202 | //Note: In HTML the value of boolean attribute can be set to anything to enable it.
203 | // This should be looked into manually to handle cases where there could be some
204 | // javascript code dependant on the actual value.
205 | if (converterType.isBooleanTypeConversionDisabled)
206 | escapedAttrValue
207 | else
208 | true
209 |
210 | case _: AttributeType.EventAttribute =>
211 | //Event attributes get converted to however the target library's expected syntax.
212 | //https://github.com/simerplaha/html-to-scala-converter/issues/9
213 | converterType match {
214 | case _: ScalaJSReact =>
215 | s"""Callback(js.eval($escapedAttrValue))"""
216 |
217 | case _: ScalaTags | _: Tyrian =>
218 | escapedAttrValue
219 |
220 | case _: Laminar | _: Outwatch =>
221 | s"""(_ => js.eval($escapedAttrValue))"""
222 |
223 | }
224 | }
225 |
226 | s"${converterType.attributePrefix}${attrNameMap.key} ${attrNameMap.function} $typedValue"
227 |
228 | case None =>
229 | if (attrKey.matches("[a-zA-Z0-9]*$"))
230 | s"${converterType.attributePrefix}$attrKey := $escapedAttrValue"
231 | else //else it's a custom attribute
232 | s"""${converterType.customAttributeFunctionName}("$attrKey") := $escapedAttrValue"""
233 | }
234 | }
235 | }
236 |
237 | /**
238 | * Javascript html parser seems to add , and tags the parsed tree by default.
239 | * This remove the ones that are not in the input HTML.
240 | *
241 | * Might not work for tags other then html, head and body. Have not looked into others, didn't need them so far.
242 | */
243 | def removeTagsFromScalaCode(htmlCode: String, scalaCode: String, tagsToRemove: String*): String =
244 | tagsToRemove.foldLeft(scalaCode) {
245 | case (newScalaCode, tagToRemove) =>
246 | s"(?i)<$tagToRemove".r.findFirstMatchIn(htmlCode) match {
247 | case None =>
248 | val scalaCodeWithoutTag = newScalaCode.replaceFirst(s".*$tagToRemove.+", "").trim
249 | if (tagToRemove == "head") //If head if head is empty in html. Result Scalatag would be head() in Scala.
250 | scalaCodeWithoutTag
251 | else
252 | scalaCodeWithoutTag.dropRight(1) //remove the closing ')' for the tag if it's not head.
253 |
254 | case Some(_) =>
255 | newScalaCode
256 | }
257 | }
258 | }
259 |
--------------------------------------------------------------------------------