Commit 77bb69d3d0
Unsigned
Layout: unified · split
Sources/OrgSwift/AST/OrgInlineParser.swift +8 −2
| @@ -142,13 +142,19 @@ extension OrgParser { | ||
| 142 | 142 | |
| 143 | 143 | private static func scanEmphasis(_ chars: [Character], from start: Int, marker: Character) |
| 144 | 144 | -> (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 | ||
| 145 | 149 | var j = start + 1 |
| 146 | 150 | var body = "" |
| 147 | 151 | 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 { | |
| 149 | 155 | // The closer must end the run or be followed by space/punctuation. |
| 150 | 156 | 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 { | |
| 152 | 158 | return (body, j + 1) |
| 153 | 159 | } |
| 154 | 160 | } |
Tests/OrgSwiftTests/OrgTreeTests.swift +21
| @@ -323,6 +323,27 @@ struct OrgRenderOptionsTests { | ||
| 323 | 323 | #expect(without.contains("<p>Body.</p>")) |
| 324 | 324 | } |
| 325 | 325 | |
| 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 | ||
| 326 | 347 | @Test |
| 327 | 348 | func rejectsUnsafeSchemes() { |
| 328 | 349 | let html = OrgRenderer.renderToHTML("[[javascript:alert(1)][click]]") |