Search code examples
clojureclojurescriptomreagenthiccup

How to convert HTML tag with style to Hiccup? React problems


I'm trying to parse HTML with CSS into Hiccup in a Reagent project. I am using Hickory. When I parse HTML with inline CSS, React throws an exception.

      (map 
         as-hiccup (parse-fragment "<div style='color:red'>test</div>")
      ) 

The above generates [:div {:style color:red} "test"] & Reactjs returns exception from Reactjs:

Violation: The style prop expects a mapping from style properties to values, not a string.

I believe [:div {:style {"color" "red"}} "test"] must be returned instead.

Here is the code view:

(ns main.views.job
  (:require [reagent.core :as reagent :refer [atom]]
                    [hickory.core :refer [as-hiccup parse parse-fragment]]))

(enable-console-print!)

(defn some-view [uid]
  [:div
     (map as-hiccup (parse-fragment "<div style='color:red'>test</div>"))   
  ])

Solution

  • The whole repo is here and it works. I added the parsing from style tag to a map for React in the core.cljs file:

    (ns hickory-stack.core
      (:require [clojure.string :as s]
                [clojure.walk :as w]
                [reagent.core :as reagent :refer [atom]]
                [hickory.core :as h]))
    
    (enable-console-print!)
    
    (defn string->tokens
      "Takes a string with syles and parses it into properties and value tokens"
      [style]
      {:pre [(string? style)]
       :post [(even? (count %))]}
      (->> (s/split style #";")
           (mapcat #(s/split % #":"))
           (map s/trim)))
    
    (defn tokens->map
      "Takes a seq of tokens with the properties (even) and their values (odd)
       and returns a map of {properties values}"
      [tokens]
      {:pre [(even? (count tokens))]
       :post [(map? %)]}
      (zipmap (keep-indexed #(if (even? %1) %2) tokens)
              (keep-indexed #(if (odd? %1) %2) tokens)))
    
    (defn style->map
      "Takes an inline style attribute stirng and converts it to a React Style map"
      [style]
      (tokens->map (string->tokens style)))
    
    (defn hiccup->sablono
      "Transforms a style inline attribute into a style map for React"
      [coll]
      (w/postwalk
       (fn [x]
         (if (map? x)
           (update-in x [:style] style->map)
           x))
       coll))
    
    ;; Test Data
    
    (def good-style "color:red;background:black; font-style: normal    ;font-size : 20px")
    
    (def html-fragment
      (str "<div style='" good-style "'><div id='a' class='btn' style='font-size:30px;color:white'>test1</div>test2</div>"))
    
    ;; Rendering
    
    (defn some-view []
      [:div (hiccup->sablono
             (first (map h/as-hiccup (h/parse-fragment html-fragment))))])
    
    (reagent/render-component [some-view]
                              (. js/document (getElementById "app")))