fast-sentence-segment 1.2.1__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fast_sentence_segment/__init__.py +34 -1
- fast_sentence_segment/cli.py +94 -1
- fast_sentence_segment/dmo/__init__.py +5 -0
- fast_sentence_segment/dmo/abbreviations.py +55 -2
- fast_sentence_segment/dmo/dehyphenator.py +55 -0
- fast_sentence_segment/dmo/group_quoted_sentences.py +141 -0
- fast_sentence_segment/dmo/normalize_quotes.py +80 -0
- fast_sentence_segment/dmo/spacy_doc_segmenter.py +24 -11
- fast_sentence_segment/dmo/strip_trailing_period_after_quote.py +70 -0
- fast_sentence_segment/dmo/unwrap_hard_wrapped_text.py +75 -0
- fast_sentence_segment/svc/perform_sentence_segmentation.py +11 -0
- {fast_sentence_segment-1.2.1.dist-info → fast_sentence_segment-1.4.0.dist-info}/METADATA +29 -34
- {fast_sentence_segment-1.2.1.dist-info → fast_sentence_segment-1.4.0.dist-info}/RECORD +16 -11
- {fast_sentence_segment-1.2.1.dist-info → fast_sentence_segment-1.4.0.dist-info}/WHEEL +1 -1
- {fast_sentence_segment-1.2.1.dist-info → fast_sentence_segment-1.4.0.dist-info}/entry_points.txt +1 -0
- {fast_sentence_segment-1.2.1.dist-info/licenses → fast_sentence_segment-1.4.0.dist-info}/LICENSE +0 -0
|
@@ -3,11 +3,44 @@ from .svc import *
|
|
|
3
3
|
from .dmo import *
|
|
4
4
|
|
|
5
5
|
from .bp.segmenter import Segmenter
|
|
6
|
+
from .dmo.unwrap_hard_wrapped_text import unwrap_hard_wrapped_text
|
|
7
|
+
from .dmo.normalize_quotes import normalize_quotes
|
|
6
8
|
|
|
7
9
|
segment = Segmenter().input_text
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
def segment_text(
|
|
12
|
+
def segment_text(
|
|
13
|
+
input_text: str,
|
|
14
|
+
flatten: bool = False,
|
|
15
|
+
unwrap: bool = False,
|
|
16
|
+
normalize: bool = True,
|
|
17
|
+
) -> list:
|
|
18
|
+
"""Segment text into sentences.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
input_text: The text to segment.
|
|
22
|
+
flatten: If True, return a flat list of sentences instead of
|
|
23
|
+
nested paragraphs.
|
|
24
|
+
unwrap: If True, unwrap hard-wrapped lines (e.g., Project
|
|
25
|
+
Gutenberg e-texts) before segmenting.
|
|
26
|
+
normalize: If True (default), normalize unicode quote variants
|
|
27
|
+
to ASCII equivalents before segmenting. Ensures consistent
|
|
28
|
+
quote characters for downstream processing.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of sentences (if flatten=True) or list of paragraph
|
|
32
|
+
groups, each containing a list of sentences.
|
|
33
|
+
|
|
34
|
+
Related GitHub Issue:
|
|
35
|
+
#6 - Review findings from Issue #5
|
|
36
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/6
|
|
37
|
+
"""
|
|
38
|
+
if unwrap:
|
|
39
|
+
input_text = unwrap_hard_wrapped_text(input_text)
|
|
40
|
+
|
|
41
|
+
if normalize:
|
|
42
|
+
input_text = normalize_quotes(input_text)
|
|
43
|
+
|
|
11
44
|
results = segment(input_text)
|
|
12
45
|
|
|
13
46
|
if flatten:
|
fast_sentence_segment/cli.py
CHANGED
|
@@ -3,12 +3,45 @@
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
import sys
|
|
8
|
+
import time
|
|
7
9
|
|
|
8
10
|
from fast_sentence_segment import segment_text
|
|
11
|
+
from fast_sentence_segment.dmo.group_quoted_sentences import format_grouped_sentences
|
|
9
12
|
|
|
10
13
|
logging.disable(logging.CRITICAL)
|
|
11
14
|
|
|
15
|
+
# ANSI color codes
|
|
16
|
+
BOLD = "\033[1m"
|
|
17
|
+
DIM = "\033[2m"
|
|
18
|
+
CYAN = "\033[36m"
|
|
19
|
+
GREEN = "\033[32m"
|
|
20
|
+
YELLOW = "\033[33m"
|
|
21
|
+
RESET = "\033[0m"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _header(title: str):
|
|
25
|
+
print(f"\n{BOLD}{CYAN}{title}{RESET}")
|
|
26
|
+
print(f"{DIM}{'─' * 40}{RESET}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _param(label: str, value: str):
|
|
30
|
+
print(f" {DIM}{label}:{RESET} {value}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _done(msg: str):
|
|
34
|
+
print(f"\n {GREEN}✓{RESET} {msg}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _file_size(path: str) -> str:
|
|
38
|
+
size = os.path.getsize(path)
|
|
39
|
+
if size < 1024:
|
|
40
|
+
return f"{size} B"
|
|
41
|
+
elif size < 1024 * 1024:
|
|
42
|
+
return f"{size / 1024:.1f} KB"
|
|
43
|
+
return f"{size / (1024 * 1024):.1f} MB"
|
|
44
|
+
|
|
12
45
|
|
|
13
46
|
def main():
|
|
14
47
|
parser = argparse.ArgumentParser(
|
|
@@ -29,6 +62,11 @@ def main():
|
|
|
29
62
|
action="store_true",
|
|
30
63
|
help="Number output lines",
|
|
31
64
|
)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--unwrap",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Unwrap hard-wrapped lines and dehyphenate split words",
|
|
69
|
+
)
|
|
32
70
|
args = parser.parse_args()
|
|
33
71
|
|
|
34
72
|
# Get input text
|
|
@@ -44,7 +82,7 @@ def main():
|
|
|
44
82
|
sys.exit(1)
|
|
45
83
|
|
|
46
84
|
# Segment and output
|
|
47
|
-
sentences = segment_text(text.strip(), flatten=True)
|
|
85
|
+
sentences = segment_text(text.strip(), flatten=True, unwrap=args.unwrap)
|
|
48
86
|
for i, sentence in enumerate(sentences, 1):
|
|
49
87
|
if args.numbered:
|
|
50
88
|
print(f"{i}. {sentence}")
|
|
@@ -52,5 +90,60 @@ def main():
|
|
|
52
90
|
print(sentence)
|
|
53
91
|
|
|
54
92
|
|
|
93
|
+
def file_main():
|
|
94
|
+
parser = argparse.ArgumentParser(
|
|
95
|
+
prog="segment-file",
|
|
96
|
+
description="Segment a text file into sentences and write to an output file",
|
|
97
|
+
)
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"--input-file", required=True,
|
|
100
|
+
help="Path to input text file",
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--output-file", required=True,
|
|
104
|
+
help="Path to output file",
|
|
105
|
+
)
|
|
106
|
+
parser.add_argument(
|
|
107
|
+
"--unwrap", action="store_true",
|
|
108
|
+
help="Unwrap hard-wrapped lines (e.g., Project Gutenberg e-texts)",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--no-normalize-quotes", action="store_true",
|
|
112
|
+
help="Disable unicode quote normalization to ASCII equivalents",
|
|
113
|
+
)
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
_header("segment-file")
|
|
117
|
+
_param("Input", args.input_file)
|
|
118
|
+
_param("Output", args.output_file)
|
|
119
|
+
_param("Size", _file_size(args.input_file))
|
|
120
|
+
if args.unwrap:
|
|
121
|
+
_param("Unwrap", "enabled")
|
|
122
|
+
|
|
123
|
+
print(f"\n {YELLOW}Segmenting...{RESET}", end="", flush=True)
|
|
124
|
+
|
|
125
|
+
with open(args.input_file, "r", encoding="utf-8") as f:
|
|
126
|
+
text = f.read()
|
|
127
|
+
|
|
128
|
+
start = time.perf_counter()
|
|
129
|
+
normalize = not args.no_normalize_quotes
|
|
130
|
+
sentences = segment_text(
|
|
131
|
+
text.strip(), flatten=True, unwrap=args.unwrap, normalize=normalize,
|
|
132
|
+
)
|
|
133
|
+
elapsed = time.perf_counter() - start
|
|
134
|
+
|
|
135
|
+
with open(args.output_file, "w", encoding="utf-8") as f:
|
|
136
|
+
if args.unwrap:
|
|
137
|
+
f.write(format_grouped_sentences(sentences) + "\n")
|
|
138
|
+
else:
|
|
139
|
+
for sentence in sentences:
|
|
140
|
+
f.write(sentence + "\n")
|
|
141
|
+
|
|
142
|
+
print(f"\r {' ' * 20}\r", end="")
|
|
143
|
+
_done(f"{len(sentences):,} sentences in {elapsed:.2f}s")
|
|
144
|
+
_done(f"Written to {args.output_file}")
|
|
145
|
+
print()
|
|
146
|
+
|
|
147
|
+
|
|
55
148
|
if __name__ == "__main__":
|
|
56
149
|
main()
|
|
@@ -2,9 +2,14 @@ from .abbreviation_merger import AbbreviationMerger
|
|
|
2
2
|
from .abbreviation_splitter import AbbreviationSplitter
|
|
3
3
|
from .title_name_merger import TitleNameMerger
|
|
4
4
|
from .bullet_point_cleaner import BulletPointCleaner
|
|
5
|
+
from .dehyphenator import Dehyphenator
|
|
5
6
|
from .ellipsis_normalizer import EllipsisNormalizer
|
|
6
7
|
from .newlines_to_periods import NewlinesToPeriods
|
|
7
8
|
from .post_process_sentences import PostProcessStructure
|
|
8
9
|
from .question_exclamation_splitter import QuestionExclamationSplitter
|
|
9
10
|
from .spacy_doc_segmenter import SpacyDocSegmenter
|
|
10
11
|
from .numbered_list_normalizer import NumberedListNormalizer
|
|
12
|
+
from .unwrap_hard_wrapped_text import unwrap_hard_wrapped_text
|
|
13
|
+
from .normalize_quotes import normalize_quotes
|
|
14
|
+
from .group_quoted_sentences import group_quoted_sentences, format_grouped_sentences
|
|
15
|
+
from .strip_trailing_period_after_quote import StripTrailingPeriodAfterQuote
|
|
@@ -20,6 +20,8 @@ SENTENCE_ENDING_ABBREVIATIONS: List[str] = [
|
|
|
20
20
|
# Common sentence-enders
|
|
21
21
|
"etc.",
|
|
22
22
|
"ext.",
|
|
23
|
+
"approx.",
|
|
24
|
+
"dept.",
|
|
23
25
|
|
|
24
26
|
# Academic degrees (when at end of sentence)
|
|
25
27
|
"Ph.D.",
|
|
@@ -32,6 +34,9 @@ SENTENCE_ENDING_ABBREVIATIONS: List[str] = [
|
|
|
32
34
|
"J.D.",
|
|
33
35
|
"D.D.S.",
|
|
34
36
|
"R.N.",
|
|
37
|
+
"M.B.A.",
|
|
38
|
+
"LL.B.",
|
|
39
|
+
"LL.M.",
|
|
35
40
|
|
|
36
41
|
# Business (when at end of sentence)
|
|
37
42
|
"Inc.",
|
|
@@ -39,6 +44,14 @@ SENTENCE_ENDING_ABBREVIATIONS: List[str] = [
|
|
|
39
44
|
"Ltd.",
|
|
40
45
|
"Co.",
|
|
41
46
|
"Bros.",
|
|
47
|
+
"LLC.",
|
|
48
|
+
"LLP.",
|
|
49
|
+
|
|
50
|
+
# Academic/legal citations (can end sentences)
|
|
51
|
+
"ibid.",
|
|
52
|
+
"Ibid.",
|
|
53
|
+
"cf.",
|
|
54
|
+
"Cf.",
|
|
42
55
|
|
|
43
56
|
# Countries/Regions (when at end of sentence)
|
|
44
57
|
"U.S.",
|
|
@@ -62,23 +75,59 @@ TITLE_ABBREVIATIONS: List[str] = [
|
|
|
62
75
|
"Sr.",
|
|
63
76
|
"Jr.",
|
|
64
77
|
"Rev.",
|
|
78
|
+
"Hon.",
|
|
79
|
+
"Esq.",
|
|
80
|
+
|
|
81
|
+
# French/formal titles (common in translated literature)
|
|
82
|
+
"Mme.",
|
|
83
|
+
"Mlle.",
|
|
84
|
+
"Messrs.",
|
|
85
|
+
|
|
86
|
+
# Military ranks
|
|
65
87
|
"Gen.",
|
|
66
88
|
"Col.",
|
|
67
89
|
"Capt.",
|
|
68
90
|
"Lt.",
|
|
69
91
|
"Sgt.",
|
|
92
|
+
"Maj.",
|
|
93
|
+
"Cpl.",
|
|
94
|
+
"Pvt.",
|
|
95
|
+
"Adm.",
|
|
96
|
+
"Cmdr.",
|
|
97
|
+
|
|
98
|
+
# Political titles
|
|
70
99
|
"Rep.",
|
|
71
100
|
"Sen.",
|
|
72
101
|
"Gov.",
|
|
73
102
|
"Pres.",
|
|
74
|
-
|
|
103
|
+
|
|
104
|
+
# Ecclesiastical titles
|
|
105
|
+
"Fr.",
|
|
106
|
+
"Msgr.",
|
|
75
107
|
|
|
76
108
|
# Geographic prefixes
|
|
77
109
|
"St.",
|
|
78
110
|
"Mt.",
|
|
79
111
|
"Ft.",
|
|
112
|
+
"Ave.",
|
|
113
|
+
"Blvd.",
|
|
114
|
+
"Rd.",
|
|
80
115
|
|
|
81
|
-
#
|
|
116
|
+
# Latin terms (never end sentences -- always introduce clauses)
|
|
117
|
+
# Include common inconsistent forms: with/without internal periods,
|
|
118
|
+
# and with trailing comma (the most common real-world form)
|
|
119
|
+
"i.e.",
|
|
120
|
+
"i.e.,",
|
|
121
|
+
"ie.",
|
|
122
|
+
"ie.,",
|
|
123
|
+
"e.g.",
|
|
124
|
+
"e.g.,",
|
|
125
|
+
"eg.",
|
|
126
|
+
"eg.,",
|
|
127
|
+
"viz.",
|
|
128
|
+
"viz.,",
|
|
129
|
+
|
|
130
|
+
# Reference/numbering prefixes
|
|
82
131
|
"Fig.",
|
|
83
132
|
"fig.",
|
|
84
133
|
"Sec.",
|
|
@@ -93,4 +142,8 @@ TITLE_ABBREVIATIONS: List[str] = [
|
|
|
93
142
|
"no.",
|
|
94
143
|
"Pt.",
|
|
95
144
|
"pt.",
|
|
145
|
+
|
|
146
|
+
# Legal / adversarial
|
|
147
|
+
"vs.",
|
|
148
|
+
"Vs.",
|
|
96
149
|
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Dehyphenate words split across lines.
|
|
3
|
+
|
|
4
|
+
Related GitHub Issue:
|
|
5
|
+
#8 - Add dehyphenation support for words split across lines
|
|
6
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/8
|
|
7
|
+
|
|
8
|
+
When processing ebooks and scanned documents, words are often hyphenated
|
|
9
|
+
at line breaks for typesetting purposes. This module rejoins those words.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
from fast_sentence_segment.core import BaseObject
|
|
15
|
+
|
|
16
|
+
# Pattern to match hyphenated word breaks at end of line:
|
|
17
|
+
# - A single hyphen (not -- em-dash)
|
|
18
|
+
# - Followed by newline and optional whitespace
|
|
19
|
+
# - Followed by a lowercase letter (continuation of word)
|
|
20
|
+
_HYPHEN_LINE_BREAK_PATTERN = re.compile(r'(?<!-)-\n\s*([a-z])')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Dehyphenator(BaseObject):
|
|
24
|
+
"""Rejoin words that were hyphenated across line breaks."""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
"""Change Log
|
|
28
|
+
|
|
29
|
+
Created:
|
|
30
|
+
3-Feb-2026
|
|
31
|
+
craigtrim@gmail.com
|
|
32
|
+
* add dehyphenation support for words split across lines
|
|
33
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/8
|
|
34
|
+
"""
|
|
35
|
+
BaseObject.__init__(self, __name__)
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def process(input_text: str) -> str:
|
|
39
|
+
"""Rejoin words that were hyphenated across line breaks.
|
|
40
|
+
|
|
41
|
+
Detects the pattern of a word fragment ending with a hyphen
|
|
42
|
+
at the end of a line, followed by the word continuation
|
|
43
|
+
starting with a lowercase letter on the next line.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
"bot-\\ntle" -> "bottle"
|
|
47
|
+
"cham-\\n bermaid" -> "chambermaid"
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
input_text: Text that may contain hyphenated line breaks.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Text with hyphenated word breaks rejoined.
|
|
54
|
+
"""
|
|
55
|
+
return _HYPHEN_LINE_BREAK_PATTERN.sub(r'\1', input_text)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Group sentences that belong to the same open-quote span.
|
|
3
|
+
|
|
4
|
+
When outputting segmented text with blank-line separators, sentences
|
|
5
|
+
that open with a double quote but do not close it should be grouped
|
|
6
|
+
with subsequent sentences (no blank line between them) until the
|
|
7
|
+
closing quote is found.
|
|
8
|
+
|
|
9
|
+
Related GitHub Issues:
|
|
10
|
+
#5 - Normalize quotes and group open-quote sentences in unwrap mode
|
|
11
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/5
|
|
12
|
+
|
|
13
|
+
#6 - Review findings from Issue #5
|
|
14
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/6
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import List
|
|
18
|
+
|
|
19
|
+
# Maximum number of sentences that can be grouped under a single
|
|
20
|
+
# open-quote span before the quote state is forcibly reset. This
|
|
21
|
+
# bounds the damage from a stray quote character (e.g., OCR artifact)
|
|
22
|
+
# which would otherwise corrupt grouping for all subsequent sentences.
|
|
23
|
+
#
|
|
24
|
+
# A typical quoted passage in literature rarely exceeds 20 sentences.
|
|
25
|
+
# This limit is deliberately generous to avoid false resets on
|
|
26
|
+
# legitimately long quoted passages while still preventing runaway
|
|
27
|
+
# grouping on malformed input.
|
|
28
|
+
MAX_QUOTE_GROUP_SIZE = 20
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def group_quoted_sentences(sentences: List[str]) -> List[List[str]]:
|
|
32
|
+
"""Group sentences into blocks based on open/close quote tracking.
|
|
33
|
+
|
|
34
|
+
Sentences within an unclosed double-quote span are grouped together
|
|
35
|
+
into the same block. Sentences outside of a quote span each form
|
|
36
|
+
their own block.
|
|
37
|
+
|
|
38
|
+
When rendered, each block is joined by newlines, and blocks are
|
|
39
|
+
separated by blank lines (double newlines).
|
|
40
|
+
|
|
41
|
+
The algorithm tracks the quote state by counting ASCII double quote
|
|
42
|
+
characters in each sentence. An odd count toggles the open/close
|
|
43
|
+
state. When a quote is open, subsequent sentences are appended to
|
|
44
|
+
the current group rather than starting a new one.
|
|
45
|
+
|
|
46
|
+
A safety limit (MAX_QUOTE_GROUP_SIZE) prevents a stray or malformed
|
|
47
|
+
quote from swallowing all remaining sentences into one group. When
|
|
48
|
+
the limit is reached, the current group is flushed and the quote
|
|
49
|
+
state is reset. This bounds corruption from OCR artifacts or
|
|
50
|
+
encoding errors to a bounded window rather than the entire document.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
sentences: Flat list of segmented sentences.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
List of sentence groups. Each group is a list of sentences
|
|
57
|
+
that should be rendered together without blank-line separators.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> groups = group_quoted_sentences([
|
|
61
|
+
... '"The probability lies in that direction.',
|
|
62
|
+
... 'And if we take this as a working hypothesis."',
|
|
63
|
+
... 'He paused.',
|
|
64
|
+
... ])
|
|
65
|
+
>>> groups
|
|
66
|
+
[['"The probability lies in that direction.',
|
|
67
|
+
'And if we take this as a working hypothesis."'],
|
|
68
|
+
['He paused.']]
|
|
69
|
+
|
|
70
|
+
Related GitHub Issues:
|
|
71
|
+
#5 - Normalize quotes and group open-quote sentences in unwrap mode
|
|
72
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/5
|
|
73
|
+
|
|
74
|
+
#6 - Review findings from Issue #5
|
|
75
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/6
|
|
76
|
+
"""
|
|
77
|
+
if not sentences:
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
groups: List[List[str]] = []
|
|
81
|
+
current_group: List[str] = []
|
|
82
|
+
quote_open = False
|
|
83
|
+
|
|
84
|
+
for sentence in sentences:
|
|
85
|
+
quote_count = sentence.count('"')
|
|
86
|
+
|
|
87
|
+
if not quote_open:
|
|
88
|
+
# Starting a new group
|
|
89
|
+
if current_group:
|
|
90
|
+
groups.append(current_group)
|
|
91
|
+
current_group = [sentence]
|
|
92
|
+
else:
|
|
93
|
+
# Inside an open quote span -- append to current group
|
|
94
|
+
current_group.append(sentence)
|
|
95
|
+
|
|
96
|
+
# Toggle quote state on odd quote count
|
|
97
|
+
if quote_count % 2 == 1:
|
|
98
|
+
quote_open = not quote_open
|
|
99
|
+
|
|
100
|
+
# Safety: if a group grows beyond the limit, the quote is
|
|
101
|
+
# likely corrupted (stray quote character). Flush the group
|
|
102
|
+
# and reset state to prevent runaway grouping.
|
|
103
|
+
if quote_open and len(current_group) >= MAX_QUOTE_GROUP_SIZE:
|
|
104
|
+
groups.append(current_group)
|
|
105
|
+
current_group = []
|
|
106
|
+
quote_open = False
|
|
107
|
+
|
|
108
|
+
# Flush the final group
|
|
109
|
+
if current_group:
|
|
110
|
+
groups.append(current_group)
|
|
111
|
+
|
|
112
|
+
return groups
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def format_grouped_sentences(sentences: List[str]) -> str:
|
|
116
|
+
"""Format sentences with quote-aware blank-line separation.
|
|
117
|
+
|
|
118
|
+
Sentences within the same quoted span are separated by single
|
|
119
|
+
newlines. Sentence groups are separated by blank lines (double
|
|
120
|
+
newlines).
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
sentences: Flat list of segmented sentences.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Formatted string with appropriate line separation.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
>>> text = format_grouped_sentences([
|
|
130
|
+
... '"The probability lies in that direction.',
|
|
131
|
+
... 'And if we take this as a working hypothesis."',
|
|
132
|
+
... 'He paused.',
|
|
133
|
+
... ])
|
|
134
|
+
>>> print(text)
|
|
135
|
+
"The probability lies in that direction.
|
|
136
|
+
And if we take this as a working hypothesis."
|
|
137
|
+
<BLANKLINE>
|
|
138
|
+
He paused.
|
|
139
|
+
"""
|
|
140
|
+
groups = group_quoted_sentences(sentences)
|
|
141
|
+
return '\n\n'.join('\n'.join(group) for group in groups)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Normalize unicode quote variants to ASCII equivalents.
|
|
3
|
+
|
|
4
|
+
E-texts use a variety of quote characters (curly/smart quotes, unicode
|
|
5
|
+
variants, primes, guillemets). This module normalizes all quote variants
|
|
6
|
+
to their standard ASCII equivalents: double quote (") and single
|
|
7
|
+
quote/apostrophe (').
|
|
8
|
+
|
|
9
|
+
Related GitHub Issues:
|
|
10
|
+
#5 - Normalize quotes and group open-quote sentences in unwrap mode
|
|
11
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/5
|
|
12
|
+
|
|
13
|
+
#6 - Review findings from Issue #5
|
|
14
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/6
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
# Unicode double quote variants to normalize to ASCII " (U+0022).
|
|
20
|
+
#
|
|
21
|
+
# U+201C " LEFT DOUBLE QUOTATION MARK
|
|
22
|
+
# U+201D " RIGHT DOUBLE QUOTATION MARK
|
|
23
|
+
# U+201E „ DOUBLE LOW-9 QUOTATION MARK
|
|
24
|
+
# U+201F ‟ DOUBLE HIGH-REVERSED-9 QUOTATION MARK
|
|
25
|
+
# U+00AB « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
|
|
26
|
+
# U+00BB » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
|
|
27
|
+
# U+2033 ″ DOUBLE PRIME
|
|
28
|
+
# U+301D 〝 REVERSED DOUBLE PRIME QUOTATION MARK
|
|
29
|
+
# U+301E 〞 DOUBLE PRIME QUOTATION MARK
|
|
30
|
+
# U+301F 〟 LOW DOUBLE PRIME QUOTATION MARK
|
|
31
|
+
# U+FF02 " FULLWIDTH QUOTATION MARK
|
|
32
|
+
DOUBLE_QUOTE_PATTERN = re.compile(
|
|
33
|
+
'[\u201c\u201d\u201e\u201f\u00ab\u00bb\u2033\u301d\u301e\u301f\uff02]'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Unicode single quote variants to normalize to ASCII ' (U+0027).
|
|
37
|
+
#
|
|
38
|
+
# U+2018 ' LEFT SINGLE QUOTATION MARK
|
|
39
|
+
# U+2019 ' RIGHT SINGLE QUOTATION MARK
|
|
40
|
+
# U+201A ‚ SINGLE LOW-9 QUOTATION MARK
|
|
41
|
+
# U+201B ‛ SINGLE HIGH-REVERSED-9 QUOTATION MARK
|
|
42
|
+
# U+2039 ‹ SINGLE LEFT-POINTING ANGLE QUOTATION MARK
|
|
43
|
+
# U+203A › SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
|
|
44
|
+
# U+2032 ′ PRIME
|
|
45
|
+
# U+FF07 ' FULLWIDTH APOSTROPHE
|
|
46
|
+
# U+0060 ` GRAVE ACCENT (used as opening quote in some e-texts)
|
|
47
|
+
# U+00B4 ´ ACUTE ACCENT (used as closing quote in some e-texts)
|
|
48
|
+
SINGLE_QUOTE_PATTERN = re.compile(
|
|
49
|
+
'[\u2018\u2019\u201a\u201b\u2039\u203a\u2032\uff07\u0060\u00b4]'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def normalize_quotes(text: str) -> str:
|
|
54
|
+
"""Replace all unicode quote variants with their ASCII equivalents.
|
|
55
|
+
|
|
56
|
+
Double quote variants are normalized to ASCII " (U+0022).
|
|
57
|
+
Single quote variants are normalized to ASCII ' (U+0027).
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
text: Input text potentially containing unicode quotes.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Text with all quote variants replaced by ASCII equivalents.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> normalize_quotes('\u201cHello,\u201d she said.')
|
|
67
|
+
'"Hello," she said.'
|
|
68
|
+
>>> normalize_quotes('It\u2019s fine.')
|
|
69
|
+
"It's fine."
|
|
70
|
+
|
|
71
|
+
Related GitHub Issues:
|
|
72
|
+
#5 - Normalize quotes and group open-quote sentences in unwrap mode
|
|
73
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/5
|
|
74
|
+
|
|
75
|
+
#6 - Review findings from Issue #5
|
|
76
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/6
|
|
77
|
+
"""
|
|
78
|
+
text = DOUBLE_QUOTE_PATTERN.sub('"', text)
|
|
79
|
+
text = SINGLE_QUOTE_PATTERN.sub("'", text)
|
|
80
|
+
return text
|
|
@@ -23,18 +23,31 @@ class SpacyDocSegmenter(BaseObject):
|
|
|
23
23
|
|
|
24
24
|
@staticmethod
|
|
25
25
|
def _append_period(a_sentence: str) -> str:
|
|
26
|
+
"""Append a period if the sentence lacks terminal punctuation.
|
|
27
|
+
|
|
28
|
+
Checks for terminal punctuation (. ? ! :) after stripping any
|
|
29
|
+
trailing quote characters (" '). This prevents a spurious period
|
|
30
|
+
from being appended to sentences like:
|
|
31
|
+
'He said "Hello."' -> unchanged (not 'He said "Hello.".')
|
|
32
|
+
|
|
33
|
+
Related GitHub Issue:
|
|
34
|
+
#7 - Spurious trailing period appended after sentence-final
|
|
35
|
+
closing quote
|
|
36
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/7
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
a_sentence: A sentence that may or may not have terminal
|
|
40
|
+
punctuation.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The sentence with a period appended if it lacked terminal
|
|
44
|
+
punctuation, otherwise unchanged.
|
|
26
45
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
:
|
|
30
|
-
|
|
31
|
-
""
|
|
32
|
-
__blacklist = [':', '?', '!']
|
|
33
|
-
if not a_sentence.strip().endswith('.'):
|
|
34
|
-
for ch in __blacklist:
|
|
35
|
-
if not a_sentence.endswith(ch):
|
|
36
|
-
return f"{a_sentence}."
|
|
37
|
-
return a_sentence
|
|
46
|
+
# Strip trailing quotes to inspect the actual punctuation
|
|
47
|
+
stripped = a_sentence.strip().rstrip('"\'')
|
|
48
|
+
if stripped and stripped[-1] in '.?!:':
|
|
49
|
+
return a_sentence
|
|
50
|
+
return f"{a_sentence}."
|
|
38
51
|
|
|
39
52
|
@staticmethod
|
|
40
53
|
def _is_valid_sentence(a_sentence: str) -> bool:
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Strip spurious trailing periods appended after sentence-final closing quotes.
|
|
3
|
+
|
|
4
|
+
The spaCy segmenter's _append_period method can produce sentences like:
|
|
5
|
+
'He said "Hello.".' (spurious trailing period)
|
|
6
|
+
'She asked "Why?".' (spurious trailing period)
|
|
7
|
+
'He yelled "Stop!".' (spurious trailing period)
|
|
8
|
+
|
|
9
|
+
This post-processor removes the trailing period when the sentence ends
|
|
10
|
+
with a closing double quote preceded by terminal punctuation.
|
|
11
|
+
|
|
12
|
+
Related GitHub Issue:
|
|
13
|
+
#7 - Spurious trailing period appended after sentence-final
|
|
14
|
+
closing quote
|
|
15
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/7
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
from fast_sentence_segment.core import BaseObject
|
|
21
|
+
|
|
22
|
+
# Matches a sentence that ends with terminal punctuation (. ? !)
|
|
23
|
+
# followed by a closing double quote, followed by a spurious period.
|
|
24
|
+
# The fix strips the final period.
|
|
25
|
+
_SPURIOUS_PERIOD_PATTERN = re.compile(r'([.?!]")\.$')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StripTrailingPeriodAfterQuote(BaseObject):
|
|
29
|
+
"""Strip spurious trailing periods after sentence-final closing quotes.
|
|
30
|
+
|
|
31
|
+
Detects sentences ending with patterns like:
|
|
32
|
+
."." -> ."
|
|
33
|
+
?"." -> ?"
|
|
34
|
+
!"." -> !"
|
|
35
|
+
|
|
36
|
+
Applied as a post-processing step in the sentence segmentation
|
|
37
|
+
pipeline, after spaCy segmentation and after the existing
|
|
38
|
+
PostProcessStructure step.
|
|
39
|
+
|
|
40
|
+
Related GitHub Issue:
|
|
41
|
+
#7 - Spurious trailing period appended after sentence-final
|
|
42
|
+
closing quote
|
|
43
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/7
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
"""
|
|
48
|
+
Created:
|
|
49
|
+
29-Jan-2026
|
|
50
|
+
"""
|
|
51
|
+
BaseObject.__init__(self, __name__)
|
|
52
|
+
|
|
53
|
+
def process(self, sentences: list) -> list:
|
|
54
|
+
"""Remove spurious trailing periods after closing quotes.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
sentences: List of segmented sentences.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List of sentences with spurious trailing periods removed.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> proc = StripTrailingPeriodAfterQuote()
|
|
64
|
+
>>> proc.process(['He said "Hello.".', 'She waved.'])
|
|
65
|
+
['He said "Hello."', 'She waved.']
|
|
66
|
+
"""
|
|
67
|
+
return [
|
|
68
|
+
_SPURIOUS_PERIOD_PATTERN.sub(r'\1', sentence)
|
|
69
|
+
for sentence in sentences
|
|
70
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Unwrap hard-wrapped text (e.g., Project Gutenberg e-texts).
|
|
3
|
+
|
|
4
|
+
Joins lines within paragraphs into continuous strings while
|
|
5
|
+
preserving paragraph boundaries (blank lines). Also dehyphenates
|
|
6
|
+
words that were split across lines for typesetting.
|
|
7
|
+
|
|
8
|
+
Related GitHub Issue:
|
|
9
|
+
#8 - Add dehyphenation support for words split across lines
|
|
10
|
+
https://github.com/craigtrim/fast-sentence-segment/issues/8
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
# Pattern to match hyphenated word breaks at end of line:
|
|
16
|
+
# - A single hyphen (not -- em-dash)
|
|
17
|
+
# - Followed by newline and optional whitespace
|
|
18
|
+
# - Followed by a lowercase letter (continuation of word)
|
|
19
|
+
_HYPHEN_LINE_BREAK_PATTERN = re.compile(r'(?<!-)-\n\s*([a-z])')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _dehyphenate_block(block: str) -> str:
|
|
23
|
+
"""Remove hyphens from words split across lines.
|
|
24
|
+
|
|
25
|
+
Detects the pattern of a word fragment ending with a hyphen
|
|
26
|
+
at the end of a line, followed by the word continuation
|
|
27
|
+
starting with a lowercase letter on the next line.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
"bot-\\ntle" -> "bottle"
|
|
31
|
+
"cham-\\n bermaid" -> "chambermaid"
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
block: A paragraph block that may contain hyphenated line breaks.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The block with hyphenated word breaks rejoined.
|
|
38
|
+
"""
|
|
39
|
+
return _HYPHEN_LINE_BREAK_PATTERN.sub(r'\1', block)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def unwrap_hard_wrapped_text(text: str) -> str:
|
|
43
|
+
"""Unwrap hard-wrapped paragraphs into continuous lines.
|
|
44
|
+
|
|
45
|
+
Splits on blank lines to identify paragraphs, then joins
|
|
46
|
+
lines within each paragraph into a single string with
|
|
47
|
+
single spaces. Also dehyphenates words that were split
|
|
48
|
+
across lines for typesetting purposes.
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
>>> unwrap_hard_wrapped_text("a bot-\\ntle of wine")
|
|
52
|
+
'a bottle of wine'
|
|
53
|
+
>>> unwrap_hard_wrapped_text("line one\\nline two")
|
|
54
|
+
'line one line two'
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
text: Raw text with hard-wrapped lines.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Text with paragraphs unwrapped into continuous strings,
|
|
61
|
+
separated by double newlines, with hyphenated words rejoined.
|
|
62
|
+
"""
|
|
63
|
+
blocks = re.split(r'\n\s*\n', text)
|
|
64
|
+
unwrapped = []
|
|
65
|
+
|
|
66
|
+
for block in blocks:
|
|
67
|
+
# First, dehyphenate words split across lines
|
|
68
|
+
block = _dehyphenate_block(block)
|
|
69
|
+
# Then join remaining lines with spaces
|
|
70
|
+
lines = block.splitlines()
|
|
71
|
+
joined = ' '.join(line.strip() for line in lines if line.strip())
|
|
72
|
+
if joined:
|
|
73
|
+
unwrapped.append(joined)
|
|
74
|
+
|
|
75
|
+
return '\n\n'.join(unwrapped)
|
|
@@ -17,6 +17,8 @@ from fast_sentence_segment.dmo import NumberedListNormalizer
|
|
|
17
17
|
from fast_sentence_segment.dmo import QuestionExclamationSplitter
|
|
18
18
|
from fast_sentence_segment.dmo import SpacyDocSegmenter
|
|
19
19
|
from fast_sentence_segment.dmo import PostProcessStructure
|
|
20
|
+
from fast_sentence_segment.dmo import StripTrailingPeriodAfterQuote
|
|
21
|
+
from fast_sentence_segment.dmo import Dehyphenator
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class PerformSentenceSegmentation(BaseObject):
|
|
@@ -45,6 +47,7 @@ class PerformSentenceSegmentation(BaseObject):
|
|
|
45
47
|
if not self.__nlp:
|
|
46
48
|
self.__nlp = spacy.load("en_core_web_sm")
|
|
47
49
|
|
|
50
|
+
self._dehyphenate = Dehyphenator.process
|
|
48
51
|
self._newlines_to_periods = NewlinesToPeriods.process
|
|
49
52
|
self._normalize_numbered_lists = NumberedListNormalizer().process
|
|
50
53
|
self._normalize_ellipses = EllipsisNormalizer().process
|
|
@@ -55,6 +58,7 @@ class PerformSentenceSegmentation(BaseObject):
|
|
|
55
58
|
self._question_exclamation_splitter = QuestionExclamationSplitter().process
|
|
56
59
|
self._title_name_merger = TitleNameMerger().process
|
|
57
60
|
self._post_process = PostProcessStructure().process
|
|
61
|
+
self._strip_trailing_period = StripTrailingPeriodAfterQuote().process
|
|
58
62
|
|
|
59
63
|
def _denormalize(self, text: str) -> str:
|
|
60
64
|
""" Restore normalized placeholders to original form """
|
|
@@ -94,6 +98,10 @@ class PerformSentenceSegmentation(BaseObject):
|
|
|
94
98
|
# Normalize tabs to spaces
|
|
95
99
|
input_text = input_text.replace('\t', ' ')
|
|
96
100
|
|
|
101
|
+
# Dehyphenate words split across lines (issue #8)
|
|
102
|
+
# Must happen before newlines are converted to periods
|
|
103
|
+
input_text = self._dehyphenate(input_text)
|
|
104
|
+
|
|
97
105
|
input_text = self._normalize_numbered_lists(input_text)
|
|
98
106
|
input_text = self._normalize_ellipses(input_text)
|
|
99
107
|
|
|
@@ -129,6 +137,9 @@ class PerformSentenceSegmentation(BaseObject):
|
|
|
129
137
|
|
|
130
138
|
sentences = self._post_process(sentences)
|
|
131
139
|
|
|
140
|
+
# Strip spurious trailing periods after closing quotes (issue #7)
|
|
141
|
+
sentences = self._strip_trailing_period(sentences)
|
|
142
|
+
|
|
132
143
|
sentences = [
|
|
133
144
|
self._normalize_numbered_lists(x, denormalize=True)
|
|
134
145
|
for x in sentences
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: fast-sentence-segment
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Fast and Efficient Sentence Segmentation
|
|
5
|
+
Home-page: https://github.com/craigtrim/fast-sentence-segment
|
|
5
6
|
License: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
7
|
Keywords: nlp,text,preprocess,segment
|
|
8
8
|
Author: Craig Trim
|
|
9
9
|
Author-email: craigtrim@gmail.com
|
|
10
10
|
Maintainer: Craig Trim
|
|
11
11
|
Maintainer-email: craigtrim@gmail.com
|
|
12
12
|
Requires-Python: >=3.9,<4.0
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
15
18
|
Classifier: Programming Language :: Python :: 3
|
|
16
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier:
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
26
|
+
Classifier: Typing :: Typed
|
|
23
27
|
Requires-Dist: spacy (>=3.8.0,<4.0.0)
|
|
24
28
|
Project-URL: Bug Tracker, https://github.com/craigtrim/fast-sentence-segment/issues
|
|
25
29
|
Project-URL: Repository, https://github.com/craigtrim/fast-sentence-segment
|
|
@@ -29,8 +33,11 @@ Description-Content-Type: text/markdown
|
|
|
29
33
|
|
|
30
34
|
[](https://pypi.org/project/fast-sentence-segment/)
|
|
31
35
|
[](https://pypi.org/project/fast-sentence-segment/)
|
|
36
|
+
[](https://github.com/craigtrim/fast-sentence-segment/actions/workflows/ci.yml)
|
|
32
37
|
[](https://opensource.org/licenses/MIT)
|
|
33
|
-
[](https://github.com/astral-sh/ruff)
|
|
39
|
+
[](https://pepy.tech/project/fast-sentence-segment)
|
|
40
|
+
[](https://pepy.tech/project/fast-sentence-segment)
|
|
34
41
|
|
|
35
42
|
Fast and efficient sentence segmentation using spaCy with surgical post-processing fixes. Handles complex edge cases like abbreviations (Dr., Mr., etc.), ellipses, quoted text, and multi-paragraph documents.
|
|
36
43
|
|
|
@@ -142,48 +149,36 @@ results = segmenter.input_text("Your text here.")
|
|
|
142
149
|
|
|
143
150
|
### Command Line Interface
|
|
144
151
|
|
|
145
|
-
Segment text directly from the terminal:
|
|
146
|
-
|
|
147
152
|
```bash
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
```
|
|
153
|
+
# Inline text
|
|
154
|
+
segment "Gandalf paused... You shall not pass! The Balrog roared."
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
Have you seen Dr. Who?
|
|
154
|
-
It's brilliant!
|
|
155
|
-
```
|
|
156
|
+
# Pipe from stdin
|
|
157
|
+
echo "Have you seen Dr. Who? It's brilliant!" | segment
|
|
156
158
|
|
|
157
|
-
```bash
|
|
158
159
|
# Numbered output
|
|
159
|
-
segment -n
|
|
160
|
-
```
|
|
160
|
+
segment -n -f silmarillion.txt
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
2. You shall not pass!
|
|
165
|
-
3. The Balrog roared.
|
|
166
|
-
```
|
|
162
|
+
# File-to-file (one sentence per line)
|
|
163
|
+
segment-file --input-file book.txt --output-file sentences.txt
|
|
167
164
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
segment -f silmarillion.txt
|
|
165
|
+
# Unwrap hard-wrapped e-texts (Project Gutenberg, etc.)
|
|
166
|
+
segment-file --input-file book.txt --output-file sentences.txt --unwrap
|
|
171
167
|
```
|
|
172
168
|
|
|
173
169
|
## API Reference
|
|
174
170
|
|
|
175
171
|
| Function | Parameters | Returns | Description |
|
|
176
172
|
|----------|------------|---------|-------------|
|
|
177
|
-
| `segment_text()` | `input_text: str`, `flatten: bool = False` | `list` | Main entry point for segmentation |
|
|
173
|
+
| `segment_text()` | `input_text: str`, `flatten: bool = False`, `unwrap: bool = False` | `list` | Main entry point for segmentation |
|
|
178
174
|
| `Segmenter.input_text()` | `input_text: str` | `list[list[str]]` | Cached paragraph-aware segmentation |
|
|
179
175
|
|
|
180
|
-
### CLI
|
|
176
|
+
### CLI Commands
|
|
181
177
|
|
|
182
|
-
|
|
|
183
|
-
|
|
184
|
-
| `text` |
|
|
185
|
-
|
|
|
186
|
-
| `-n, --numbered` | Number output lines |
|
|
178
|
+
| Command | Description |
|
|
179
|
+
|---------|-------------|
|
|
180
|
+
| `segment [text]` | Segment text from argument, `-f FILE`, or stdin. Use `-n` for numbered output. |
|
|
181
|
+
| `segment-file --input-file IN --output-file OUT [--unwrap]` | Segment a file and write one sentence per line. Use `--unwrap` for hard-wrapped e-texts. |
|
|
187
182
|
|
|
188
183
|
## Why Nested Lists?
|
|
189
184
|
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
fast_sentence_segment/__init__.py,sha256=
|
|
1
|
+
fast_sentence_segment/__init__.py,sha256=jeb4yCy89ivyqbo-4ldJLquPAG_XR_33Q7nrDjqPxvE,1465
|
|
2
2
|
fast_sentence_segment/bp/__init__.py,sha256=j2-WfQ9WwVuXeGSjvV6XLVwEdvau8sdAQe4Pa4DrYi8,33
|
|
3
3
|
fast_sentence_segment/bp/segmenter.py,sha256=UW6DguPgA56h-pPYRsfJhjIzBe40j6NdjkwYxamASyA,1928
|
|
4
|
-
fast_sentence_segment/cli.py,sha256=
|
|
4
|
+
fast_sentence_segment/cli.py,sha256=Y89BH-xbJ0vykg301D2543MtGP4kYLnA6i3UQ7Hg5YA,3869
|
|
5
5
|
fast_sentence_segment/core/__init__.py,sha256=uoBersYyVStJ5a8zJpQz1GDGaloEdAv2jGHw1292hRM,108
|
|
6
6
|
fast_sentence_segment/core/base_object.py,sha256=AYr7yzusIwawjbKdvcv4yTEnhmx6M583kDZzhzPOmq4,635
|
|
7
7
|
fast_sentence_segment/core/stopwatch.py,sha256=hE6hMz2q6rduaKi58KZmiAL-lRtyh_wWCANhl4KLkRQ,879
|
|
8
|
-
fast_sentence_segment/dmo/__init__.py,sha256=
|
|
8
|
+
fast_sentence_segment/dmo/__init__.py,sha256=N0lLHVn6zKeg6h1LIfoc4XeXPUY-uSbMT45dP2_vn8M,862
|
|
9
9
|
fast_sentence_segment/dmo/abbreviation_merger.py,sha256=tCXM6yCfMryJvMIVWIxP_EocoibZi8vohFzJ5tvMYr0,4432
|
|
10
10
|
fast_sentence_segment/dmo/abbreviation_splitter.py,sha256=03mSyJcLooNyIjXx6mPlrnjmKgZW-uhUIqG4U-MbIGw,2981
|
|
11
|
-
fast_sentence_segment/dmo/abbreviations.py,sha256=
|
|
11
|
+
fast_sentence_segment/dmo/abbreviations.py,sha256=CGJrJDo6pmYd3pTNEQbdOo8N6tnkCnwyL2X7Si663Os,2530
|
|
12
12
|
fast_sentence_segment/dmo/bullet_point_cleaner.py,sha256=WOZQRWXiiyRi8rOuEIw36EmkaXmATHL9_Dxb2rderw4,1606
|
|
13
|
+
fast_sentence_segment/dmo/dehyphenator.py,sha256=6BJTie7tClRAifeiW8V2CdAAbcbknhtqmKylAdRZ7ko,1776
|
|
13
14
|
fast_sentence_segment/dmo/ellipsis_normalizer.py,sha256=lHs9dLFfKJe-2vFNe17Hik90g3_kXX347OzGP_IOT08,1521
|
|
15
|
+
fast_sentence_segment/dmo/group_quoted_sentences.py,sha256=Ifh_kUwi7sMbzbZvrTgEKkzWe50AafUDhVKVPR9h7wQ,5092
|
|
14
16
|
fast_sentence_segment/dmo/newlines_to_periods.py,sha256=PUrXreqZWiITINfoJL5xRRlXJH6noH0cdXtW1EqAh8I,1517
|
|
17
|
+
fast_sentence_segment/dmo/normalize_quotes.py,sha256=mr53qo_tj_I9XzElOKjUQvCtDQh7mBCGy-iqsHZDX14,2881
|
|
15
18
|
fast_sentence_segment/dmo/numbered_list_normalizer.py,sha256=q0sOCW8Jkn2vTXlUcVhmDvYES3yvJx1oUVl_8y7eL4E,1672
|
|
16
19
|
fast_sentence_segment/dmo/post_process_sentences.py,sha256=5jxG3TmFjxIExMPLhnCB5JT1lXQvFU9r4qQGoATGrWk,916
|
|
17
20
|
fast_sentence_segment/dmo/question_exclamation_splitter.py,sha256=cRsWRu8zb6wOWG-BjMahHfz4YGutKiV9lW7dE-q3tgc,2006
|
|
18
|
-
fast_sentence_segment/dmo/spacy_doc_segmenter.py,sha256=
|
|
21
|
+
fast_sentence_segment/dmo/spacy_doc_segmenter.py,sha256=_oTsrIL2rjysjt_8bPJVNTn230pUtL-geCC8g174iC4,3163
|
|
22
|
+
fast_sentence_segment/dmo/strip_trailing_period_after_quote.py,sha256=wYkoLy5XJKZIblJXBvDAB8-a81UTQOhOf2u91wjJWUw,2259
|
|
19
23
|
fast_sentence_segment/dmo/title_name_merger.py,sha256=zbG04_VjwM8TtT8LhavvmZqIZL_2xgT2OTxWkK_Zt1s,5133
|
|
24
|
+
fast_sentence_segment/dmo/unwrap_hard_wrapped_text.py,sha256=V1T5RsJBaII_iGJMyWvv6rb2mny8pnVd428oVZL0n5I,2457
|
|
20
25
|
fast_sentence_segment/svc/__init__.py,sha256=9B12mXxBnlalH4OAm1AMLwUMa-RLi2ilv7qhqv26q7g,144
|
|
21
26
|
fast_sentence_segment/svc/perform_paragraph_segmentation.py,sha256=zLKw9rSzb0NNfx4MyEeoGrHwhxTtH5oDrYcAL2LMVHY,1378
|
|
22
|
-
fast_sentence_segment/svc/perform_sentence_segmentation.py,sha256=
|
|
23
|
-
fast_sentence_segment-1.
|
|
24
|
-
fast_sentence_segment-1.
|
|
25
|
-
fast_sentence_segment-1.
|
|
26
|
-
fast_sentence_segment-1.
|
|
27
|
-
fast_sentence_segment-1.
|
|
27
|
+
fast_sentence_segment/svc/perform_sentence_segmentation.py,sha256=Qaj7oxVHfUd6pIlwCD1O8P14LaGUdolFGuykmrF6gw8,6276
|
|
28
|
+
fast_sentence_segment-1.4.0.dist-info/LICENSE,sha256=vou5JCLAT5nHcsUv-AkjUYAihYfN9mwPDXxV2DHyHBo,1067
|
|
29
|
+
fast_sentence_segment-1.4.0.dist-info/METADATA,sha256=XernLjKJbfSBxiqhQm-SZC8JFtggSoPe1hUD2X6D9N8,7853
|
|
30
|
+
fast_sentence_segment-1.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
31
|
+
fast_sentence_segment-1.4.0.dist-info/entry_points.txt,sha256=Zc8OwFKj3ofnjy5ZIFqHzDkIEWweV1AP1xap1ZFGD8M,107
|
|
32
|
+
fast_sentence_segment-1.4.0.dist-info/RECORD,,
|
{fast_sentence_segment-1.2.1.dist-info/licenses → fast_sentence_segment-1.4.0.dist-info}/LICENSE
RENAMED
|
File without changes
|