1#![warn(missing_docs)]
2
3#[macro_use]
29extern crate lazy_static;
30
31extern crate convert_case;
32
33use convert_case::{Case, Casing};
34use std::collections::BTreeMap;
35
36pub fn convert_props_react(ctx: String) -> String {
38 let mut context = ctx.clone();
39
40 lazy_static! {
41 static ref HTML_PROPS: BTreeMap<&'static str, &'static str> = BTreeMap::from([
43 ("acceptcharset", "acceptCharset"),
44 ("accesskey", "accessKey"),
45 ("allowfullscreen", "allowFullScreen"),
46 ("allowtransparency", "allowTransparency"),
47 ("autocomplete", "autoComplete"),
48 ("autofocus", "autoFocus"),
49 ("autoplay", "autoPlay"),
50 ("cellpadding", "cellPadding"),
51 ("cellspacing", "cellSpacing"),
52 ("charset", "charSet"),
53 ("class", "className"), ("classid", "classID"),
55 ("classname", "className"),
56 ("colspan", "colSpan"),
57 ("contenteditable", "contentEditable"),
58 ("contextmenu", "contextMenu"),
59 ("crossorigin", "crossOrigin"),
60 ("datetime", "dateTime"),
61 ("enctype", "encType"),
62 ("for", "htmlFor"), ("formaction", "formAction"),
64 ("formenctype", "formEncType"),
65 ("formmethod", "formMethod"),
66 ("formnovalidate", "formNoValidate"),
67 ("formtarget", "formTarget"),
68 ("frameborder", "frameBorder"),
69 ("hreflang", "hrefLang"),
70 ("htmlfor", "htmlFor"),
71 ("httpequiv", "httpEquiv"),
72 ("inputmode", "inputMode"),
73 ("keyparams", "keyParams"),
74 ("keytype", "keyType"),
75 ("marginheight", "marginHeight"),
76 ("marginwidth", "marginWidth"),
77 ("maxlength", "maxLength"),
78 ("mediagroup", "mediaGroup"),
79 ("minlength", "minLength"),
80 ("novalidate", "noValidate"),
81 ("radiogroup", "radioGroup"),
82 ("readonly", "readOnly"),
83 ("rowspan", "rowSpan"),
84 ("spellcheck", "spellCheck"),
85 ("srcdoc", "srcDoc"),
86 ("srclang", "srcLang"),
87 ("srcset", "srcSet"),
88 ("tabindex", "tabIndex"),
89 ("usemap", "useMap"),
90 ("accentheight", "accentHeight"),
92 ("alignmentbaseline", "alignmentBaseline"),
93 ("allowreorder", "allowReorder"),
94 ("arabicform", "arabicForm"),
95 ("attributename", "attributeName"),
96 ("attributetype", "attributeType"),
97 ("autoreverse", "autoReverse"),
98 ("basefrequency", "baseFrequency"),
99 ("baseprofile", "baseProfile"),
100 ("baselineshift", "baselineShift"),
101 ("calcmode", "calcMode"),
102 ("capheight", "capHeight"),
103 ("clippath", "clipPath"),
104 ("clippathunits", "clipPathUnits"),
105 ("cliprule", "clipRule"),
106 ("colorinterpolation", "colorInterpolation"),
107 ("colorinterpolationfilters", "colorInterpolationFilters"),
108 ("colorprofile", "colorProfile"),
109 ("colorrendering", "colorRendering"),
110 ("contentscripttype", "contentScriptType"),
111 ("contentstyletype", "contentStyleType"),
112 ("diffuseconstant", "diffuseConstant"),
113 ("dominantbaseline", "dominantBaseline"),
114 ("edgemode", "edgeMode"),
115 ("enablebackground", "enableBackground"),
116 ("externalresourcesrequired", "externalResourcesRequired"),
117 ("fillopacity", "fillOpacity"),
118 ("fillrule", "fillRule"),
119 ("filterres", "filterRes"),
120 ("filterunits", "filterUnits"),
121 ("floodcolor", "floodColor"),
122 ("floodopacity", "floodOpacity"),
123 ("fontfamily", "fontFamily"),
124 ("fontsize", "fontSize"),
125 ("fontsizeadjust", "fontSizeAdjust"),
126 ("fontstretch", "fontStretch"),
127 ("fontstyle", "fontStyle"),
128 ("fontvariant", "fontVariant"),
129 ("fontweight", "fontWeight"),
130 ("glyphname", "glyphName"),
131 ("glyphorientationhorizontal", "glyphOrientationHorizontal"),
132 ("glyphorientationvertical", "glyphOrientationVertical"),
133 ("glyphref", "glyphRef"),
134 ("gradienttransform", "gradientTransform"),
135 ("gradientunits", "gradientUnits"),
136 ("horizadvx", "horizAdvX"),
137 ("horizoriginx", "horizOriginX"),
138 ("imagerendering", "imageRendering"),
139 ("kernelmatrix", "kernelMatrix"),
140 ("kernelunitlength", "kernelUnitLength"),
141 ("keypoints", "keyPoints"),
142 ("keysplines", "keySplines"),
143 ("keytimes", "keyTimes"),
144 ("lengthadjust", "lengthAdjust"),
145 ("letterspacing", "letterSpacing"),
146 ("lightingcolor", "lightingColor"),
147 ("limitingconeangle", "limitingConeAngle"),
148 ("markerend", "markerEnd"),
149 ("markerheight", "markerHeight"),
150 ("markermid", "markerMid"),
151 ("markerstart", "markerStart"),
152 ("markerunits", "markerUnits"),
153 ("markerwidth", "markerWidth"),
154 ("maskcontentunits", "maskContentUnits"),
155 ("maskunits", "maskUnits"),
156 ("numoctaves", "numOctaves"),
157 ("overlineposition", "overlinePosition"),
158 ("overlinethickness", "overlineThickness"),
159 ("paintorder", "paintOrder"),
160 ("pathlength", "pathLength"),
161 ("patterncontentunits", "patternContentUnits"),
162 ("patterntransform", "patternTransform"),
163 ("patternunits", "patternUnits"),
164 ("pointerevents", "pointerEvents"),
165 ("pointsatx", "pointsAtX"),
166 ("pointsaty", "pointsAtY"),
167 ("pointsatz", "pointsAtZ"),
168 ("preservealpha", "preserveAlpha"),
169 ("preserveaspectratio", "preserveAspectRatio"),
170 ("primitiveunits", "primitiveUnits"),
171 ("refx", "refX"),
172 ("refy", "refY"),
173 ("renderingintent", "renderingIntent"),
174 ("repeatcount", "repeatCount"),
175 ("repeatdur", "repeatDur"),
176 ("requiredextensions", "requiredExtensions"),
177 ("requiredfeatures", "requiredFeatures"),
178 ("shaperendering", "shapeRendering"),
179 ("specularconstant", "specularConstant"),
180 ("specularexponent", "specularExponent"),
181 ("spreadmethod", "spreadMethod"),
182 ("startoffset", "startOffset"),
183 ("stddeviation", "stdDeviation"),
184 ("stitchtiles", "stitchTiles"),
185 ("stopcolor", "stopColor"),
186 ("stopopacity", "stopOpacity"),
187 ("strikethroughposition", "strikethroughPosition"),
188 ("strikethroughthickness", "strikethroughThickness"),
189 ("strokedasharray", "strokeDasharray"),
190 ("strokedashoffset", "strokeDashoffset"),
191 ("strokelinecap", "strokeLinecap"),
192 ("strokelinejoin", "strokeLinejoin"),
193 ("strokemiterlimit", "strokeMiterlimit"),
194 ("strokeopacity", "strokeOpacity"),
195 ("strokewidth", "strokeWidth"),
196 ("surfacescale", "surfaceScale"),
197 ("systemlanguage", "systemLanguage"),
198 ("tablevalues", "tableValues"),
199 ("targetx", "targetX"),
200 ("targety", "targetY"),
201 ("textanchor", "textAnchor"),
202 ("textdecoration", "textDecoration"),
203 ("textlength", "textLength"),
204 ("textrendering", "textRendering"),
205 ("underlineposition", "underlinePosition"),
206 ("underlinethickness", "underlineThickness"),
207 ("unicodebidi", "unicodeBidi"),
208 ("unicoderange", "unicodeRange"),
209 ("unitsperem", "unitsPerEm"),
210 ("valphabetic", "vAlphabetic"),
211 ("vhanging", "vHanging"),
212 ("videographic", "vIdeographic"),
213 ("vmathematical", "vMathematical"),
214 ("vectoreffect", "vectorEffect"),
215 ("vertadvy", "vertAdvY"),
216 ("vertoriginx", "vertOriginX"),
217 ("vertoriginy", "vertOriginY"),
218 ("viewbox", "viewBox"),
219 ("viewtarget", "viewTarget"),
220 ("wordspacing", "wordSpacing"),
221 ("writingmode", "writingMode"),
222 ("xchannelselector", "xChannelSelector"),
223 ("xheight", "xHeight"),
224 ("xlinkactuate", "xlinkActuate"),
225 ("xlinkarcrole", "xlinkArcrole"),
226 ("xlinkhref", "xlinkHref"),
227 ("xlinkrole", "xlinkRole"),
228 ("xlinkshow", "xlinkShow"),
229 ("xlinktitle", "xlinkTitle"),
230 ("xlinktype", "xlinkType"),
231 ("xmlnsxlink", "xmlnsXlink"),
232 ("xmlbase", "xmlBase"),
233 ("xmllang", "xmlLang"),
234 ("xmlspace", "xmlSpace"),
235 ("ychannelselector", "yChannelSelector"),
236 ("zoomandpan", "zoomAndPan"),
237 ("onabort", "onAbort"),
239 ("onanimationend", "onAnimationEnd"),
240 ("onanimationiteration", "onAnimationIteration"),
241 ("onanimationstart", "onAnimationStart"),
242 ("onblur", "onBlur"),
243 ("oncanplay", "onCanPlay"),
244 ("oncanplaythrough", "onCanPlayThrough"),
245 ("onchange", "onChange"),
246 ("onclick", "onClick"),
247 ("oncompositionend", "onCompositionEnd"),
248 ("oncompositionstart", "onCompositionStart"),
249 ("oncompositionupdate", "onCompositionUpdate"),
250 ("oncontextmenu", "onContextMenu"),
251 ("oncopy", "onCopy"),
252 ("oncut", "onCut"),
253 ("ondoubleclick", "onDoubleClick"),
254 ("ondrag", "onDrag"),
255 ("ondragend", "onDragEnd"),
256 ("ondragenter", "onDragEnter"),
257 ("ondragexit", "onDragExit"),
258 ("ondragleave", "onDragLeave"),
259 ("ondragover", "onDragOver"),
260 ("ondragstart", "onDragStart"),
261 ("ondrop", "onDrop"),
262 ("ondurationchange", "onDurationChange"),
263 ("onemptied", "onEmptied"),
264 ("onencrypted", "onEncrypted"),
265 ("onended", "onEnded"),
266 ("onerror", "onError"),
267 ("onfocus", "onFocus"),
268 ("oninput", "onInput"),
269 ("onkeydown", "onKeyDown"),
270 ("onkeypress", "onKeyPress"),
271 ("onkeyup", "onKeyUp"),
272 ("onload", "onLoad"),
273 ("onloadeddata", "onLoadedData"),
274 ("onloadedmetadata", "onLoadedMetadata"),
275 ("onloadstart", "onLoadStart"),
276 ("onmousedown", "onMouseDown"),
277 ("onmouseenter", "onMouseEnter"),
278 ("onmouseleave", "onMouseLeave"),
279 ("onmousemove", "onMouseMove"),
280 ("onmouseout", "onMouseOut"),
281 ("onmouseover", "onMouseOver"),
282 ("onmouseup", "onMouseUp"),
283 ("onpaste", "onPaste"),
284 ("onpause", "onPause"),
285 ("onplay", "onPlay"),
286 ("onplaying", "onPlaying"),
287 ("onprogress", "onProgress"),
288 ("onratechange", "onRateChange"),
289 ("onscroll", "onScroll"),
290 ("onseeked", "onSeeked"),
291 ("onseeking", "onSeeking"),
292 ("onselect", "onSelect"),
293 ("onstalled", "onStalled"),
294 ("onsubmit", "onSubmit"),
295 ("onsuspend", "onSuspend"),
296 ("ontimeupdate", "onTimeUpdate"),
297 ("ontouchcancel", "onTouchCancel"),
298 ("ontouchend", "onTouchEnd"),
299 ("ontouchmove", "onTouchMove"),
300 ("ontouchstart", "onTouchStart"),
301 ("ontransitionend", "onTransitionEnd"),
302 ("onvolumechange", "onVolumeChange"),
303 ("onwaiting", "onWaiting"),
304 ("onwheel", "onWheel")
305 ]);
306 };
307
308 let props: Vec<String> = extract_html_props(&context);
309
310 for item in props.iter() {
311 if item == "style" {
312 context = create_style_object(&mut context);
313 } else {
314 let value = HTML_PROPS.get(&*item.to_owned()).unwrap_or(&"");
315
316 if !value.is_empty() {
317 let v = format!("{}=", item);
318 let rp = format!("{}=", value);
319 context = context.replace(&v, &rp);
320 }
321 }
322 }
323
324 context
325}
326
327fn extract_html_props(context: &String) -> Vec<String> {
329 let mut props: Vec<String> = vec![];
330 let mut current_prop = String::from("");
331 let mut space_before_text = false;
332 let mut inside_tag = false;
333
334 for c in context.chars() {
336 if inside_tag {
337 if c == '=' {
338 space_before_text = false;
339 props.push((*current_prop).to_string());
340 current_prop.clear();
341 }
342 if space_before_text {
343 current_prop.push(c);
344 }
345 if c == ' ' {
346 space_before_text = true;
347 }
348 }
349 if c == '<' {
350 inside_tag = true;
351 }
352 if c == '>' {
353 inside_tag = false;
354 }
355 }
356
357 props.sort();
359
360 props
361}
362
363pub fn create_style_object(ctx: &mut String) -> String {
365 let style_matcher = if ctx.contains("style='") {
366 r#"'"#
367 } else {
368 r#"""#
369 };
370
371 let style_start = format!(r#"style={}"#, style_matcher);
372 let (style_string, start_idx, end_idx) = text_between(&ctx, &style_start, style_matcher);
373
374 let mut current_prop = String::from("");
375 let mut space_before_text = false;
376
377 let mut style_replacer = style_string.clone();
378
379 for c in style_string.chars() {
381 if space_before_text {
382 current_prop.push(c);
383 }
384 if c == ';' {
385 space_before_text = true;
386 style_replacer = style_replacer.replace(";", ",");
387 current_prop.clear();
388 }
389 if c == ':' {
390 let clp = ¤t_prop.trim();
391 let camel_style = &clp.to_case(Case::Camel);
392
393 style_replacer = style_replacer.replace(&*clp, &camel_style);
394 space_before_text = false;
395 current_prop.clear();
396 }
397 }
398
399 let mut space_before_text = false;
400 let mut current_prop = String::from("");
401
402 let mut style_clone = style_replacer.clone().to_owned();
403
404 for (i, c) in style_replacer.chars().enumerate() {
406 if space_before_text && c != ',' {
407 current_prop.push(c);
408 }
409
410 if space_before_text && c == ',' {
411 let current = current_prop.trim();
412 style_clone = style_clone.replace(¤t, &format!(r#""{}""#, current).to_string());
413 space_before_text = false;
414 }
415
416 if c == ':' {
417 space_before_text = true;
418 current_prop.clear();
419 }
420
421 if i + 1 == style_replacer.len() {
422 let current = current_prop.trim();
423 style_clone = style_clone.replace(¤t, &format!(r#""{}""#, current).to_string());
424 space_before_text = false;
425 }
426 }
427
428 let style_replacer = format!("{}{}{}", "style={{", style_clone, "}}");
429
430 ctx.replace_range(start_idx - 7..start_idx + end_idx + 1, &style_replacer);
431
432 ctx.to_owned()
433}
434
435fn text_between(search_str: &String, start_str: &String, end_str: &str) -> (String, usize, usize) {
437 let start_idx = {
438 let start_point = search_str.find(start_str);
439 start_point.unwrap() + start_str.len()
440 };
441
442 let remaining = &search_str[start_idx..];
443 let end_idx = remaining.find(&end_str).unwrap_or(remaining.len());
444
445 (remaining[..end_idx].to_string(), start_idx, end_idx)
446}
447
448pub fn convert_to_react(ctx: String, component_name: String) -> String {
450 let react_html = convert_props_react(ctx);
451 let mut react_html = react_html.trim().to_owned();
452
453 if react_html.starts_with("<!DOCTYPE html>") {
455 react_html = react_html.replace("<!DOCTYPE html>", "");
456 }
457 if react_html.starts_with("<html>") {
458 react_html = react_html.replace("<html>", "");
459 react_html = react_html.replace("</html>", "");
460 }
461
462 let component_name = format!(" {}", component_name.trim());
463
464 let component = format!(
465 r#"import React from "react"
466
467function{}() {{
468 return (
469 {}
470 )
471}}"#,
472 component_name, react_html
473 );
474
475 component
476}
477
478#[test]
479fn convert_props_react_test() {
480 let html = r#"<img class="something">"#;
482 let react_html = convert_props_react(html.to_string());
483
484 assert_eq!("<img className=\"something\">", react_html);
485
486 let html = r#"<img class="something" for="mystuff">"#;
488 let react_html = convert_props_react(html.to_string());
489
490 assert_eq!(
491 "<img className=\"something\" htmlFor=\"mystuff\">",
492 react_html
493 );
494
495 let html = r#"<img class="something" for="mystuff" tabindex="2">"#;
497 let react_html = convert_props_react(html.to_string());
498
499 assert_eq!(
500 "<img className=\"something\" htmlFor=\"mystuff\" tabIndex=\"2\">",
501 react_html
502 );
503}
504
505#[test]
506fn extract_html_props_test() {
507 let html = r#"<img class="something" for="mystuff" tabindex="2">"#;
508 let props = extract_html_props(&html.to_string());
509
510 assert_eq!(props, vec!["class", "for", "tabindex"]);
511}
512
513#[test]
514fn convert_props_react_styles_test() {
515 let html = r#"<img style="color: white; background-color: black">"#;
516 let props = convert_props_react(html.to_string());
517
518 assert_eq!(
519 r#"<img style={{color: "white", backgroundColor: "black"}}>"#,
520 props
521 );
522}
523
524#[test]
525fn convert_props_react_children_test() {
526 let html = r#"<div class="something" for="mystuff" tabindex="2" style="color: white; background-color: black">
527 <div class="child" for="mychildstuff" tabindex="2" style="color: white; background-color: black">
528 child
529 </div>
530</div>"#;
531 let props = convert_props_react(html.to_string());
532
533 assert_eq!(
534 r###"<div className="something" htmlFor="mystuff" tabIndex="2" style={{color: "white", backgroundColor: "black"}}>
535 <div className="child" htmlFor="mychildstuff" tabIndex="2" style={{color: "white", backgroundColor: "black"}}>
536 child
537 </div>
538</div>"###,
539 props
540 );
541}
542
543#[test]
544fn convert_react_component_test() {
545 let html = r#"<div class="something" for="mystuff" tabindex="2" style="color: white; background-color: black">
546 <div class="child" for="mychildstuff" tabindex="2" style="color: white; background-color: black">
547 child
548 </div>
549 </div>"#;
550
551 let props = convert_to_react(html.trim().to_string(), "Something".to_string());
552
553 let style_object = r#"style={{color: "white", backgroundColor: "black"}}"#;
554
555 assert_eq!(
556 format!(
557 r###"import React from "react"
558
559function Something() {{
560 return (
561 <div className="something" htmlFor="mystuff" tabIndex="2" {}>
562 <div className="child" htmlFor="mychildstuff" tabIndex="2" {}>
563 child
564 </div>
565 </div>
566 )
567}}"###,
568 style_object, style_object
569 ),
570 props
571 );
572}