Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added support for inline markup in paragraphs and headings.

  • Loading branch information...
commit 86729f279efb6ce925251a9d628faf858ab5ece1 1 parent 73a5b03
Malcolm Sparks authored
76 src/main/clojure/clj_markdown/core.clj
View
@@ -16,7 +16,9 @@
(ns clj-markdown.core
(:use clojure.contrib.pprint)
- (:require [clojure.java.io :as io]))
+ (:require [clojure.java.io :as io]
+ [clojure.zip :as zip]
+ [clojure.contrib.zip-filter :as zipf]))
(defprotocol LineProcessor
(process-line [this state])
@@ -73,12 +75,59 @@ cause the process to run infinitely."
[(keyword tag)]
[(keyword tag) attrs]))))
+(defn- fold [stack]
+ (cond (empty? stack) nil
+ (= (count stack) 1) (first stack)
+ :otherwise (conj (pop (pop stack)) (conj (peek (pop stack)) (peek stack)))))
+
+(defn- parse-xml [stack tok]
+ (cond
+ (.startsWith tok "</") (fold stack)
+ (.startsWith tok "<") (conj stack (parse-xml-open-token tok))
+ (= 0 (count (.trim tok))) stack
+ :otherwise (conj (pop stack) (conj (peek stack) tok))))
+
+(defn- tokenize-by-chevron [s]
+ (re-seq #"(?:[<>][^<>]+[<>])|(?:[^<>]+)" s))
+
+(defn replace-patterns-with-markup [s pattern tag]
+ (letfn [(wrap [tag s] (format "<%s>%s</%s>" tag s tag))
+ (rep [{[[replace with] & remaining] :groups
+ result :result}]
+ {:groups remaining :result (.replace result replace (wrap tag with))})]
+ (:result (first (drop-while #(not (nil? (:groups %))) (iterate rep {:groups (re-seq pattern s) :result s}))))))
+
+(defn substitute-emphasis
+ "Apply markdown standard emphasis"
+ [s]
+ (-> s
+ (replace-patterns-with-markup #"\*\*([\S]+)\*\*" "strong$")
+ (replace-patterns-with-markup #"__([\S]+)__" "strong$")
+ (replace-patterns-with-markup #"\*([\S]+)\*" "emphasis$")
+ (replace-patterns-with-markup #"_([\S]+)_" "emphasis$")))
+
+(defn markup-text [k s]
+ (->
+ (->> s
+ (substitute-emphasis)
+ (format "<wrapper>%s</wrapper>")
+ tokenize-by-chevron
+ (reduce parse-xml '())
+ zip/vector-zip
+ zip/next
+ ((fn [loc] (zip/replace loc k))) ; replace keyword
+ (iterate (fn [loc]
+ (cond
+ (zip/end? loc) nil
+ (= (zip/node loc) :strong$) (zip/replace loc ::strong)
+ (= (zip/node loc) :emphasis$) (zip/replace loc ::emphasis)
+ :otherwise (zip/next loc))))
+ (take-while #(not (nil? %)))
+ last
+ zip/root)))
+
(defn process-markdown-lines [input]
- (letfn [(fold [stack]
- (cond (empty? stack) nil
- (= (count stack) 1) (first stack)
- :otherwise (conj (pop (pop stack)) (conj (peek (pop stack)) (peek stack)))))
- (fold-list-temp [temp]
+ (letfn [(fold-list-temp [temp]
(conj (pop (pop temp))
(assoc (peek (pop temp)) :content (conj (:content (peek (pop temp)))
(:content (peek temp))))))
@@ -87,12 +136,7 @@ cause the process to run infinitely."
(if (<= left-trim to)
(.substring (:value line) left-trim to)
"")))
- (parse-xml [stack tok]
- (cond
- (.startsWith tok "</") (fold stack)
- (.startsWith tok "<") (conj stack (parse-xml-open-token tok))
- (= 0 (count (.trim tok))) stack
- :otherwise (conj (pop stack) (conj (peek stack) tok))))
+
(pushback [state] (assoc state :remaining (cons (:line state) (:remaining state))))
(wrap-xml [v] [::xml v])
(wrap-ulist [v] [::ulist v])
@@ -199,14 +243,14 @@ cause the process to run infinitely."
(re-matches #"[\=]+" (:value line)))
(assoc state
:case ::finish-heading1
- :yield [::heading1 (:value (first temp))] :temp [])
+ :yield (markup-text ::heading1 (:value (first temp))) :temp [])
;; Heading2
(and (= (count temp) 1)
(re-matches #"[-]+" (:value line)))
(assoc state
:case ::finish-heading2
- :yield [::heading2 (:value (first temp))]
+ :yield (markup-text ::heading2 (:value (first temp)))
:temp [])
;; Block quotes
@@ -281,10 +325,10 @@ cause the process to run infinitely."
(if (empty? temp)
(assoc state :case ::line-empty)
(assoc state :case ::para
- :yield [::para (map #(if (line? %) (:value %) %) temp)]
+ :yield (markup-text ::para (reduce str (interpose " " (map #(if (line? %) (:value %) %) temp))))
:temp []))
- ;; Default paragraph
+ ;; Otherwise
:otherwise
(assoc state :case ::default :temp (conj temp line)))))
input)))
14 src/test/clojure/clj_markdown/test/core.clj
View
@@ -44,3 +44,17 @@
(pprint
(markdown
(.getResourceAsStream (class System) "/markdown-tests/Blockquotes with code blocks.text")))
+
+(pprint
+ (markdown
+ (io/file "/home/malcolm/src/clojure-contrib/README.md")))
+
+(markdown (java.io.StringReader. "this is some <tt>hello</tt> span
+with a new-line with some <emphasis><tt>other span</tt></emphasis>"))
+
+
+;; TODO: Download Markdown.pl
+;; TODO: Better list handling
+;; TODO: Nested (stack-based) support for block-quotes
+;; TODO: Paragraph mutations
+;; TODO: Download github hints
51 src/test/clojure/clj_markdown/test/inline.clj
View
@@ -0,0 +1,51 @@
+;; Copyright 2011 Malcolm Sparks.
+;;
+;; This file is part of clj-markdown.
+;;
+;; clj-markdown is free software: you can redistribute it and/or modify it under the
+;; terms of the GNU Affero General Public License as published by the Free
+;; Software Foundation, either version 3 of the License, or (at your option) any
+;; later version.
+;;
+;; clj-markdown is distributed in the hope that it will be useful but WITHOUT ANY
+;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+;; A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+;; details.
+;;
+;; Please see the LICENSE file for a copy of the GNU Affero General Public License.
+
+(ns clj-markdown.test.inline
+ (:use
+ clojure.contrib.pprint
+ clojure.test
+ clj-markdown.core)
+ (:require
+ [clojure.java.io :as io]))
+
+
+(defn process [s]
+ (markdown (java.io.StringReader. s)))
+
+
+
+(deftest inline
+ (is (= [[:clj-markdown.core/para "Hello World!"]]
+ (process "Hello World!")))
+
+ (testing "inline-html"
+ (is (= [[:clj-markdown.core/para "Hello " [:span [:em "World!"]]]]
+ (process "Hello <span><em>World!</em></span>"))))
+
+ (testing "emphasis-light"
+ (let [expected [[:clj-markdown.core/para "Hello " [:clj-markdown.core/emphasis "World!"]]]]
+ (is (= expected (process "Hello *World!*")))
+ (is (= expected (process "Hello _World!_")))))
+
+ (testing "emphasis-strong"
+ (let [expected [[:clj-markdown.core/para "Hello " [:clj-markdown.core/strong "World!"]]]]
+ (is (= expected (process "Hello **World!**")))
+ (is (= expected (process "Hello __World!__"))))))
+
+
+
+
34 src/test/clojure/clj_markdown/test/perl_test.clj
View
@@ -0,0 +1,34 @@
+(ns clj-markdown.test.perl-test
+ (:use clojure.contrib.pprint)
+ (:require [clojure.java.shell :as sh]
+ [clojure.java.io :as io]
+ [clojure.xml :as xml]))
+
+(defn wrap [coll head tail]
+ (concat (list head) coll (list tail)))
+
+
+(def markdown-home (io/file (System/getProperty "user.home") "src/markdown/MarkdownTest_1.0"))
+
+(defn get-official-result [test]
+ (letfn [(convert-map [{tag :tag attrs :attrs content :content}] (vec (concat (if (nil? attrs) [tag] [tag attrs]) (map s content))))
+ (s [x] (cond (map? x) (convert-map x) :otherwise x))]
+ (s (xml/parse (io/input-stream (.getBytes (reduce str (-> (sh/sh
+ (.getAbsolutePath (io/file markdown-home "Markdown.pl"))
+ (.getAbsolutePath (io/file markdown-home (str "Tests/" test))))
+ :out .toCharArray io/reader line-seq (wrap "<div>" "</div>")))))))))
+
+
+
+(get-official-result "Auto links.text")
+
+
+(comment
+ (io/input-stream (.getBytes (str "<div>" (slurp htmlfile) "</div>")))
+
+ (xml/parse (io/file markdown-home "Tests/Auto links.html"))
+
+ (-> "ls" sh/sh :out .toCharArray io/reader line-seq))
+
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.