From a8a70ad157d37ef4bf9bf79b16b523b6ab071b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afonso=20Janu=C3=A1rio?= Date: Sat, 26 Sep 2026 22:24:02 +0100 Subject: [PATCH] Fix CRLF handling with preserve_blank_lines When preserve_blank_lines=True, lines were split on the character class [\r\n], which treats a Windows CRLF as two separate line endings instead of one. Every \r\n in the input therefore produced an extra blank line in the output (a single line break could turn into up to four newlines). Split on the line-ending sequence \r\n|\r|\n instead, so CRLF, lone CR, and LF are all treated as one line break each. The preserve_blank_lines=False path already used [\r\n]+ and was not affected. --- titlecase/__init__.py | 2 +- titlecase/tests.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/titlecase/__init__.py b/titlecase/__init__.py index fd24431..27b25a6 100755 --- a/titlecase/__init__.py +++ b/titlecase/__init__.py @@ -98,7 +98,7 @@ def titlecase(text, callback=None, small_first_last=True, preserve_blank_lines=F """ if preserve_blank_lines: - lines = regex.split('[\r\n]', text) + lines = regex.split(r'\r\n|\r|\n', text) else: lines = regex.split('[\r\n]+', text) processed = [] diff --git a/titlecase/tests.py b/titlecase/tests.py index 9265ec0..96d2447 100644 --- a/titlecase/tests.py +++ b/titlecase/tests.py @@ -433,6 +433,17 @@ def test_complex_blanks(self): self.assertEqual(titlecase(s, preserve_blank_lines=True), '\n\nLeading Blank\n\n\nMulti-Blank\n\n\n\n\nTrailing Blank\n\n') + def test_crlf_line_endings(self): + # A CRLF is a single line ending, not two, so it shouldn't introduce + # extra blank lines when preserve_blank_lines is on. + self.assertEqual(titlecase('a\r\nb', preserve_blank_lines=True), 'A\nB') + self.assertEqual(titlecase('Line1\r\nLine2\r\nLine3', preserve_blank_lines=True), + 'Line1\nLine2\nLine3') + self.assertEqual(titlecase('Line1\r\n\r\nLine2', preserve_blank_lines=True), + 'Line1\n\nLine2') + # Lone CR line endings should behave the same way as LF ones. + self.assertEqual(titlecase('a\rb', preserve_blank_lines=True), 'A\nB') + class TestNormaliseSpaceCharacters(unittest.TestCase): def test_tabs(self): s = 'text\twith\ttabs'