Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
(ns xyz.utils.css-parser
"
Generates a bunch of keywords from parsing a CSS files.
Useful to get autocompletion when using Cursive (or other editors?)
Then you can write you cljs/sablono code like this:
[:div {:class [:mdl-chip--contact
:mdl-chip--deletable] (etc)
"
(:require [clojure.java.io :as io]
[clojure.string :as str])
(:import (com.helger.css.decl CascadingStyleSheet CSSImportRule CSSNamespaceRule
CSSStyleRule CSSPageRule CSSMediaRule CSSFontFaceRule
CSSKeyframesRule CSSViewportRule CSSSupportsRule CSSUnknownRule
CSSSelector CSSDeclaration CSSMediaQuery CSSKeyframesBlock ECSSSelectorCombinator CSSSelectorMemberNot IHasCSSDeclarations CSSSelectorSimpleMember CSSSelectorAttribute CSSSelectorMemberFunctionLike ECSSAttributeOperator CSSSupportsConditionNested CSSSupportsConditionDeclaration ECSSSupportsConditionOperator CSSSupportsConditionNegation)
com.helger.commons.charset.CCharset
com.helger.css.ECSSVersion
com.helger.css.writer.CSSWriterSettings
com.helger.css.reader.CSSReader))
;; Some of this is stolen from:
;; https://github.com/degree9/dickory-dock
(defprotocol CSSRepresentable
"Objects that can be represented as CSS Rule node maps, similar to
hickory.core, implement this protocol to make the conversion."
(as-css [this]
"Converts the node given into a hickory-css-format data structure. The
node must have an implementation of the CSSRepresentable protocol;
nodes created by parse-css already do."))
(def writer-settings (CSSWriterSettings. (. ECSSVersion CSS30) false))
(defn vec-css [this] (not-empty (into [] (map as-css this))))
(defn get-declarations
[^IHasCSSDeclarations this]
(not-empty (into (sorted-map) (map as-css (.getAllDeclarations this)))))
(extend-protocol CSSRepresentable
CascadingStyleSheet
(as-css [this] {:type :stylesheet
:rules (->> (concat (map as-css (.getAllImportRules this))
(map as-css (.getAllNamespaceRules this))
(map as-css (.getAllRules this)))
(into [])
not-empty)})
;;;;;;;;;;;;;;;;;;; RULES
CSSImportRule
(as-css [this] {:type :import
:at-rule "@import"
:url (.getLocationString this)
:queries (vec-css (.getAllMediaQueries this))})
CSSNamespaceRule
(as-css [this] {:type :namespace
:at-rule "@namespace"
:prefix (.getNamespacePrefix this)
:url (.getNamespaceURL this)})
CSSMediaRule
(as-css [this] {:type :media
:at-rule "@media"
:queries (vec-css (.getAllMediaQueries this))
:rules (vec-css (.getAllRules this))})
CSSFontFaceRule
(as-css [this] {:type :fontface
:at-rule "@font-face"
:declarations (get-declarations this)})
CSSKeyframesRule
(as-css [this] {:type :keyframes
:at-rule "@keyframes"
:animation (.getAnimationName this)
:rules (vec-css (.getAllBlocks this))})
CSSKeyframesBlock
(as-css [this] {:type :keyframesblock
:selector (vec (.getAllKeyframesSelectors this))
:declarations (get-declarations this)})
CSSViewportRule
(as-css [this] {:type :viewport
:at-rule "@viewport"
:declarations (get-declarations this)})
CSSSupportsRule
(as-css [this] {:type :supports
:at-rule "@supports"
:condition (vec-css (.getAllSupportConditionMembers this))
:rules (vec-css (.getAllRules this))})
CSSStyleRule
(as-css [this] {:type :style
:selectors (vec-css (.getAllSelectors this))
:declarations (get-declarations this)})
CSSUnknownRule
(as-css [this] {:type :unknown
:at-rule (.getDeclaration this)
:parameters (.getParameterList this)
:rules (vec-css (.getBody this))})
;;;;;;;;;;;;;;;;;;;; MISC
CSSMediaQuery
(as-css [this] (.getAsCSSString this writer-settings 0))
CSSSelector
#_(as-css [this] (.getAsCSSString this writer-settings 0))
(as-css [this] (mapv
(fn [i]
(as-css (.getMemberAtIndex this i)))
(range 0 (.getMemberCount this))))
CSSDeclaration
(as-css [this] [(keyword (.getProperty this))
(.getAsCSSString (.getExpression this) writer-settings 0)])
;;;;;;;;;;;;;;;;;; Process ICSSSupportsConditionMember
CSSSupportsConditionNested
(as-css [this] {:type :nested
:conditions (vec-css (.getAllMembers this))})
CSSSupportsConditionNegation
(as-css [this] {:type :not
:condition (as-css (.getSupportsMember this))})
ECSSSupportsConditionOperator
(as-css [this] {:type :condition
:condition (.getName this)})
CSSSupportsConditionDeclaration
(as-css [this] {:type :declaration
:declaration (as-css (.getDeclaration this))})
;;;;;;;;;;;;;;;;;; Process ICSSSelectorMember
ECSSSelectorCombinator
(as-css [this] {:type :combinator
:combinator (.getName this)})
CSSSelectorMemberNot
(as-css [this] {:not (vec-css (.getAllSelectors this))})
CSSSelectorSimpleMember
(as-css [this] {:type (cond
(.isHash this) :id
(.isClass this) :class
(.isPseudo this) :pseudo
(.isElementName this) :element)
:simple (.getValue this)})
CSSSelectorMemberFunctionLike
(as-css [this] {:type :function
:function (.getFunctionName this)
:selector (as-css (.getParameterExpression this))})
CSSSelectorAttribute
(as-css [this]
(let [ns-prefix (.getNamespacePrefix this)
op (as-css (.getOperator this))
attr-val (.getAttrValue this)]
(cond-> {:type :attribute
:attr (.getAttrName this)}
ns-prefix (assoc :ns ns-prefix)
op (assoc :op op)
attr-val (assoc :value attr-val))))
;; ~=, =, ^= etc:
ECSSAttributeOperator
(as-css [this] (.getName this))
nil
(as-css [this] nil))
(defn parse-css [^String string]
(as-css (CSSReader/readFromString string (. CCharset CHARSET_UTF_8_OBJ) (. ECSSVersion CSS30))))
(defn parse-stylesheet [file]
(as-css (CSSReader/readFromFile file (. CCharset CHARSET_UTF_8_OBJ) (. ECSSVersion CSS30))))
#_(def css-data
(parse-stylesheet
(io/file "." "third-party/mdl/1.2.1" "material.blue-orange.min.css")))
#_(spit "/tmp/mdl-rules.clj"
(clojure.pprint/pprint css-data))
(defn get-all-classes
"Returns a sorted collection of all css classes"
[css-data]
(require 'com.rpl.specter)
(sort
(into #{}
(comp (map #(subs % 1))
(map keyword))
(com.rpl.specter/select
[(com.rpl.specter/walker (fn [x]
(and (map? x)
(= :class (:type x))
(:simple x))))
:simple]
css-data))))
#_(get-all-classes css-data)
@rauhs
Copy link
Author

rauhs commented Dec 11, 2016

Example (partial) output for the css-data:

{:type :stylesheet,
 :rules [{:type :style,
          :selectors [[{:type :element, :simple "html"}]],
          :declarations {:color "rgba(0,0,0,.87)"}}
         {:type :style,
          :selectors [[{:type :pseudo, :simple "::-moz-selection"}]],
          :declarations {:background "#b3d4fc", :text-shadow "none"}}
         {:type :style,
           :selectors [[{:type :element, :simple "a"}
                        {:type :attribute, :attr "href", :op "^=", :value "\"#\""}
                        {:type :pseudo, :simple ":after"}]
                       [{:type :element, :simple "a"}
                        {:type :attribute, :attr "href", :op "^=", :value "\"javascript:\""}
                        {:type :pseudo, :simple ":after"}]],
           :declarations {:content "\"\""}}
]}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment