607 lines
18 KiB
Python
607 lines
18 KiB
Python
"""
|
||
novelWriter – Patterns Module Tester
|
||
====================================
|
||
|
||
This file is a part of novelWriter
|
||
Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful, but
|
||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
""" # noqa
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
import pytest
|
||
|
||
from novelwriter import CONFIG
|
||
from novelwriter.constants import nwUnicode
|
||
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
|
||
|
||
|
||
def allMatches(regEx: re.Pattern, text: str) -> list[list[tuple[str, int, int]]]:
|
||
"""Get all matches for a regex."""
|
||
return [
|
||
[(res.group(n), res.start(n), res.end(n)) for n in range((res.lastindex or 0) + 1)]
|
||
for res in regEx.finditer(text)
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_Urls():
|
||
"""Test the URL regex."""
|
||
regEx = REGEX_PATTERNS.url
|
||
|
||
valid = [
|
||
"http://example.com",
|
||
"http://example.com/",
|
||
"http://example.com/path+to+page",
|
||
"http://example.com/path-to-page",
|
||
"http://example.com/path_to_page",
|
||
"http://example.com/path~to~page",
|
||
"http://example.com/path/to/page",
|
||
"http://example.com/path/to/page.html",
|
||
"http://example.com/path/to/page.html#title",
|
||
"http://example.com/path/to/page.html#title%20here",
|
||
"http://example.com/path/to/page.html#title%20here",
|
||
"http://example.com/path/to/page?foo=bar&bar=baz",
|
||
"http://example.com/path/to/page.html?foo=bar&bar=baz",
|
||
"http://example.com/path/to/page.html#title?foo=bar&bar=baz",
|
||
"http://user:password@example.com/",
|
||
"http://www.example.com/",
|
||
"http://www.www.example.com/",
|
||
"http://www.www.www.example.com/",
|
||
"https://example.com",
|
||
"https://www.example.com/",
|
||
]
|
||
invalid = [
|
||
"hppt://example.com/",
|
||
"sftp://example.com/",
|
||
"http:/example.com/",
|
||
"http://www example com/",
|
||
"http://www\texample\tcom/",
|
||
]
|
||
|
||
for test in valid:
|
||
assert allMatches(regEx, f"Text {test} more text") == [[(test, 5, 5 + len(test))]]
|
||
|
||
for test in invalid:
|
||
assert allMatches(regEx, f"Text {test} more text") == []
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_Words():
|
||
"""Test the word split regex."""
|
||
regEx = REGEX_PATTERNS.wordSplit
|
||
|
||
# Spaces
|
||
assert allMatches(regEx, "one two three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Hyphens
|
||
assert allMatches(regEx, "one-two-three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Em Dashes
|
||
assert allMatches(regEx, "one\u2014two\u2014three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Em Dashes
|
||
assert allMatches(regEx, "one\u2014two\u2014three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Plus
|
||
assert allMatches(regEx, "one+two+three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Slash
|
||
assert allMatches(regEx, "one/two/three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Brackets
|
||
assert allMatches(regEx, "one[two]three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
# Colon
|
||
assert allMatches(regEx, "one:two:three") == [
|
||
[("one", 0, 3)], [("two", 4, 7)], [("three", 8, 13)]
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_Markdown():
|
||
"""Test the markdown pattern regexes."""
|
||
# Bold
|
||
regEx = REGEX_PATTERNS.markdownBold
|
||
assert allMatches(regEx, "one **two** three") == [
|
||
[("**two**", 4, 11), ("**", 4, 6), ("two", 6, 9), ("**", 9, 11)]
|
||
]
|
||
assert allMatches(regEx, "one **two* three") == []
|
||
assert allMatches(regEx, "one *two** three") == []
|
||
assert allMatches(regEx, "one**two**three") == []
|
||
|
||
# Italic
|
||
regEx = REGEX_PATTERNS.markdownItalic
|
||
assert allMatches(regEx, "one _two_ three") == [
|
||
[("_two_", 4, 9), ("_", 4, 5), ("two", 5, 8), ("_", 8, 9)]
|
||
]
|
||
assert allMatches(regEx, "one __two_ three") == []
|
||
assert allMatches(regEx, "one _two__ three") == [
|
||
[("_two__", 4, 10), ("_", 4, 5), ("two_", 5, 9), ("_", 9, 10)]
|
||
]
|
||
assert allMatches(regEx, "one_two_three") == []
|
||
|
||
# Strike
|
||
regEx = REGEX_PATTERNS.markdownStrike
|
||
assert allMatches(regEx, "one ~~two~~ three") == [
|
||
[("~~two~~", 4, 11), ("~~", 4, 6), ("two", 6, 9), ("~~", 9, 11)]
|
||
]
|
||
assert allMatches(regEx, "one ~~two~ three") == []
|
||
assert allMatches(regEx, "one ~two~~ three") == []
|
||
assert allMatches(regEx, "one~~two~~three") == []
|
||
|
||
# Mark
|
||
regEx = REGEX_PATTERNS.markdownMark
|
||
assert allMatches(regEx, "one ==two== three") == [
|
||
[("==two==", 4, 11), ("==", 4, 6), ("two", 6, 9), ("==", 9, 11)]
|
||
]
|
||
assert allMatches(regEx, "one ==two= three") == []
|
||
assert allMatches(regEx, "one =two== three") == []
|
||
assert allMatches(regEx, "one==two==three") == []
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_ShortcodesPlain():
|
||
"""Test the shortcode pattern regexes."""
|
||
regEx = REGEX_PATTERNS.shortcodePlain
|
||
|
||
# Test Usage
|
||
# ==========
|
||
|
||
# General, normal usage
|
||
assert allMatches(regEx, "one [b]two[/b] three") == [
|
||
[("[b]", 4, 7), ("[b]", 4, 7)],
|
||
[("[/b]", 10, 14), ("[/b]", 10, 14)],
|
||
]
|
||
|
||
# General, no spaces
|
||
assert allMatches(regEx, "one[b]two[/b]three") == [
|
||
[("[b]", 3, 6), ("[b]", 3, 6)],
|
||
[("[/b]", 9, 13), ("[/b]", 9, 13)],
|
||
]
|
||
|
||
# General, with padding
|
||
assert allMatches(regEx, "one [b] two [/b] three") == [
|
||
[("[b]", 4, 7), ("[b]", 4, 7)],
|
||
[("[/b]", 12, 16), ("[/b]", 12, 16)],
|
||
]
|
||
|
||
# General, with escapes
|
||
assert allMatches(regEx, "one \\[b]two[/b\\] three") == []
|
||
|
||
# Test Formats
|
||
# ============
|
||
|
||
# Bold
|
||
assert allMatches(regEx, "one [b]two[/b] three") == [
|
||
[("[b]", 4, 7), ("[b]", 4, 7)],
|
||
[("[/b]", 10, 14), ("[/b]", 10, 14)],
|
||
]
|
||
|
||
# Italic
|
||
assert allMatches(regEx, "one [i]two[/i] three") == [
|
||
[("[i]", 4, 7), ("[i]", 4, 7)],
|
||
[("[/i]", 10, 14), ("[/i]", 10, 14)],
|
||
]
|
||
|
||
# Strike
|
||
assert allMatches(regEx, "one [s]two[/s] three") == [
|
||
[("[s]", 4, 7), ("[s]", 4, 7)],
|
||
[("[/s]", 10, 14), ("[/s]", 10, 14)],
|
||
]
|
||
|
||
# Underline
|
||
assert allMatches(regEx, "one [u]two[/u] three") == [
|
||
[("[u]", 4, 7), ("[u]", 4, 7)],
|
||
[("[/u]", 10, 14), ("[/u]", 10, 14)],
|
||
]
|
||
|
||
# Mark
|
||
assert allMatches(regEx, "one [m]two[/m] three") == [
|
||
[("[m]", 4, 7), ("[m]", 4, 7)],
|
||
[("[/m]", 10, 14), ("[/m]", 10, 14)],
|
||
]
|
||
|
||
# Superscript
|
||
assert allMatches(regEx, "one [sup]two[/sup] three") == [
|
||
[("[sup]", 4, 9), ("[sup]", 4, 9)],
|
||
[("[/sup]", 12, 18), ("[/sup]", 12, 18)],
|
||
]
|
||
|
||
# Subscript
|
||
assert allMatches(regEx, "one [sub]two[/sub] three") == [
|
||
[("[sub]", 4, 9), ("[sub]", 4, 9)],
|
||
[("[/sub]", 12, 18), ("[/sub]", 12, 18)],
|
||
]
|
||
|
||
# Test Invalid
|
||
# ============
|
||
|
||
assert allMatches(regEx, "one [x]two[/x] three") == []
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_LineBreakReplace():
|
||
"""Test replacing forced line breaks."""
|
||
regEx = REGEX_PATTERNS.lineBreak
|
||
|
||
assert regEx.sub("\n", "one[br]two") == "one\ntwo"
|
||
assert regEx.sub("\n", "one[br]\ntwo") == "one\ntwo"
|
||
assert regEx.sub("\n", "one[br]\n\ntwo") == "one\n\ntwo"
|
||
assert regEx.sub("\n", "one[BR]two") == "one\ntwo"
|
||
assert regEx.sub("\n", "one[BR]\ntwo") == "one\ntwo"
|
||
assert regEx.sub("\n", "one[BR]\n\ntwo") == "one\n\ntwo"
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_ShortcodesValue():
|
||
"""Test the shortcode with value pattern regexes."""
|
||
regEx = REGEX_PATTERNS.shortcodeValue
|
||
|
||
assert allMatches(regEx, "one [footnote:two] three") == [
|
||
[("[footnote:two]", 4, 18), ("[footnote:", 4, 14), ("two", 14, 17), ("]", 17, 18)]
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialogueStyle():
|
||
"""Test the dialogue style pattern regexes."""
|
||
# Before set, the regex is None
|
||
CONFIG.dialogStyle = 0
|
||
assert REGEX_PATTERNS.dialogStyle is None
|
||
|
||
# Set the config
|
||
CONFIG.fmtSQuoteOpen = nwUnicode.U_LSQUO
|
||
CONFIG.fmtSQuoteClose = nwUnicode.U_RSQUO
|
||
CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO
|
||
CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO
|
||
|
||
CONFIG.dialogStyle = 3
|
||
|
||
# Closed
|
||
# ======
|
||
|
||
CONFIG.allowOpenDial = False
|
||
regEx = REGEX_PATTERNS.dialogStyle
|
||
assert regEx is not None
|
||
|
||
# Defined single quotes are recognised
|
||
assert allMatches(regEx, "one \u2018two\u2019 three") == [
|
||
[("\u2018two\u2019", 4, 9)]
|
||
]
|
||
|
||
# Defined double quotes are recognised
|
||
assert allMatches(regEx, "one \u201ctwo\u201d three") == [
|
||
[("\u201ctwo\u201d", 4, 9)]
|
||
]
|
||
|
||
# Both single and double quotes are recognised
|
||
assert allMatches(regEx, "one \u2018two\u2019 three \u201cfour\u201d five") == [
|
||
[("\u2018two\u2019", 4, 9)], [("\u201cfour\u201d", 16, 22)]
|
||
]
|
||
|
||
# But not mixed
|
||
assert allMatches(regEx, "one \u2018two\u201d three \u201cfour\u2019 five") == [
|
||
[("\u2018two\u201d three \u201cfour\u2019", 4, 22)]
|
||
]
|
||
|
||
# Straight single quotes are ignored
|
||
assert allMatches(regEx, "one 'two' three") == []
|
||
|
||
# Straight double quotes are ignored
|
||
assert allMatches(regEx, 'one "two" three') == []
|
||
|
||
# Check with no whitespace, single quote
|
||
# The apostrophe ambiguity rule should kick in here
|
||
assert allMatches(regEx, "one\u2018two\u2019three") == []
|
||
assert allMatches(regEx, "one\u2018two\u2019 three") == []
|
||
assert allMatches(regEx, "one \u2018two\u02bc three\u2019 four") == [
|
||
[("\u2018two\u02bc three\u2019", 4, 16)],
|
||
]
|
||
|
||
# Check with no whitespace, double quote
|
||
assert allMatches(regEx, "one\u201ctwo\u201dthree") == [
|
||
[("\u201ctwo\u201d", 3, 8)]
|
||
]
|
||
assert allMatches(regEx, "one\u201ctwo\u201d three") == [
|
||
[("\u201ctwo\u201d", 3, 8)]
|
||
]
|
||
|
||
# Check with apostrophe
|
||
assert allMatches(regEx, "one \u2018two\u2019s three\u2019, \u2018four\u2019 five") == [
|
||
[("\u2018two\u2019s three\u2019", 4, 17)],
|
||
[("\u2018four\u2019", 19, 25)],
|
||
]
|
||
|
||
# Open
|
||
# ====
|
||
|
||
CONFIG.allowOpenDial = True
|
||
regEx = REGEX_PATTERNS.dialogStyle
|
||
assert regEx is not None
|
||
|
||
# Defined single quotes are recognised also when open
|
||
assert allMatches(regEx, "one \u2018two three") == [
|
||
[("\u2018two three", 4, 14)]
|
||
]
|
||
|
||
# Defined double quotes are recognised also when open
|
||
assert allMatches(regEx, "one \u201ctwo three") == [
|
||
[("\u201ctwo three", 4, 14)]
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialoguePlain():
|
||
"""Test the dialogue style pattern regexes for plain quotes."""
|
||
# Set the config
|
||
CONFIG.fmtSQuoteOpen = "'"
|
||
CONFIG.fmtSQuoteClose = "'"
|
||
CONFIG.fmtDQuoteOpen = '"'
|
||
CONFIG.fmtDQuoteClose = '"'
|
||
|
||
CONFIG.dialogStyle = 3
|
||
CONFIG.allowOpenDial = False
|
||
regEx = REGEX_PATTERNS.dialogStyle
|
||
assert regEx is not None
|
||
|
||
# Double
|
||
# ======
|
||
|
||
# One double quoted string
|
||
assert allMatches(regEx, 'one "two" three') == [
|
||
[('"two"', 4, 9)]
|
||
]
|
||
|
||
# Two double quoted strings
|
||
assert allMatches(regEx, 'one "two" three "four" five') == [
|
||
[('"two"', 4, 9)], [('"four"', 16, 22)],
|
||
]
|
||
|
||
# No space
|
||
assert allMatches(regEx, 'one"two" three') == []
|
||
assert allMatches(regEx, 'one "two"three') == []
|
||
assert allMatches(regEx, 'one"two"three') == []
|
||
|
||
# Single
|
||
# ======
|
||
|
||
# One single quoted string
|
||
assert allMatches(regEx, "one 'two' three") == [
|
||
[("'two'", 4, 9)]
|
||
]
|
||
|
||
# Two single quoted strings
|
||
assert allMatches(regEx, "one 'two' three 'four' five") == [
|
||
[("'two'", 4, 9)], [("'four'", 16, 22)],
|
||
]
|
||
|
||
# No space
|
||
assert allMatches(regEx, "one'two' three") == []
|
||
assert allMatches(regEx, "one 'two'three") == []
|
||
assert allMatches(regEx, "one'two'three") == []
|
||
|
||
# Check with apostrophe
|
||
assert allMatches(regEx, "one 'two's three', 'four' five") == [
|
||
[("'two's three'", 4, 17)],
|
||
[("'four'", 19, 25)],
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialogueSpecial():
|
||
"""Test the special dialogue style pattern regexes."""
|
||
# Before set, the regex is None
|
||
CONFIG.altDialogOpen = ""
|
||
CONFIG.altDialogClose = ""
|
||
assert REGEX_PATTERNS.altDialogStyle is None
|
||
|
||
# Set the config
|
||
CONFIG.altDialogOpen = "::"
|
||
CONFIG.altDialogClose = "::"
|
||
|
||
# Alternative Dialogue
|
||
# ====================
|
||
regEx = REGEX_PATTERNS.altDialogStyle
|
||
assert regEx is not None
|
||
|
||
# With no padding
|
||
assert allMatches(regEx, "one ::two:: three") == [
|
||
[("::two::", 4, 11)]
|
||
]
|
||
|
||
# With padding
|
||
assert allMatches(regEx, "one :: two :: three") == [
|
||
[(":: two ::", 4, 13)]
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialogParserEnglish():
|
||
"""Test the dialog parser with English settings."""
|
||
# Set the config
|
||
CONFIG.dialogStyle = 3
|
||
CONFIG.fmtSQuoteOpen = nwUnicode.U_LSQUO
|
||
CONFIG.fmtSQuoteClose = nwUnicode.U_RSQUO
|
||
CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO
|
||
CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO
|
||
|
||
parser = DialogParser()
|
||
parser.initParser()
|
||
assert parser.enabled is True
|
||
|
||
# Positions: 0 18
|
||
assert parser("“Simple dialogue.”") == [
|
||
(0, 18),
|
||
]
|
||
|
||
# Positions: 0 18
|
||
assert parser("“Simple dialogue,” argued John.") == [
|
||
(0, 18),
|
||
]
|
||
|
||
# Positions: 0 18 32 56
|
||
assert parser("“Simple dialogue,” argued John, “is not always so easy.”") == [
|
||
(0, 18), (32, 56),
|
||
]
|
||
|
||
# With Narrator breaks
|
||
CONFIG.dialogLine = ""
|
||
CONFIG.narratorBreak = nwUnicode.U_EMDASH
|
||
parser.initParser()
|
||
|
||
# Positions: 0 18 34 58
|
||
assert parser("“Simple dialogue, — argued John, — is not always so easy.”") == [
|
||
(0, 18), (34, 58),
|
||
]
|
||
|
||
# Positions: 0 18 32 56
|
||
assert parser("“Simple dialogue, —argued John—, is not always so easy.”") == [
|
||
(0, 18), (32, 56),
|
||
]
|
||
|
||
# Positions: 0 31
|
||
assert parser("“Simple dialogue, —argued John”") == [
|
||
(0, 31),
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialogParserSpanish():
|
||
"""Test the dialog parser with Spanish settings."""
|
||
# Set the config
|
||
CONFIG.dialogStyle = 3
|
||
CONFIG.fmtSQuoteOpen = nwUnicode.U_LSAQUO
|
||
CONFIG.fmtSQuoteClose = nwUnicode.U_RSAQUO
|
||
CONFIG.fmtDQuoteOpen = nwUnicode.U_LAQUO
|
||
CONFIG.fmtDQuoteClose = nwUnicode.U_RAQUO
|
||
CONFIG.dialogLine = nwUnicode.U_EMDASH + nwUnicode.U_RAQUO
|
||
CONFIG.narratorBreak = nwUnicode.U_EMDASH
|
||
|
||
parser = DialogParser()
|
||
parser.initParser()
|
||
|
||
# Positions: 0 18 54 70
|
||
assert parser("—No te preocupes. —Cerró la puerta y salió corriendo—. Volveré pronto.") == [
|
||
(0, 18), (54, 70),
|
||
]
|
||
|
||
# Positions: 0 14
|
||
assert parser("«Tengo hambre», pensó Pedro.") == [
|
||
(0, 14),
|
||
]
|
||
|
||
# Positions: 0 16
|
||
assert parser("—Puedes hacerlo —le dije y pensé «pero te costará mucho trabajo».") == [
|
||
(0, 16),
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialogParserPortuguese():
|
||
"""Test the dialog parser with Portuguese settings."""
|
||
# Set the config
|
||
CONFIG.dialogStyle = 0
|
||
CONFIG.fmtSQuoteOpen = nwUnicode.U_LSAQUO
|
||
CONFIG.fmtSQuoteClose = nwUnicode.U_RSAQUO
|
||
CONFIG.fmtDQuoteOpen = nwUnicode.U_LAQUO
|
||
CONFIG.fmtDQuoteClose = nwUnicode.U_RAQUO
|
||
CONFIG.dialogLine = nwUnicode.U_EMDASH
|
||
CONFIG.narratorBreak = nwUnicode.U_EMDASH
|
||
|
||
parser = DialogParser()
|
||
parser.initParser()
|
||
|
||
# Positions: 0 21
|
||
assert parser("— Está ficando tarde.") == [
|
||
(0, 21),
|
||
]
|
||
|
||
# Positions: 0 12
|
||
assert parser("— Ainda não — ela responde.") == [
|
||
(0, 12),
|
||
]
|
||
|
||
# Positions: 0 12 29 49
|
||
assert parser("— Tudo bem? — ele pergunta. — Você falou com ele?") == [
|
||
(0, 12), (29, 49),
|
||
]
|
||
|
||
# Positions: 0 12 29 49
|
||
assert parser("— Tudo bem? — ele pergunta —. Você falou com ele?") == [
|
||
(0, 12), (29, 49),
|
||
]
|
||
|
||
|
||
@pytest.mark.core
|
||
def testTextPatterns_DialogParserPolish():
|
||
"""Test the dialog parser with alternating Polish settings."""
|
||
# Set the config
|
||
CONFIG.dialogStyle = 0
|
||
CONFIG.fmtSQuoteOpen = "'"
|
||
CONFIG.fmtSQuoteClose = "'"
|
||
CONFIG.fmtDQuoteOpen = '"'
|
||
CONFIG.fmtDQuoteClose = '"'
|
||
CONFIG.dialogLine = ""
|
||
CONFIG.narratorBreak = ""
|
||
CONFIG.narratorDialog = nwUnicode.U_ENDASH
|
||
|
||
parser = DialogParser()
|
||
parser.initParser()
|
||
|
||
# This is what an example dialogue might look like using Polish punctuation rules
|
||
# See discussion #1976
|
||
|
||
assert parser(
|
||
"– Example statement – someone said. And he added: – Another example statement."
|
||
) == [
|
||
(0, 20), (50, 78),
|
||
]
|
||
|
||
assert parser(
|
||
"– Oh my! – It would be nice if only the statements were highlighted, without "
|
||
"any narration. In a paragraph where there is only a short statement and then "
|
||
"a lot happens, this would be especially justified."
|
||
) == [
|
||
(0, 9),
|
||
]
|
||
|
||
assert parser(
|
||
"There are also sometimes paragraphs that start with a narrative, and only then "
|
||
"someone shouts out the words: – Oooh! Look!"
|
||
) == [
|
||
(109, 122),
|
||
]
|
||
|
||
assert parser(
|
||
'And so on and so forth. However, "text in quotation marks" should not be '
|
||
"highlighted at all, and if so, it should be highlighted differently."
|
||
) == []
|