diff --git a/Haskell/blog/Html.hs b/Haskell/blog/Html.hs
new file mode 100644
index 0000000..444117e
--- /dev/null
+++ b/Haskell/blog/Html.hs
@@ -0,0 +1,16 @@
+module Html
+ ( Html
+ , Title
+ , Structure
+ , html_
+ , p_
+ , h1_
+ , body_
+ , head_
+ , title_
+ , append_
+ , render
+ )
+ where
+
+import Html.Internal
diff --git a/Haskell/blog/Html/Internal.hs b/Haskell/blog/Html/Internal.hs
new file mode 100644
index 0000000..512a358
--- /dev/null
+++ b/Haskell/blog/Html/Internal.hs
@@ -0,0 +1,84 @@
+module Html.Internal where
+
+-- * Types
+
+newtype Html = Html String
+
+newtype Structure = Structure String
+
+type Title = String
+
+-- * EDSL
+
+html_ :: Title -> Structure -> Html
+html_ title content = Html
+ $ el "html"
+ $ el "head"
+ $ (el "title" $ escape title) ++ el "body" (getStructuredString content)
+
+body_ :: String -> Structure
+body_ = Structure . el "body"
+
+head_ :: String -> Structure
+head_ = Structure . el "head"
+
+title_ :: String -> Structure
+title_ = Structure . el "title"
+
+p_ :: String -> Structure
+p_ = Structure . el "p" . escape
+
+h1_ :: String -> Structure
+h1_ = Structure . el "h1" . escape
+
+append_ :: Structure -> Structure -> Structure
+append_ c1 c2 = Structure (getStructuredString c1 ++ getStructuredString c2)
+
+
+ul_ :: [Structure] -> Structure
+ul_ = list "ul"
+
+ol_ :: [Structure] -> Structure
+ol_ = list "ol"
+
+code_ :: String -> Structure
+code_ = Structure . el "pre" . escape
+
+-- * Render
+
+render :: Html -> String
+render html =
+ case html of
+ Html str -> str
+
+-- * Utilities
+
+el :: String -> String -> String
+el tag content =
+ "<" ++ tag ++ ">" ++ content ++ "" ++ tag ++ ">"
+
+getStructuredString :: Structure -> String
+getStructuredString content =
+ case content of
+ Structure str -> str
+
+escape :: String -> String
+escape =
+ let
+ escapeChar c =
+ case c of
+ '<' -> "<"
+ '>' -> ">"
+ '&' -> "&"
+ '"' -> """
+ '\'' -> "'"
+ _ -> [c]
+ in
+ concat . map escapeChar
+
+list :: String -> [Structure] -> Structure
+list listType =
+ Structure
+ . el listType
+ . concat
+ . map (el "li" . getStructuredString)
diff --git a/Haskell/blog/Main.hs b/Haskell/blog/Main.hs
index 514609d..dcef8bd 100644
--- a/Haskell/blog/Main.hs
+++ b/Haskell/blog/Main.hs
@@ -1,48 +1,6 @@
module Main where
-newtype Html = Html String
-
-newtype Structure = Structure String
-
-type Title = String
-
-el :: String -> String -> String
-el tag content =
- "<" ++ tag ++ ">" ++ content ++ "" ++ tag ++ ">"
-
-html_ :: Title -> Structure -> Html
-html_ title content = Html
- $ el "html"
- $ el "head"
- $ (el "title" title) ++ el "body" (getStructuredString content)
-
-body_ :: String -> Structure
-body_ = Structure . el "body"
-
-head_ :: String -> Structure
-head_ = Structure . el "head"
-
-title_ :: String -> Structure
-title_ = Structure . el "title"
-
-p_ :: String -> Structure
-p_ = Structure . el "p"
-
-h1_ :: String -> Structure
-h1_ = Structure . el "h1"
-
-append_ :: Structure -> Structure -> Structure
-append_ c1 c2 = Structure (getStructuredString c1 ++ getStructuredString c2)
-
-render :: Html -> String
-render html =
- case html of
- Html str -> str
-
-getStructuredString :: Structure -> String
-getStructuredString content =
- case content of
- Structure str -> str
+import Html
main :: IO ()
main = putStrLn