krz/org-swift

A dependency-free Swift library that renders org-mode to sanitized HTML. html library org-mode swift

Commit 77bb69d3d0

77bb69d3d00d14411ae7d10a935711590a52114f

parent: ca2a0de207

Unsigned

cmc <hello@cleberg.net> · 2026-08-31 18:30 UTC

Apply org's border rule to emphasis

An emphasis body may not begin or end with whitespace. Only the post-closer
character was checked, so `=ssh.mode = "system"=` closed at the middle `=`
and rendered as two verbatim spans with the quoted value loose between
them, in both renderers. Corpus conformance is unchanged at 11/12.

Layout: unified · split

Sources/OrgSwift/AST/OrgInlineParser.swift +8 −2
@@ -142,13 +142,19 @@ extension OrgParser {
142142
143143 private static func scanEmphasis(_ chars: [Character], from start: Int, marker: Character)
144144 -> (body: String, next: Int)? {
145 // org's border rule: the body may not begin with whitespace, so an opener followed by
146 // a space is not an opener at all.
147 guard start + 1 < chars.count, !chars[start + 1].isWhitespace else { return nil }
148
145149 var j = start + 1
146150 var body = ""
147151 while j < chars.count {
148 if chars[j] == marker {
152 // The body may not end with whitespace either — which is what makes the closer of
153 // `=ssh.mode = "system"=` the last `=` and not the middle one.
154 if chars[j] == marker, let last = body.last, !last.isWhitespace {
149155 // The closer must end the run or be followed by space/punctuation.
150156 let after = j + 1 < chars.count ? chars[j + 1] : " "
151 if !body.isEmpty, after.isWhitespace || ".,;:!?)]}'\"".contains(after) || j + 1 == chars.count {
157 if after.isWhitespace || ".,;:!?)]}'\"".contains(after) || j + 1 == chars.count {
152158 return (body, j + 1)
153159 }
154160 }
Tests/OrgSwiftTests/OrgTreeTests.swift +21
@@ -323,6 +323,27 @@ struct OrgRenderOptionsTests {
323323 #expect(without.contains("<p>Body.</p>"))
324324 }
325325
326 /// org's border rule: an emphasis body may not begin or end with whitespace. Without the
327 /// trailing half, `=ssh.mode = "system"=` closed at the middle `=` and rendered as two
328 /// verbatim spans with the quoted value loose between them.
329 @Test
330 func emphasisBodyCannotBeginOrEndWithWhitespace() {
331 let objects = OrgParser.parseInline(#"=ssh.mode = "system"= to run"#)
332 let verbatims = objects.compactMap { object -> String? in
333 if case .verbatim(let text) = object { return text } else { return nil }
334 }
335 #expect(verbatims == [#"ssh.mode = "system""#])
336
337 // A leading space means the marker never opens.
338 #expect(OrgParser.parseInline("= not verbatim=") == [.text("= not verbatim=")])
339
340 // The ordinary cases still work.
341 #expect(OrgParser.parseInline("=plain=") == [.verbatim("plain")])
342 let bold = OrgParser.parseInline("*bold* and *two words*")
343 #expect(bold.contains(.bold([.text("bold")])))
344 #expect(bold.contains(.bold([.text("two words")])))
345 }
346
326347 @Test
327348 func rejectsUnsafeSchemes() {
328349 let html = OrgRenderer.renderToHTML("[[javascript:alert(1)][click]]")