Skip to content

Commit 831d109

Browse files
committed
Support definition lists. Make parser more extensible.
1 parent ec84795 commit 831d109

23 files changed

+802
-132
lines changed

MarkdownKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
CC5E530C2359FF3A00C72CE2 /* EscapeTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC5E530B2359FF3A00C72CE2 /* EscapeTransformer.swift */; };
2121
CC6988C4227CDF810021C7E1 /* BlockquoteParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6988C3227CDF810021C7E1 /* BlockquoteParser.swift */; };
2222
CC6D239822C03CBA00BB1302 /* LinkTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6D239722C03CBA00BB1302 /* LinkTransformer.swift */; };
23+
CC6DE38C24C48483004F8D44 /* ExtendedListItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6DE38B24C48483004F8D44 /* ExtendedListItemParser.swift */; };
24+
CC6DE38E24C4865E004F8D44 /* ExtendedDocumentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6DE38D24C4865E004F8D44 /* ExtendedDocumentParser.swift */; };
2325
CC72627122F318C0001AB6D9 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC72627022F318C0001AB6D9 /* main.swift */; };
2426
CC72627622F318F3001AB6D9 /* AttributedStringGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC72627522F318F3001AB6D9 /* AttributedStringGenerator.swift */; };
2527
CC72627822F31C98001AB6D9 /* MarkdownKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC2AF6E8227CD98100BEA420 /* MarkdownKit.framework */; };
@@ -92,6 +94,8 @@
9294
CC6988C5227CDFB10021C7E1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
9395
CC6988C6227CDFB10021C7E1 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
9496
CC6D239722C03CBA00BB1302 /* LinkTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTransformer.swift; sourceTree = "<group>"; };
97+
CC6DE38B24C48483004F8D44 /* ExtendedListItemParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedListItemParser.swift; sourceTree = "<group>"; };
98+
CC6DE38D24C4865E004F8D44 /* ExtendedDocumentParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtendedDocumentParser.swift; sourceTree = "<group>"; };
9599
CC72626E22F318C0001AB6D9 /* MarkdownKitProcess */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MarkdownKitProcess; sourceTree = BUILT_PRODUCTS_DIR; };
96100
CC72627022F318C0001AB6D9 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
97101
CC72627522F318F3001AB6D9 /* AttributedStringGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringGenerator.swift; sourceTree = "<group>"; };
@@ -246,6 +250,7 @@
246250
CCB033C7227CDB2F00E1C4DC /* MarkdownParser.swift */,
247251
CC24757624C1ED7E00678C59 /* ExtendedMarkdownParser.swift */,
248252
CCB033CE227CDB2F00E1C4DC /* DocumentParser.swift */,
253+
CC6DE38D24C4865E004F8D44 /* ExtendedDocumentParser.swift */,
249254
CCB033CA227CDB2F00E1C4DC /* BlockParser.swift */,
250255
CCB033CD227CDB2F00E1C4DC /* Container.swift */,
251256
CCB033C8227CDB2F00E1C4DC /* AtxHeadingParser.swift */,
@@ -257,6 +262,7 @@
257262
CC8CBC20228730800084683B /* LinkRefDefinitionParser.swift */,
258263
CC6988C3227CDF810021C7E1 /* BlockquoteParser.swift */,
259264
CC8CBC16227F39C00084683B /* ListItemParser.swift */,
265+
CC6DE38B24C48483004F8D44 /* ExtendedListItemParser.swift */,
260266
CCD07465229FDFB90053B73C /* InlineParser.swift */,
261267
CCD0746922A324400053B73C /* InlineTransformer.swift */,
262268
CC7B603A22AC6AB40092188C /* DelimiterTransformer.swift */,
@@ -415,6 +421,7 @@
415421
CC8CBC25228898B00084683B /* HtmlBlockParser.swift in Sources */,
416422
CC088C0F22DD2BCD0059460E /* HtmlGenerator.swift in Sources */,
417423
CCB033D5227CDB2F00E1C4DC /* Container.swift in Sources */,
424+
CC6DE38E24C4865E004F8D44 /* ExtendedDocumentParser.swift in Sources */,
418425
CCD0746A22A324400053B73C /* InlineTransformer.swift in Sources */,
419426
CC7B603B22AC6AB40092188C /* DelimiterTransformer.swift in Sources */,
420427
CCD0746822A060CF0053B73C /* Text.swift in Sources */,
@@ -428,6 +435,7 @@
428435
CC8CBC1F2286CED60084683B /* Blocks.swift in Sources */,
429436
CC7B603F22AEFDFF0092188C /* ParserUtil.swift in Sources */,
430437
CCB033D0227CDB2F00E1C4DC /* AtxHeadingParser.swift in Sources */,
438+
CC6DE38C24C48483004F8D44 /* ExtendedListItemParser.swift in Sources */,
431439
CC7B604122B67F0C0092188C /* EmphasisTransformer.swift in Sources */,
432440
);
433441
runOnlyForDeploymentPostprocessing = 0;

