ja_sentence_segmenter.concatenate.simple_concatenator
Simple sentence concatenator for japanese text.
1"""Simple sentence concatenator for japanese text.""" 2 3import re 4from collections.abc import Generator, Iterator 5from typing import Optional, Union, overload 6 7 8def __concatenate_matching_iter( 9 texts: Iterator[str], 10 former_matching_rule: Optional[str], 11 latter_matching_rule: Optional[str], 12 remove_former_matched: bool, 13 remove_latter_matched: bool, 14 max_concatenate_length: Optional[int], 15) -> Generator[str, None, None]: 16 # 累積は入力1行ごとに再走査されるので、パターンのコンパイルはループの外で1度だけ行う。 17 former_pattern = re.compile(former_matching_rule) if former_matching_rule else None 18 latter_pattern = re.compile(latter_matching_rule) if latter_matching_rule else None 19 20 try: 21 former = next(texts) 22 # 上限は「現在の累積で1回以上結合が起きた後」にのみ確認する。 23 # そうしないと、1行目が単独で上限を超えている場合に 24 # former_matching_rule が一度も評価されないまま素通りしてしまう。 25 concatenated = False 26 27 for latter in texts: 28 if concatenated and max_concatenate_length is not None and len(former) >= max_concatenate_length: 29 yield former 30 former = latter 31 concatenated = False 32 continue 33 34 former_match_obj = former_pattern.match(former) if former_pattern else None 35 latter_match_obj = latter_pattern.match(latter) if latter_pattern else None 36 37 if former_matching_rule and latter_matching_rule and former_match_obj and latter_match_obj: 38 tmp_former = former_match_obj.group("result") if remove_former_matched else former 39 tmp_latter = latter_match_obj.group("result") if remove_latter_matched else latter 40 former = tmp_former + tmp_latter 41 concatenated = True 42 elif former_matching_rule and not latter_matching_rule and former_match_obj: 43 tmp_former = former_match_obj.group("result") if remove_former_matched else former 44 former = tmp_former + latter 45 concatenated = True 46 elif not former_matching_rule and latter_matching_rule and latter_match_obj: 47 tmp_latter = latter_match_obj.group("result") if remove_latter_matched else latter 48 former += tmp_latter 49 concatenated = True 50 else: 51 yield former 52 former = latter 53 concatenated = False 54 55 yield former 56 except StopIteration: 57 pass 58 59 60@overload 61def concatenate_matching( 62 arg: str, 63 former_matching_rule: Optional[str] = None, 64 latter_matching_rule: Optional[str] = None, 65 remove_former_matched: bool = True, 66 remove_latter_matched: bool = True, 67 max_concatenate_length: Optional[int] = None, 68) -> Generator[str, None, None]: ... 69 70 71@overload 72def concatenate_matching( 73 arg: list[str], 74 former_matching_rule: Optional[str] = None, 75 latter_matching_rule: Optional[str] = None, 76 remove_former_matched: bool = True, 77 remove_latter_matched: bool = True, 78 max_concatenate_length: Optional[int] = None, 79) -> Generator[str, None, None]: ... 80 81 82@overload 83def concatenate_matching( 84 arg: Iterator[str], 85 former_matching_rule: Optional[str] = None, 86 latter_matching_rule: Optional[str] = None, 87 remove_former_matched: bool = True, 88 remove_latter_matched: bool = True, 89 max_concatenate_length: Optional[int] = None, 90) -> Generator[str, None, None]: ... 91 92 93def concatenate_matching( 94 arg: Union[str, list[str], Iterator[str]], 95 former_matching_rule: Optional[str] = None, 96 latter_matching_rule: Optional[str] = None, 97 remove_former_matched: bool = True, 98 remove_latter_matched: bool = True, 99 max_concatenate_length: Optional[int] = None, 100) -> Generator[str, None, None]: 101 r"""Concatenate two lines with regular expression rule. 102 103 Parameters 104 ---------- 105 arg : Union[str, list[str], Iterator[str]] 106 texts you want to concatenate. 107 a str is treated as one indivisible line and is never split, so it has 108 nothing to be concatenated with and comes back unchanged. split it into 109 lines yourself first -- `split_newline` does this -- and pass the 110 result here. 111 former_matching_rule : Optional[str], optional 112 regular expression for former line, by default None 113 latter_matching_rule : Optional[str], optional 114 regular expression for latter line, by default None 115 remove_former_matched : bool, optional 116 whether to remove matched place of former line, by default True. 117 if this is True, former_matching_rule must contain named group 'result', 118 only that group remains. 119 e.g. r"^(\s*[>]+\s*)(?P<result>.+)$" 120 remove_latter_matched : bool, optional 121 whether to remove matched place of latter line, by default True. 122 if this is True, latter_matching_rule must contain named group 'result', 123 only that group remains. 124 e.g. r"^(\s*[>]+\s*)(?P<result>.+)$" 125 max_concatenate_length : Optional[int], optional 126 soft upper bound on the length of an accumulation, by default None, 127 meaning no bound. must be positive if not None. 128 set it when segmenting untrusted text, and see the notes below for 129 what it costs. leaving it None keeps the unbounded behaviour. 130 131 Yields 132 ------ 133 Generator[str, None, None] 134 concatenated texts. 135 136 Raises 137 ------ 138 TypeError 139 if arg is not a str, a list or an Iterator. 140 ValueError 141 if max_concatenate_length is not None and not positive. 142 both are raised on the first iteration rather than at call time, 143 because this is a generator function. 144 145 Notes 146 ----- 147 Why max_concatenate_length exists: without a bound, former_matching_rule 148 is re-applied to an ever growing accumulation and the accumulation itself 149 is rebuilt on every input line. both costs grow with the accumulated 150 length, so the total work is quadratic in the size of the input. 151 152 What setting it costs: once an accumulation reaches the bound it is 153 yielded as is and a new accumulation starts. no text is lost and no 154 exception is raised, but the output differs from an unbounded run by more 155 than where it is split. 156 157 - the bound is soft. it is only checked after an accumulation has been 158 concatenated at least once, so former_matching_rule is applied at least 159 once per accumulation even when the first line already exceeds the 160 bound. an accumulation may therefore exceed the bound by the length of 161 the line that started it plus one more line. 162 - neither matching rule is evaluated at a bound boundary, so text that 163 remove_former_matched or remove_latter_matched would have stripped 164 survives into the output there. 165 - an accumulation may be broken between an opening bracket and its 166 closing one, which stops split_punctuation from protecting the 167 punctuation inside it. 168 """ 169 if max_concatenate_length is not None and max_concatenate_length <= 0: 170 msg = f"max_concatenate_length must be positive or None, got {max_concatenate_length}" 171 raise ValueError(msg) 172 173 if isinstance(arg, str): 174 yield from __concatenate_matching_iter( 175 iter([arg]), former_matching_rule, latter_matching_rule, remove_former_matched, remove_latter_matched, max_concatenate_length 176 ) 177 elif isinstance(arg, list): 178 yield from __concatenate_matching_iter( 179 iter(arg), former_matching_rule, latter_matching_rule, remove_former_matched, remove_latter_matched, max_concatenate_length 180 ) 181 elif isinstance(arg, Iterator): 182 yield from __concatenate_matching_iter( 183 arg, former_matching_rule, latter_matching_rule, remove_former_matched, remove_latter_matched, max_concatenate_length 184 ) 185 else: 186 # 静的には到達しないが、型注釈のない呼び出しが tuple や set を渡したときに 187 # 黙って空を返さないための実行時ガード。 188 msg = f"arg must be a str, a list or an Iterator, got {type(arg).__name__}" # type: ignore[unreachable] 189 raise TypeError(msg)
def
concatenate_matching( arg: Union[str, list[str], Iterator[str]], former_matching_rule: Optional[str] = None, latter_matching_rule: Optional[str] = None, remove_former_matched: bool = True, remove_latter_matched: bool = True, max_concatenate_length: Optional[int] = None) -> Generator[str, None, None]:
94def concatenate_matching( 95 arg: Union[str, list[str], Iterator[str]], 96 former_matching_rule: Optional[str] = None, 97 latter_matching_rule: Optional[str] = None, 98 remove_former_matched: bool = True, 99 remove_latter_matched: bool = True, 100 max_concatenate_length: Optional[int] = None, 101) -> Generator[str, None, None]: 102 r"""Concatenate two lines with regular expression rule. 103 104 Parameters 105 ---------- 106 arg : Union[str, list[str], Iterator[str]] 107 texts you want to concatenate. 108 a str is treated as one indivisible line and is never split, so it has 109 nothing to be concatenated with and comes back unchanged. split it into 110 lines yourself first -- `split_newline` does this -- and pass the 111 result here. 112 former_matching_rule : Optional[str], optional 113 regular expression for former line, by default None 114 latter_matching_rule : Optional[str], optional 115 regular expression for latter line, by default None 116 remove_former_matched : bool, optional 117 whether to remove matched place of former line, by default True. 118 if this is True, former_matching_rule must contain named group 'result', 119 only that group remains. 120 e.g. r"^(\s*[>]+\s*)(?P<result>.+)$" 121 remove_latter_matched : bool, optional 122 whether to remove matched place of latter line, by default True. 123 if this is True, latter_matching_rule must contain named group 'result', 124 only that group remains. 125 e.g. r"^(\s*[>]+\s*)(?P<result>.+)$" 126 max_concatenate_length : Optional[int], optional 127 soft upper bound on the length of an accumulation, by default None, 128 meaning no bound. must be positive if not None. 129 set it when segmenting untrusted text, and see the notes below for 130 what it costs. leaving it None keeps the unbounded behaviour. 131 132 Yields 133 ------ 134 Generator[str, None, None] 135 concatenated texts. 136 137 Raises 138 ------ 139 TypeError 140 if arg is not a str, a list or an Iterator. 141 ValueError 142 if max_concatenate_length is not None and not positive. 143 both are raised on the first iteration rather than at call time, 144 because this is a generator function. 145 146 Notes 147 ----- 148 Why max_concatenate_length exists: without a bound, former_matching_rule 149 is re-applied to an ever growing accumulation and the accumulation itself 150 is rebuilt on every input line. both costs grow with the accumulated 151 length, so the total work is quadratic in the size of the input. 152 153 What setting it costs: once an accumulation reaches the bound it is 154 yielded as is and a new accumulation starts. no text is lost and no 155 exception is raised, but the output differs from an unbounded run by more 156 than where it is split. 157 158 - the bound is soft. it is only checked after an accumulation has been 159 concatenated at least once, so former_matching_rule is applied at least 160 once per accumulation even when the first line already exceeds the 161 bound. an accumulation may therefore exceed the bound by the length of 162 the line that started it plus one more line. 163 - neither matching rule is evaluated at a bound boundary, so text that 164 remove_former_matched or remove_latter_matched would have stripped 165 survives into the output there. 166 - an accumulation may be broken between an opening bracket and its 167 closing one, which stops split_punctuation from protecting the 168 punctuation inside it. 169 """ 170 if max_concatenate_length is not None and max_concatenate_length <= 0: 171 msg = f"max_concatenate_length must be positive or None, got {max_concatenate_length}" 172 raise ValueError(msg) 173 174 if isinstance(arg, str): 175 yield from __concatenate_matching_iter( 176 iter([arg]), former_matching_rule, latter_matching_rule, remove_former_matched, remove_latter_matched, max_concatenate_length 177 ) 178 elif isinstance(arg, list): 179 yield from __concatenate_matching_iter( 180 iter(arg), former_matching_rule, latter_matching_rule, remove_former_matched, remove_latter_matched, max_concatenate_length 181 ) 182 elif isinstance(arg, Iterator): 183 yield from __concatenate_matching_iter( 184 arg, former_matching_rule, latter_matching_rule, remove_former_matched, remove_latter_matched, max_concatenate_length 185 ) 186 else: 187 # 静的には到達しないが、型注釈のない呼び出しが tuple や set を渡したときに 188 # 黙って空を返さないための実行時ガード。 189 msg = f"arg must be a str, a list or an Iterator, got {type(arg).__name__}" # type: ignore[unreachable] 190 raise TypeError(msg)
Concatenate two lines with regular expression rule.
Parameters
- arg (Union[str, list[str], Iterator[str]]):
texts you want to concatenate.
a str is treated as one indivisible line and is never split, so it has
nothing to be concatenated with and comes back unchanged. split it into
lines yourself first --
split_newlinedoes this -- and pass the result here. - former_matching_rule (Optional[str], optional): regular expression for former line, by default None
- latter_matching_rule (Optional[str], optional): regular expression for latter line, by default None
- remove_former_matched (bool, optional):
whether to remove matched place of former line, by default True.
if this is True, former_matching_rule must contain named group 'result',
only that group remains.
e.g. r"^(\s*[>]+\s*)(?P
.+)$" - remove_latter_matched (bool, optional):
whether to remove matched place of latter line, by default True.
if this is True, latter_matching_rule must contain named group 'result',
only that group remains.
e.g. r"^(\s*[>]+\s*)(?P
.+)$" - max_concatenate_length (Optional[int], optional): soft upper bound on the length of an accumulation, by default None, meaning no bound. must be positive if not None. set it when segmenting untrusted text, and see the notes below for what it costs. leaving it None keeps the unbounded behaviour.
Yields
- Generator[str, None, None]: concatenated texts.
Raises
- TypeError: if arg is not a str, a list or an Iterator.
- ValueError: if max_concatenate_length is not None and not positive. both are raised on the first iteration rather than at call time, because this is a generator function.
Notes
Why max_concatenate_length exists: without a bound, former_matching_rule is re-applied to an ever growing accumulation and the accumulation itself is rebuilt on every input line. both costs grow with the accumulated length, so the total work is quadratic in the size of the input.
What setting it costs: once an accumulation reaches the bound it is yielded as is and a new accumulation starts. no text is lost and no exception is raised, but the output differs from an unbounded run by more than where it is split.
- the bound is soft. it is only checked after an accumulation has been concatenated at least once, so former_matching_rule is applied at least once per accumulation even when the first line already exceeds the bound. an accumulation may therefore exceed the bound by the length of the line that started it plus one more line.
- neither matching rule is evaluated at a bound boundary, so text that remove_former_matched or remove_latter_matched would have stripped survives into the output there.
- an accumulation may be broken between an opening bracket and its closing one, which stops split_punctuation from protecting the punctuation inside it.