{"id":115,"date":"2005-03-07T21:22:18","date_gmt":"2005-03-07T19:22:18","guid":{"rendered":"http:\/\/blogs.bl0rg.net\/netzstaub\/?p=115"},"modified":"2005-03-07T21:22:18","modified_gmt":"2005-03-07T19:22:18","slug":"css-formattierer","status":"publish","type":"post","link":"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/03\/07\/css-formattierer\/","title":{"rendered":"CSS Formattierer"},"content":{"rendered":"<p>\nDer Javascript-Compiler ist fertig, HTML kann ich aus dem Lisp auch generieren. Um eine halbwegs komplette Webseitenl&#246;sung zu bauen braucht jedoch eine weitere &#8220;Sprache&#8221;: CSS. Nat&#252;rlich ist CSS keineswegs irgendwie kompatibel mit dem Rest, was die Syntax angeht. Und der Emacs-Mode, den ich finden konnte, passt mir &#252;berhaupt nicht. Was bleibt einem also &#252;brig, als ein LISP nach CSS Formattierer zu bauen? Kann sein, dass ich an dem NIH-Syndrom leide, aber ich kann ziemlich gut damit leben \ud83d\ude42\n<\/p>\n<p>\nZum Gl&#252;ck ist CSS &#8220;einfach&#8221;, was die Syntax und Struktur einer Datei angeht. Mittels &#8220;Selektoren&#8221; werden Knoten aus dem HTML Dokument ausgew&#228;hlt, und ihnen werden dann &#8220;Properties&#8221;, die jeweils einen Namen und einen Wert haben, zugewiesen. Zum Beispiel:\n<\/p>\n<p>\n* {<br \/>\n<br \/>  background-color: black;<br \/>\n<br \/>}\n<\/p>\n<p>\nDieses CSS Konstrukt weist allen Knoten (der Selektor &#8220;*&#8221; selektiert alles) die Property &#8220;background-color&#8221; mit dem Wert &#8220;black&#8221; zu. Es gibt eine ganze Reihe an Properties, die von dem CSS 1 Standard festgelegt werden (es gibt auch CSS 2, aber das kenn ich jetzt nicht so wirklich). Viele von diesen Properties haben auch eine feste Anzahl an m&#246;glichen Werten. Das ist so ein bisschen wie eine Enumeration in C. Zum Beispiel unterst&#252;tzt die Property &#8220;font-style&#8221; die Werte &#8220;normal&#8221;, &#8220;italic&#8221; oder &#8220;oblique&#8221;. Andere Properties unterst&#252;tzen numerische Werte, unter Umst&#228;nden gefolgt von einer Einheit. Gr&#246;ssen kann man z.B. in Pixel angeben (z.B. &#8220;10px&#8221;), oder als gr&#246;ssen von einem &#8220;m&#8221; (z.B. &#8220;10em&#8221;). Man kann sie auch als Prozentangaben der &#252;bergeordneten Element angeben (z.B. &#8220;10%&#8221;). Andere Werte, wie Farben, k&#246;nnen entweder Farbnamen oder hexadecimal-Darstellungen der Farben in RGB annehmen. Also ein recht un&#252;bersichtliches Gew&#228;chs.\n<\/p>\n<p>\nDie Frage stellt sich, wie man diese CSS-Struktur jetzt in Lisp darstellen soll. Daf&#252;r bin ich dem selben Ansatz gefolgt wie bei meinem Javascript-Compiler: im Zweifelsfall alles lasch handeln, der Programmierer wird schon wissen, was er tut. Ein Selektor kann also ein Symbol sein, ein String, oder eine Liste. Wenn es eine Liste ist, dann werden die einzelnen Element mit &#8220;,&#8221; zusammengebunden, es ist also eine Oder-Verkn&#252;pfung. Es gibt aber bei Selektoren auch die M&#246;glichkeit, eine Hierarchie anzugeben. Wenn man z.B. die Kinder von dem Element P mit Id &#8220;foobar ansprechen will: &#8220;p#foobar *&#8221;. Diese hierarchischen Selektoren muss man in meinem CSS Generator ausschreiben. Ist nicht so ganz schick, aber f&#252;rs erste tut. Jetzt als erstes ein Beispiel, damit ich nicht ins Leere blogge, und damit ihr euch vorstellen k&#246;nnt, was ich meine.\n<\/p>\n<p>\nUnser CSS:\n<\/p>\n<p>\nbody,td,p {<br \/>\n<br \/>   font-family:Verdana, &#8220;Trebuchet MS&#8221;, Trebuchet, Arial, sans-serif;<br \/>\n<br \/>   font-size:11px;<br \/>\n<br \/>   line-height:16px;<br \/>\n<br \/>   color:#666666;<br \/>\n<br \/>}\n<\/p>\n<p>\nli {<br \/>\n<br \/>   margin:3px 0px;<br \/>\n<br \/>   padding:0px 0px;<br \/>\n<br \/>}\n<\/p>\n<p>\na:link,a:visited {<br \/>\n<br \/>   color:#989833;<br \/>\n<br \/>   text-decoration:none;<br \/>\n<br \/>}\n<\/p>\n<p>\na:hover,a:active {<br \/>\n<br \/>   text-decoration:underline;<br \/>\n<br \/>}\n<\/p>\n<p>\nJetzt die LISP-Darstellung:\n<\/p>\n<p>\n(css-file<br \/>\n<br \/>   ;; general tags<br \/>\n<br \/>   ((:body :td :p)<br \/>\n<br \/>      :font-family &#8220;Verdana, \\&#8221;Trebuchet MS\\&#8221;, Trebuchet, Arial, sans-serif&#8221;<br \/>\n<br \/>      :font-size   &#8220;11px&#8221;<br \/>\n<br \/>      :line-height &#8220;16px&#8221;<br \/>\n<br \/>      :color       &#8220;#666666&#8221;)<\/p>\n<p>    (:li<br \/>\n<br \/>        :margin   &#8220;3px 0px&#8221;<br \/>\n<br \/>        :padding  &#8220;0px 0px&#8221;)<br \/>\n<br \/>    ((&#8220;a:link&#8221; &#8220;a:visited&#8221;)<br \/>\n<br \/>        :color           &#8220;#989833&#8221;<br \/>\n<br \/>        :text-decoration :none)<br \/>\n<br \/>    ((&#8220;a:hover&#8221; &#8220;a:active&#8221;)<br \/>\n<br \/>        :text-decoration :underline))\n<\/p>\n<p>\nDie CSS-Regeln sind jetzt also&#8230; Listen (!). Das erste Element einer CSS-Regel-Liste ist der Selektor. Der Rest sind dann die Properties. Die Namen der Properties sind eigentlich im CSS Standard festgelegt, und bestehen alle aus lowercase-Strings. Man kann sie also durch Keyword-Symbole darstellen. Damit ergibt sich f&#252;r die CSS-Regel auch eine Struktur, die Lisp-Programmierern geheuer ist. Man kann z.B. auf die Property einer CSS-Regel mit (GETF (CDR RULE) PROPERTY) zur&#252;ckgreifen. In bestimmten F&#228;llen (Browserspezifische Extensions z.B.) will man auch &#8220;komisch&#8221; geformte Properties angeben wollen, Strings gehen also immer noch. Hier sind alle Property-Werte Strings, es gehen aber auch wieder Symbole (die nach lowercase-Strings konvertiert werden) oder Nummern.\n<\/p>\n<p>\nIn dem obigen Beispiel sieht man auch, was f&#252;r Selektoren m&#246;glich sind. Um Tags zu selektieren kann man einfach das Keyword verwenden. Das ist dann ein bisschen so wie bei dem HTML-Generator. Man kann aber auch Strings angeben. Z.B. kann man nicht A:HOVER als Symbol hinschreiben, weil der LISP-Reader das als &#8220;das Symbol HOVER im Package A&#8221; interpretiert. Deswegen also &#8220;a:hover&#8221;. Die erste CSS-Regel soll auf BODY, TD und P Elemente zugleich angewendet werden, ich hab daraus also eine Liste gemacht.\n<\/p>\n<p>\nJetzt aber genug an dem Beispiel rumerkl&#228;rt, hier der Lisp Code. Als erstes brauchen wir eine Funktion, die aus unseren Werten (Symbol, String oder Wasauchimmer) ein String generiert. Diese Funktion wird dann sowohl auf die Selektoren als auch auf die Propertynamen als auch auf die Propertywerte losgelassen.\n<\/p>\n<pre>(defun val-to-string (val)\n  (cond ((stringp val) val)\n\t((symbolp val) (string-downcase (symbol-name val)))\n\t(t (princ-to-string val))))\n<\/pre>\n<p>Die Funktion ist weitgehend selbsterkl&#228;rend. Interessant ist, dass die meistens Funktionen die wir jetzt geschrieben haben sind stark &#228;hneln. Sie laufen sozusagen einen Baum ab, und machen auf dem Baum Pattern-Matching. Wenn der Wert hier ein String ist, wird er als solcher zur&#252;ckgegeben. Das gibt dem Programmier alle Freiheiten, die er braucht, um das Output zu tweaken. Wenn ihm Lustig ist kann er hier komplette CSS-Regeln auch &#252;bergeben, oder CSS-Kommentare. Der CSS-Generator ist also &#8220;Zukunftssicher&#8221; (*hust* *hust*).<\/p>\n<p>Als n&#228;chstes brauchen wir einen Datentyp f&#252;r unsere CSS-Rules (als interne Darstellung). Nat&#252;rlich verwenden wir dazu auch wieder Listen, und bauen die passenden Funktionen als Abstraktionen dazu.<\/p>\n<pre>(defun make-css-rule (selectors properties)\n  (list (mapcar #'val-to-string\n\t\t(if (atom selectors)\n\t\t    (list selectors)\n\t\t    selectors))\n\tproperties))\n\n(defun css-rule-selectors (css-rule)\n  (first css-rule))\n\n(defun css-rule-properties (css-rule)\n  (second css-rule))\n\n(defmacro css-rule (selectors &#38;rest properties)\n  `(make-css-rule ',selectors ',properties))\n<\/pre>\n<p>MAKE-CSS-RULE erleichtert uns schon die Arbeit, indem es die Selektoren schon zu Strings konvertiert. Es konvertiert auch einen einzelnen Selektor zu einem OR-Ausdruck mit einem Element (also eine Liste mit einem Element). Dadurch haben wir sp&#228;ter weniger Spezialf&#228;lle zu ber&#252;cksichtigen. Das Makro CSS-RULE ist nur da, weil ich faul bin, und nicht immer quoten will.<\/p>\n<p>CSS kann man auch inline als Attribut eines HTML Elements angeben. Da werden aber keine Selektoren mehr angegeben, weil der Knoten ja schon ausgew&#228;hlt wurde. F&#252;r diesen Minimalfall brauchen wir also eine Funktion, die eine Property nach String konvertiert. Diese ist erstaunlich kompliziert:<\/p>\n<pre>(defun propval-to-string (propval)\n  (format nil \"~A:~A\" (val-to-string (first propval))\n\t  (val-to-string (second propval))))\n<\/pre>\n<p>Das passende Makro zum Einsetzen in dem HTML Generator ist auch nicht wesentlich schwieriger:<\/p>\n<pre>(defmacro css-inline (&#38;rest propvals)\n  `(concatenate 'string ,@(loop for propval on propvals by #'cddr\n\t\t\t\tcollect (propval-to-string propval))))\n<\/pre>\n<p>Mit diesem Grundbaustein k&#246;nnen wir jetzt die Funktion CSS-RULE-TO-STRING bauen, die noch die Selektoren davor packt:<\/p>\n<pre>(defun css-rule-to-string (css-rule)\n  (format nil \"~A {~%~{~A;~%~}}~%~%\"\n\t  (string-join (css-rule-selectors css-rule) \",\")\n\t  (loop for propval on (css-rule-properties css-rule) by #'cddr\n\t\t    collect (concatenate 'string \"   \" (propval-to-string propval)))))\n<\/pre>\n<p>Das ist wie immer Hack-Code, deswegen auch die vielen Loops (ich kann damit besser Prototypen). Wo wir jetzt eine Regel zu einem String konvertieren k&#246;nnen ist der Rest nur noch formalkrams:<\/p>\n<pre>(defmacro css (&#38;rest rules)\n  `((:style :type \"text\/css\")\n    (:princ #\\Newline \"&lt;!--\" #\\Newline)\n    (:princ ,@(mapcar #'(lambda (rule) `(css-rule-to-string (css-rule ,@rule))) rules))\n    (:princ \"--&gt;\" #\\Newline)))\n\n(defmacro css-file (&#38;rest rules)\n  `(html\n    (:princ\n     ,@(mapcar #'(lambda (rule) `(css-rule-to-string (css-rule ,@rule))) rules))))\n<\/pre>\n<p>Zum Schluss ein paar Anwendungsbeispiele:<\/p>\n<pre>;;; generate a CSS file\n#+nil\n(html-stream *standard-output* \n      (css-file (* :border \"1px solid black\")\n\t    (div.bl0rg :font-family \"serif\")\n\t    ((\"a:active\" \"a:hoover\") :color \"black\" :size \"200%\")))\n\n\n;;; generate an inline CSS spec in a HTML head element\n#+nil\n(html-stream *standard-output* \n  (html\n   (:html\n    (:head\n     (css (* :border \"1px solid black\")\n\t  (div.bl0rg :font-family \"serif\")\n\t  ((\"a:active\" \"a:hoover\") :color \"black\" :size \"200%\"))))))\n\n;;; generate a style attribute for a DIV element\n#+nil\n(html-stream *standard-output* \n      (html (:html (:body ((:div :style (css-inline :border \"1px solid black\"))\n\t\t\t   \"foobar\")))))\n<\/pre>\n<p>Das wars, jetzt haben wir alle Werkzeuge, um Webseiten kompletten aus dem Lisp zu generieren.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Der Javascript-Compiler ist fertig, HTML kann ich aus dem Lisp auch generieren. Um eine halbwegs komplette Webseitenl&#246;sung zu bauen braucht jedoch eine weitere &#8220;Sprache&#8221;: CSS. Nat&#252;rlich ist CSS keineswegs irgendwie kompatibel mit dem Rest, was die Syntax angeht. Und der &hellip; <a href=\"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/03\/07\/css-formattierer\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-115","post","type-post","status-publish","format-standard","hentry","category-lispstaub"],"_links":{"self":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/115","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/comments?post=115"}],"version-history":[{"count":0,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/115\/revisions"}],"wp:attachment":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/media?parent=115"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/categories?post=115"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/tags?post=115"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}