MarkdownKitPlayground.playground/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
// limitations under the License.
1919
//
2020

21-
markdownView(file: "Demo", width: 500, height: 640)
21+
markdownView(file: "Demo", width: 500, height: 810)

MarkdownKitPlayground.playground/Resources/Demo.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,18 @@ The `enum-set-indexer` procedure could be defined as follows using the new `memq
3939
>
4040
> This is still in the blockquote.
4141
42-
There is more text coming after the blockquote.
42+
There is more text coming after the blockquote, including a table:
43+
44+
| Country | Country code | Dialing code |
45+
| :----------- | :--------------: | :-------------: |
46+
| Albania | AL | +355 |
47+
| Argentina | AR | +54 |
48+
| Austria | AT | +43 |
49+
50+
Description lists also need special treatment when converted to `NSAttributedString`:
51+
52+
One
53+
: This is the description for _One_
54+
55+
Two
56+
: This is the description for _Two_

MarkdownKitPlayground.playground/Sources/MarkdownPlayground.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public func loadText(file: String) -> String? {
5050
}
5151

5252
public func markdownView(text: String, width: Double, height: Double) -> NSView {
53-
let markdown = MarkdownParser.standard.parse(text)
53+
let markdown = ExtendedMarkdownParser.standard.parse(text)
5454
return MarkdownView(str: AttributedStringGenerator.standard.generate(doc: markdown),
5555
width: width,
5656
height: height)

Sources/MarkdownKit/AttributedString/AttributedStringGenerator.swift

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ open class AttributedStringGenerator {
3131
/// Customized html generator to work around limitations of the current HTML to
3232
/// `NSAttributedString` conversion logic provided by the operating system.
3333
open class InternalHtmlGenerator: HtmlGenerator {
34+
weak var outer: AttributedStringGenerator?
35+
36+
init(outer: AttributedStringGenerator) {
37+
self.outer = outer
38+
}
39+
3440
open override func generate(block: Block, tight: Bool = false) -> String {
3541
switch block {
3642
case .list(_, _, _):
@@ -49,6 +55,51 @@ open class AttributedStringGenerator {
4955
case .thematicBreak:
5056
return "<p><table style=\"width: 100%; margin-bottom: 3px;\"><tbody>" +
5157
"<tr><td class=\"thematic\"></td></tr></tbody></table></p>\n"
58+
case .table(let header, let align, let rows):
59+
var tagsuffix: [String] = []
60+
for a in align {
61+
switch a {
62+
case .undefined:
63+
tagsuffix.append(">")
64+
case .left:
65+
tagsuffix.append(" align=\"left\">")
66+
case .right:
67+
tagsuffix.append(" align=\"right\">")
68+
case .center:
69+
tagsuffix.append(" align=\"center\">")
70+
}
71+
}
72+
var html = "<table class=\"mtable\" " +
73+
"cellpadding=\"\(self.outer?.tableCellPadding ?? 2)\"><thead><tr>\n"
74+
var i = 0
75+
for head in header {
76+
html += "<th\(tagsuffix[i])\(self.generate(text: head))&nbsp;</th>"
77+
i += 1
78+
}
79+
html += "\n</tr></thead><tbody>\n"
80+
for row in rows {
81+
html += "<tr>"
82+
i = 0
83+
for cell in row {
84+
html += "<td\(tagsuffix[i])\(self.generate(text: cell))&nbsp;</td>"
85+
i += 1
86+
}
87+
html += "</tr>\n"
88+
}
89+
html += "</tbody></table><p style=\"margin: 0;\" />\n"
90+
return html
91+
case .definitionList(let defs):
92+
var html = "<dl>\n"
93+
for def in defs {
94+
html += "<dt>" + self.generate(text: def.item) + "</dt>\n"
95+
for descr in def.descriptions {
96+
if case .listItem(_, _, let blocks) = descr {
97+
html += "<dd>" + self.generate(blocks: blocks) + "</dd>\n"
98+
}
99+
}
100+
}
101+
html += "</dl>\n"
102+
return html
52103
default:
53104
return super.generate(block: block, tight: tight)
54105
}
@@ -57,16 +108,7 @@ open class AttributedStringGenerator {
57108

58109
/// Default `AttributedStringGenerator` implementation.
59110
public static let standard: AttributedStringGenerator = AttributedStringGenerator()
60-
61-
/// Internal HTML generator.
62-
private static let internalHtmlGenerator: InternalHtmlGenerator = InternalHtmlGenerator()
63-
64-
/// Override this class property if `InternalHtmlGenerator` gets extended in a subclass of
65-
/// `AttributedStringGenerator`.
66-
open class var htmlGenerator: HtmlGenerator {
67-
return AttributedStringGenerator.internalHtmlGenerator
68-
}
69-
111+
70112
/// The base font size.
71113
public let fontSize: Float
72114

@@ -171,7 +213,7 @@ open class AttributedStringGenerator {
171213
}
172214

173215
public var htmlGenerator: HtmlGenerator {
174-
return type(of: self).htmlGenerator
216+
return InternalHtmlGenerator(outer: self)
175217
}
176218

177219
open func generateHtml(_ htmlBody: String) -> String {
@@ -199,12 +241,22 @@ open class AttributedStringGenerator {
199241
"ol { \(self.olStyle) }\n" +
200242
"li { \(self.liStyle) }\n" +
201243
"table.blockquote { \(self.blockquoteStyle) }\n" +
244+
"table.mtable { \(self.tableStyle) }\n" +
245+
"table.mtable thead th { \(self.tableHeaderStyle) }\n" +
202246
"pre { \(self.preStyle) }\n" +
203247
"code { \(self.codeStyle) }\n" +
204248
"pre code { \(self.preCodeStyle) }\n" +
205249
"td.codebox { \(self.codeBoxStyle) }\n" +
206250
"td.thematic { \(self.thematicBreakStyle) }\n" +
207-
"td.quote { \(self.quoteStyle) }\n"
251+
"td.quote { \(self.quoteStyle) }\n" +
252+
"dt {\n" +
253+
" font-weight: bold;\n" +
254+
" margin: 0.6em 0 0.4em 0;\n" +
255+
"}\n" +
256+
"dd {\n" +
257+
" margin: 0.5em 0 1em 2em;\n" +
258+
" padding: 0.5em 0 1em 2em;\n" +
259+
"}\n"
208260
}
209261

210262
open var bodyStyle: String {
@@ -222,19 +274,19 @@ open class AttributedStringGenerator {
222274
open var h2Style: String {
223275
return "font-size: \(self.fontSize + 4)px;" +
224276
"color: \(self.h2Color);" +
225-
"margin: 0.6em 0 0.6em 0;"
277+
"margin: 0.6em 0 0.4em 0;"
226278
}
227279

228280
open var h3Style: String {
229281
return "font-size: \(self.fontSize + 2)px;" +
230282
"color: \(self.h3Color);" +
231-
"margin: 0.6em 0 0.6em 0;"
283+
"margin: 0.5em 0 0.3em 0;"
232284
}
233285

234286
open var h4Style: String {
235287
return "font-size: \(self.fontSize + 1)px;" +
236288
"color: \(self.h4Color);" +
237-
"margin: 0.7em 0 0.6em 0;"
289+
"margin: 0.5em 0 0.3em 0;"
238290
}
239291

240292
open var pStyle: String {
@@ -286,9 +338,24 @@ open class AttributedStringGenerator {
286338
"margin: 0.3em 0;" +
287339
"font-size: \(self.fontSize)px;"
288340
}
289-
341+
290342
open var quoteStyle: String {
291343
return "background: \(self.blockquoteColor);" +
292344
"width: 0.4em;"
293345
}
346+
347+
open var tableStyle: String {
348+
return "border-collapse: collapse;" +
349+
"margin: 0.3em 0;" +
350+
"padding: 3px;" +
351+
"font-size: \(self.fontSize)px;"
352+
}
353+
354+
open var tableHeaderStyle: String {
355+
return "border-top: 1px solid #888;"
356+
}
357+
358+
open var tableCellPadding: Int {
359+
return 2
360+
}
294361
}

Sources/MarkdownKit/Block.swift

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,23 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
3838
case referenceDef(String, Substring, Lines)
3939
case thematicBreak
4040
case table(Row, Alignments, Rows)
41+
case definitionList(Definitions)
4142

4243
/// Returns a description of the block as a string.
4344
public var description: String {
4445
switch self {
4546
case .document(let blocks):
46-
return "document(\(self.string(from: blocks))))"
47+
return "document(\(Block.string(from: blocks))))"
4748
case .blockquote(let blocks):
48-
return "blockquote(\(self.string(from: blocks))))"
49+
return "blockquote(\(Block.string(from: blocks))))"
4950
case .list(let start, let tight, let blocks):
5051
if let start = start {
51-
return "list(\(start), \(tight ? "tight" : "loose"), \(self.string(from: blocks)))"
52+
return "list(\(start), \(tight ? "tight" : "loose"), \(Block.string(from: blocks)))"
5253
} else {
53-
return "list(\(tight ? "tight" : "loose"), \(self.string(from: blocks)))"
54+
return "list(\(tight ? "tight" : "loose"), \(Block.string(from: blocks)))"
5455
}
5556
case .listItem(let type, let tight, let blocks):
56-
return "listItem(\(type), \(tight ? "tight" : "loose"), \(self.string(from: blocks)))"
57+
return "listItem(\(type), \(tight ? "tight" : "loose"), \(Block.string(from: blocks)))"
5758
case .paragraph(let text):
5859
return "paragraph(\(text.debugDescription))"
5960
case .heading(let level, let text):
@@ -109,14 +110,22 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
109110
case .thematicBreak:
110111
return "thematicBreak"
111112
case .table(let header, let align, let rows):
112-
var res = self.string(from: header) + ", "
113+
var res = Block.string(from: header) + ", "
113114
for a in align {
114115
res += a.description
115116
}
116117
for row in rows {
117-
res += ", " + self.string(from: row)
118+
res += ", " + Block.string(from: row)
118119
}
119120
return "table(\(res))"
121+
case .definitionList(let defs):
122+
var res = "definitionList"
123+
var sep = "("
124+
for def in defs {
125+
res += sep + def.description
126+
sep = "; "
127+
}
128+
return res + ")"
120129
}
121130
}
122131

@@ -125,7 +134,7 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
125134
return self.description
126135
}
127136

128-
private func string(from blocks: Blocks) -> String {
137+
fileprivate static func string(from blocks: Blocks) -> String {
129138
var res = ""
130139
for block in blocks {
131140
if res.isEmpty {
@@ -137,7 +146,7 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
137146
return res
138147
}
139148

140-
private func string(from row: Row) -> String {
149+
fileprivate static func string(from row: Row) -> String {
141150
var res = "row("
142151
for cell in row {
143152
if res.isEmpty {
@@ -176,6 +185,8 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
176185
return true
177186
case (.table(let lheader, let lalign, let lrows), .table(let rheader, let ralign, let rrows)):
178187
return lheader == rheader && lalign == ralign && lrows == rrows
188+
case (.definitionList(let ldefs), .definitionList(let rdefs)):
189+
return ldefs == rdefs
179190
default:
180191
return false
181192
}
@@ -257,3 +268,18 @@ public enum Alignment: UInt, CustomStringConvertible, CustomDebugStringConvertib
257268
}
258269

259270
public typealias Alignments = ContiguousArray<Alignment>
271+
272+
public struct Definition: Equatable, CustomStringConvertible, CustomDebugStringConvertible {
273+
let item: Text
274+
let descriptions: Blocks
275+
276+
public var description: String {
277+
return "\(self.item.debugDescription) : \(Block.string(from: self.descriptions))"
278+
}
279+
280+
public var debugDescription: String {
281+
return self.description
282+
}
283+
}
284+
285+
public typealias Definitions = ContiguousArray<Definition>

0 commit comments

Comments
 (0)