This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 758e4de954 keep document line breaks inside markdown code, alt text 
and links (#3567)
758e4de954 is described below

commit 758e4de9542ef55da29f77f6c714f621bf70a648
Author: Naveed <[email protected]>
AuthorDate: Sun Nov 8 04:07:59 2026 +0530

    keep document line breaks inside markdown code, alt text and links (#3567)
    
    ToMarkdownContentHandler collapses line breaks in prose and math but not
    in the literals commonmark writes without line handling. An LF in inline
    <code> ends the code span's line, and a next line starting with <details
    or <script opens a raw HTML block. A lone CR in a <pre> inside a list
    item or blockquote gets no container prefix, so the code block and the
    container close there and the rest of the line is raw HTML. Collapse
    line breaks in code spans and alt text, and turn CR and CRLF into LF in
    code blocks and in link and image destinations.
    
    Generated-by: Claude Code (claude-opus-5-5)
---
 .../apache/tika/sax/ToMarkdownContentHandler.java  | 23 +++++--
 .../tika/sax/ToMarkdownContentHandlerTest.java     | 71 ++++++++++++++++++++++
 2 files changed, 89 insertions(+), 5 deletions(-)

diff --git 
a/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java 
b/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java
index cd65a3e69c..00c7b95d1a 100644
--- a/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java
+++ b/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java
@@ -257,7 +257,7 @@ public class ToMarkdownContentHandler extends 
DefaultHandler {
                 break;
             case "a":
                 String href = atts.getValue("href");
-                openInline(new Link(href != null ? href : "", null));
+                openInline(new Link(href != null ? normalizeLineEndings(href) 
: "", null));
                 break;
             case "code":
                 if (codeBlockText == null) {
@@ -292,10 +292,10 @@ public class ToMarkdownContentHandler extends 
DefaultHandler {
                 break;
             case "img":
                 ensureInlineContainer();
-                Image img = new Image(value(atts, "src"), null);
+                Image img = new Image(normalizeLineEndings(value(atts, 
"src")), null);
                 String alt = atts.getValue("alt");
                 if (alt != null && !alt.isEmpty()) {
-                    img.appendChild(new Text(alt));
+                    img.appendChild(new Text(collapseLineBreaks(alt)));
                 }
                 stack.peek().appendChild(img);
                 break;
@@ -363,14 +363,16 @@ public class ToMarkdownContentHandler extends 
DefaultHandler {
                 break;
             case "pre":
                 if (codeBlock != null) {
-                    
codeBlock.setLiteral(withTrailingNewline(codeBlockText.toString()));
+                    codeBlock.setLiteral(withTrailingNewline(
+                            normalizeLineEndings(codeBlockText.toString())));
                     codeBlock = null;
                     codeBlockText = null;
                 }
                 break;
             case "code":
                 if (inlineCode != null) {
-                    inlineCode.setLiteral(inlineCodeText.toString());
+                    // commonmark writes the literal raw: a line break would 
end the span's line
+                    
inlineCode.setLiteral(collapseLineBreaks(inlineCodeText.toString()));
                     inlineCode = null;
                     inlineCodeText = null;
                 }
@@ -988,6 +990,17 @@ public class ToMarkdownContentHandler extends 
DefaultHandler {
         return chars == null ? s : new String(chars);
     }
 
+    /**
+     * commonmark prefixes code block lines and encodes destination line 
breaks on LF only;
+     * a lone CR still ends a line for every CommonMark parser.
+     */
+    private static String normalizeLineEndings(String s) {
+        if (s.indexOf('\r') < 0) {
+            return s;
+        }
+        return s.replace("\r\n", "\n").replace('\r', '\n');
+    }
+
     private static String withTrailingNewline(String s) {
         if (s.isEmpty() || s.charAt(s.length() - 1) == '\n') {
             return s;
diff --git 
a/tika-core/src/test/java/org/apache/tika/sax/ToMarkdownContentHandlerTest.java 
b/tika-core/src/test/java/org/apache/tika/sax/ToMarkdownContentHandlerTest.java
index 22ecf02e0c..6d43be4656 100644
--- 
a/tika-core/src/test/java/org/apache/tika/sax/ToMarkdownContentHandlerTest.java
+++ 
b/tika-core/src/test/java/org/apache/tika/sax/ToMarkdownContentHandlerTest.java
@@ -1688,6 +1688,77 @@ public class ToMarkdownContentHandlerTest {
         assertFalse(result.contains("](https://evil.example) x)"), result);
     }
 
+    @Test
+    public void testLineBreakInCodeSpanCannotOpenHtml() throws Exception {
+        for (String lineBreak : new String[]{"\n", "\r", "\r\n"}) {
+            ToMarkdownContentHandler handler = new ToMarkdownContentHandler();
+            handler.startDocument();
+            startElement(handler, "p");
+            chars(handler, "See ");
+            startElement(handler, "code");
+            chars(handler, "x" + lineBreak + "<details open 
ontoggle=alert(1)>");
+            endElement(handler, "code");
+            chars(handler, " here");
+            endElement(handler, "p");
+            handler.endDocument();
+
+            String md = handler.toString();
+            String html = renderHtml(md);
+            assertFalse(html.contains("<details"), md);
+            assertContains("&lt;details open ontoggle=alert(1)&gt;</code> 
here", html);
+        }
+    }
+
+    @Test
+    public void testCarriageReturnInCodeBlockCannotLeaveContainer() throws 
Exception {
+        for (String container : new String[]{"li", "blockquote"}) {
+            for (String lineBreak : new String[]{"\r", "\r\n"}) {
+                ToMarkdownContentHandler handler = new 
ToMarkdownContentHandler();
+                handler.startDocument();
+                if (container.equals("li")) {
+                    startElement(handler, "ul");
+                }
+                startElement(handler, container);
+                startElement(handler, "pre");
+                chars(handler, "a" + lineBreak + "<img src=x 
onerror=alert(1)>");
+                endElement(handler, "pre");
+                endElement(handler, container);
+                if (container.equals("li")) {
+                    endElement(handler, "ul");
+                }
+                handler.endDocument();
+
+                String md = handler.toString();
+                String html = renderHtml(md);
+                assertFalse(html.contains("<img"), md);
+                assertContains("<pre><code>a\n&lt;img src=x 
onerror=alert(1)&gt;\n</code></pre>", html);
+            }
+        }
+    }
+
+    @Test
+    public void testLineBreakInImageAltOrDestinationStaysInline() throws 
Exception {
+        ToMarkdownContentHandler handler = new ToMarkdownContentHandler();
+        handler.startDocument();
+        startElement(handler, "p");
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "alt", "alt", "CDATA", "a\r# alt");
+        atts.addAttribute("", "src", "src", "CDATA", "i.png\r# src");
+        startElement(handler, "img", atts);
+        endElement(handler, "img");
+        startElement(handler, "a", "href", "http://e\r#href";);
+        chars(handler, "link");
+        endElement(handler, "a");
+        endElement(handler, "p");
+        handler.endDocument();
+
+        String md = handler.toString();
+        String html = renderHtml(md);
+        assertFalse(html.contains("<h1>"), md);
+        assertContains("alt=\"a # alt\"", html);
+        assertContains(">link</a>", html);
+    }
+
     @Test
     public void testBoldInsideLinkIsNested() throws Exception {
         ToMarkdownContentHandler handler = new ToMarkdownContentHandler();

Reply via email to