Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
-
# verify_bug2.py
|
| 2 |
# 実行方法: python verify_bug2.py
|
| 3 |
-
# 前提: app.py と同じディレクトリに配置して実行
|
| 4 |
|
| 5 |
import re
|
| 6 |
import difflib
|
|
@@ -144,12 +143,6 @@ def build_active_text_and_map(original, flags):
|
|
| 144 |
index_map = [i for i, _ in active_chars]
|
| 145 |
return active_text, index_map
|
| 146 |
|
| 147 |
-
def build_removed_text_and_map(original, flags):
|
| 148 |
-
removed_chars = [(i, ch) for i, ch in enumerate(original) if not flags[i]]
|
| 149 |
-
removed_text = ''.join(ch for _, ch in removed_chars)
|
| 150 |
-
index_map = [i for i, _ in removed_chars]
|
| 151 |
-
return removed_text, index_map
|
| 152 |
-
|
| 153 |
def tokenize(text):
|
| 154 |
pattern = re.compile(
|
| 155 |
r'[a-zA-ZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+|'
|
|
@@ -195,7 +188,7 @@ def compare_texts(src, dst, settings):
|
|
| 195 |
|
| 196 |
if src_active != '' or dst_active != '':
|
| 197 |
# ---- トークン単位で比較 ----
|
| 198 |
-
src_tokens = tokenize(src_active)
|
| 199 |
dst_tokens = tokenize(dst_active)
|
| 200 |
|
| 201 |
src_token_strs = [t[0] for t in src_tokens]
|
|
@@ -204,32 +197,21 @@ def compare_texts(src, dst, settings):
|
|
| 204 |
matcher = difflib.SequenceMatcher(None, src_token_strs, dst_token_strs, autojunk=False)
|
| 205 |
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
| 206 |
if tag in ('replace', 'delete') and i2 > i1:
|
| 207 |
-
#
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
|
|
|
| 212 |
src_diff_ranges.append((orig_start, orig_end))
|
| 213 |
if tag in ('replace', 'insert') and j2 > j1:
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
orig_start = dst_map[
|
| 217 |
-
orig_end = dst_map[
|
| 218 |
dst_diff_ranges.append((orig_start, orig_end))
|
| 219 |
|
| 220 |
-
# 除去文字の比較
|
| 221 |
-
src_removed, src_removed_map = build_removed_text_and_map(src, src_flags)
|
| 222 |
-
dst_removed, dst_removed_map = build_removed_text_and_map(dst, dst_flags)
|
| 223 |
-
|
| 224 |
-
if src_removed != dst_removed and (src_active != '' or dst_active != ''):
|
| 225 |
-
rm_matcher = difflib.SequenceMatcher(None, src_removed, dst_removed, autojunk=False)
|
| 226 |
-
for tag, i1, i2, j1, j2 in rm_matcher.get_opcodes():
|
| 227 |
-
if tag in ('replace', 'delete') and i2 > i1:
|
| 228 |
-
for k in range(i1, i2):
|
| 229 |
-
src_diff_ranges.append((src_removed_map[k], src_removed_map[k] + 1))
|
| 230 |
-
if tag in ('replace', 'insert') and j2 > j1:
|
| 231 |
-
for k in range(j1, j2):
|
| 232 |
-
dst_diff_ranges.append((dst_removed_map[k], dst_removed_map[k] + 1))
|
| 233 |
|
| 234 |
has_diff = bool(src_diff_ranges or dst_diff_ranges)
|
| 235 |
|
|
@@ -261,11 +243,10 @@ def compare_texts(src, dst, settings):
|
|
| 261 |
return src_html, dst_html, has_diff
|
| 262 |
|
| 263 |
# ============================================================
|
| 264 |
-
#
|
| 265 |
# ============================================================
|
| 266 |
|
| 267 |
def extract_highlighted(text, html_result):
|
| 268 |
-
"""HTMLからハイライトされた文字列リストを抽出"""
|
| 269 |
pattern = re.compile(r'<span style="[^"]*">(.+?)</span>', re.DOTALL)
|
| 270 |
results = []
|
| 271 |
for m in pattern.finditer(html_result):
|
|
@@ -275,7 +256,6 @@ def extract_highlighted(text, html_result):
|
|
| 275 |
|
| 276 |
def run_test(name, src, dst, settings, expect_src_hl, expect_dst_hl, expect_has_diff):
|
| 277 |
src_html, dst_html, has_diff = compare_texts(src, dst, settings)
|
| 278 |
-
|
| 279 |
src_hl = extract_highlighted(src, src_html)
|
| 280 |
dst_hl = extract_highlighted(dst, dst_html)
|
| 281 |
|
|
@@ -295,7 +275,7 @@ def run_test(name, src, dst, settings, expect_src_hl, expect_dst_hl, expect_has_
|
|
| 295 |
if not ok_diff:
|
| 296 |
print(f" [has_diff] 期待: {expect_has_diff} / 実際: {has_diff}")
|
| 297 |
|
| 298 |
-
OFF = {}
|
| 299 |
ALL_ON = {
|
| 300 |
'ignore_alpha': True, 'ignore_digit': True, 'ignore_kana': True,
|
| 301 |
'ignore_yakumono': True, 'ignore_ligature': True, 'ignore_kangxi': True,
|
|
@@ -309,217 +289,93 @@ ALL_ON = {
|
|
| 309 |
# ============================================================
|
| 310 |
|
| 311 |
print("=" * 60)
|
| 312 |
-
print("バグ2 検証スクリプト")
|
| 313 |
print("=" * 60)
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
"
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
run_test(
|
| 339 |
-
"
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
)
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
"
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
run_test(
|
| 383 |
-
"
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
expect_dst_hl=[],
|
| 400 |
-
expect_has_diff=False,
|
| 401 |
-
)
|
| 402 |
-
|
| 403 |
-
# TEST-21 スペース削除 設定オフ(スペースが差分)
|
| 404 |
-
run_test(
|
| 405 |
-
"TEST-21 スペース削除 設定オフ",
|
| 406 |
-
src="abc def ghi",
|
| 407 |
-
dst="abcdefghi",
|
| 408 |
-
settings=OFF,
|
| 409 |
-
expect_src_hl=[" ", " "],
|
| 410 |
-
expect_dst_hl=[],
|
| 411 |
-
expect_has_diff=True,
|
| 412 |
-
)
|
| 413 |
-
|
| 414 |
-
# TEST-22 改行削除
|
| 415 |
-
run_test(
|
| 416 |
-
"TEST-22 改行削除",
|
| 417 |
-
src="abc\ndef\nghi",
|
| 418 |
-
dst="abcdefghi",
|
| 419 |
-
settings={'ignore_newline': True},
|
| 420 |
-
expect_src_hl=[],
|
| 421 |
-
expect_dst_hl=[],
|
| 422 |
-
expect_has_diff=False,
|
| 423 |
-
)
|
| 424 |
-
|
| 425 |
-
# TEST-23 約物と括弧付き文字両方
|
| 426 |
-
run_test(
|
| 427 |
-
"TEST-23 約物と括弧付き文字両方",
|
| 428 |
-
src="吾輩は(猫)である。名前は【まだ】ない!",
|
| 429 |
-
dst="吾輩はである。名前はない!",
|
| 430 |
-
settings={'ignore_yakumono': True, 'ignore_brackets_with_content': True},
|
| 431 |
-
expect_src_hl=[],
|
| 432 |
-
expect_dst_hl=[],
|
| 433 |
-
expect_has_diff=False,
|
| 434 |
-
)
|
| 435 |
-
|
| 436 |
-
# TEST-25 全設定オフ(バグ2メイン修正対象)
|
| 437 |
-
run_test(
|
| 438 |
-
"TEST-25 全設定オフ",
|
| 439 |
-
src="abc(def)123アイウ〔ghi〕fi\u2F00zero-test \nabc",
|
| 440 |
-
dst="xyz(www)789カキクケコ〔jkl〕fl\u2F01one+test \nxyz",
|
| 441 |
-
settings=OFF,
|
| 442 |
-
expect_src_hl=["abc", "def", "123", "アイウ", "ghi", "fi", "\u2F00", "zero"],
|
| 443 |
-
expect_dst_hl=["xyz", "www", "789", "カキクケコ", "jkl", "fl", "\u2F01", "one", "+"],
|
| 444 |
-
expect_has_diff=True,
|
| 445 |
-
)
|
| 446 |
-
|
| 447 |
-
# TEST-25 全設定オン
|
| 448 |
-
run_test(
|
| 449 |
-
"TEST-25 全設定オン",
|
| 450 |
-
src="abc(def)123アイウ〔ghi〕fi\u2F00zero-test \nabc",
|
| 451 |
-
dst="xyz(www)789カキクケコ〔jkl〕fl\u2F01one+test \nxyz",
|
| 452 |
-
settings=ALL_ON,
|
| 453 |
-
expect_src_hl=[],
|
| 454 |
-
expect_dst_hl=[],
|
| 455 |
-
expect_has_diff=False,
|
| 456 |
-
)
|
| 457 |
-
|
| 458 |
-
# TEST-30 スペースのみ相違 設定オフ
|
| 459 |
-
run_test(
|
| 460 |
-
"TEST-30 スペースのみ相違 設定オフ",
|
| 461 |
-
src="abc def",
|
| 462 |
-
dst="abcdef",
|
| 463 |
-
settings=OFF,
|
| 464 |
-
expect_src_hl=[" "],
|
| 465 |
-
expect_dst_hl=[],
|
| 466 |
-
expect_has_diff=True,
|
| 467 |
-
)
|
| 468 |
-
|
| 469 |
-
# TEST-35 比較元削除箇所ハイライト
|
| 470 |
-
run_test(
|
| 471 |
-
"TEST-35 比較元削除箇所ハイライト",
|
| 472 |
-
src="abcXYZdef",
|
| 473 |
-
dst="abcdef",
|
| 474 |
-
settings=OFF,
|
| 475 |
-
expect_src_hl=["XYZ"],
|
| 476 |
-
expect_dst_hl=[],
|
| 477 |
-
expect_has_diff=True,
|
| 478 |
-
)
|
| 479 |
-
|
| 480 |
-
# TEST-37 ハイフン全種
|
| 481 |
-
run_test(
|
| 482 |
-
"TEST-37 ハイフン全種",
|
| 483 |
-
src="abc-def\uFF0Dghi\u2010jkl\u2011mno\u2013pqr\u2014stu",
|
| 484 |
-
dst="abcdefghijklmnopqrstu",
|
| 485 |
-
settings={'ignore_hyphen': True},
|
| 486 |
-
expect_src_hl=[],
|
| 487 |
-
expect_dst_hl=[],
|
| 488 |
-
expect_has_diff=False,
|
| 489 |
-
)
|
| 490 |
-
|
| 491 |
-
# TEST-27 空文字
|
| 492 |
-
run_test(
|
| 493 |
-
"TEST-27 空文字",
|
| 494 |
-
src="",
|
| 495 |
-
dst="",
|
| 496 |
-
settings=OFF,
|
| 497 |
-
expect_src_hl=[],
|
| 498 |
-
expect_dst_hl=[],
|
| 499 |
-
expect_has_diff=False,
|
| 500 |
-
)
|
| 501 |
-
|
| 502 |
-
# TEST-28 片方空文字
|
| 503 |
-
run_test(
|
| 504 |
-
"TEST-28 片方空文字",
|
| 505 |
-
src="abcdef",
|
| 506 |
-
dst="",
|
| 507 |
-
settings=OFF,
|
| 508 |
-
expect_src_hl=["abcdef"],
|
| 509 |
-
expect_dst_hl=[],
|
| 510 |
-
expect_has_diff=True,
|
| 511 |
-
)
|
| 512 |
-
|
| 513 |
-
# TEST-29 改行のみ相違 設定オフ
|
| 514 |
-
run_test(
|
| 515 |
-
"TEST-29 改行のみ相違 設定オフ",
|
| 516 |
-
src="\n\n",
|
| 517 |
-
dst="\n",
|
| 518 |
-
settings=OFF,
|
| 519 |
-
expect_src_hl=["\n"],
|
| 520 |
-
expect_dst_hl=[],
|
| 521 |
-
expect_has_diff=True,
|
| 522 |
-
)
|
| 523 |
|
| 524 |
print("=" * 60)
|
| 525 |
print("完了")
|
|
|
|
| 1 |
+
# verify_bug2.py ver2
|
| 2 |
# 実行方法: python verify_bug2.py
|
|
|
|
| 3 |
|
| 4 |
import re
|
| 5 |
import difflib
|
|
|
|
| 143 |
index_map = [i for i, _ in active_chars]
|
| 144 |
return active_text, index_map
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
def tokenize(text):
|
| 147 |
pattern = re.compile(
|
| 148 |
r'[a-zA-ZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]+|'
|
|
|
|
| 188 |
|
| 189 |
if src_active != '' or dst_active != '':
|
| 190 |
# ---- トークン単位で比較 ----
|
| 191 |
+
src_tokens = tokenize(src_active)
|
| 192 |
dst_tokens = tokenize(dst_active)
|
| 193 |
|
| 194 |
src_token_strs = [t[0] for t in src_tokens]
|
|
|
|
| 197 |
matcher = difflib.SequenceMatcher(None, src_token_strs, dst_token_strs, autojunk=False)
|
| 198 |
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
| 199 |
if tag in ('replace', 'delete') and i2 > i1:
|
| 200 |
+
# active_text内の開始・終了位置
|
| 201 |
+
a_start = src_tokens[i1][1]
|
| 202 |
+
a_end = src_tokens[i2-1][1] + len(src_tokens[i2-1][0])
|
| 203 |
+
# src_map で元テキスト位置に変換
|
| 204 |
+
orig_start = src_map[a_start]
|
| 205 |
+
orig_end = src_map[a_end - 1] + 1
|
| 206 |
src_diff_ranges.append((orig_start, orig_end))
|
| 207 |
if tag in ('replace', 'insert') and j2 > j1:
|
| 208 |
+
a_start = dst_tokens[j1][1]
|
| 209 |
+
a_end = dst_tokens[j2-1][1] + len(dst_tokens[j2-1][0])
|
| 210 |
+
orig_start = dst_map[a_start]
|
| 211 |
+
orig_end = dst_map[a_end - 1] + 1
|
| 212 |
dst_diff_ranges.append((orig_start, orig_end))
|
| 213 |
|
| 214 |
+
# ※除去文字の比較は行わない(無視設定した文字はハイライト対象外)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
has_diff = bool(src_diff_ranges or dst_diff_ranges)
|
| 217 |
|
|
|
|
| 243 |
return src_html, dst_html, has_diff
|
| 244 |
|
| 245 |
# ============================================================
|
| 246 |
+
# ヘルパー・テスト実行関数
|
| 247 |
# ============================================================
|
| 248 |
|
| 249 |
def extract_highlighted(text, html_result):
|
|
|
|
| 250 |
pattern = re.compile(r'<span style="[^"]*">(.+?)</span>', re.DOTALL)
|
| 251 |
results = []
|
| 252 |
for m in pattern.finditer(html_result):
|
|
|
|
| 256 |
|
| 257 |
def run_test(name, src, dst, settings, expect_src_hl, expect_dst_hl, expect_has_diff):
|
| 258 |
src_html, dst_html, has_diff = compare_texts(src, dst, settings)
|
|
|
|
| 259 |
src_hl = extract_highlighted(src, src_html)
|
| 260 |
dst_hl = extract_highlighted(dst, dst_html)
|
| 261 |
|
|
|
|
| 275 |
if not ok_diff:
|
| 276 |
print(f" [has_diff] 期待: {expect_has_diff} / 実際: {has_diff}")
|
| 277 |
|
| 278 |
+
OFF = {}
|
| 279 |
ALL_ON = {
|
| 280 |
'ignore_alpha': True, 'ignore_digit': True, 'ignore_kana': True,
|
| 281 |
'ignore_yakumono': True, 'ignore_ligature': True, 'ignore_kangxi': True,
|
|
|
|
| 289 |
# ============================================================
|
| 290 |
|
| 291 |
print("=" * 60)
|
| 292 |
+
print("バグ2 検証スクリプト ver2")
|
| 293 |
print("=" * 60)
|
| 294 |
|
| 295 |
+
run_test("TEST-01 相違なし",
|
| 296 |
+
"吾輩は猫である。名前はまだない。",
|
| 297 |
+
"吾輩は猫である。名前はまだない。",
|
| 298 |
+
OFF, [], [], False)
|
| 299 |
+
|
| 300 |
+
run_test("TEST-02 相違あり基本",
|
| 301 |
+
"吾輩は猫である。",
|
| 302 |
+
"吾輩は犬である。",
|
| 303 |
+
OFF, ["猫"], ["犬"], True)
|
| 304 |
+
|
| 305 |
+
run_test("TEST-03 比較先に追加",
|
| 306 |
+
"abcdef", "abcXYZdef",
|
| 307 |
+
OFF, [], ["XYZ"], True)
|
| 308 |
+
|
| 309 |
+
run_test("TEST-04 比較元に削除",
|
| 310 |
+
"abcXYZdef", "abcdef",
|
| 311 |
+
OFF, ["XYZ"], [], True)
|
| 312 |
+
|
| 313 |
+
run_test("TEST-08 約物全半角無視",
|
| 314 |
+
"吾輩は、猫である。「名前」は(まだ)ない!",
|
| 315 |
+
"吾輩は猫である名前はまだない",
|
| 316 |
+
{'ignore_yakumono': True}, [], [], False)
|
| 317 |
+
|
| 318 |
+
run_test("TEST-09 約物括弧の中身は残る",
|
| 319 |
+
"(abc)テスト", "abcテスト",
|
| 320 |
+
{'ignore_yakumono': True}, [], [], False)
|
| 321 |
+
|
| 322 |
+
run_test("TEST-19 ハイフン無視",
|
| 323 |
+
"abc-def\uFF0Dghi\u2010jkl\u2011mno\u2013pqr\u2014stu",
|
| 324 |
+
"abcdefghijklmnopqrstu",
|
| 325 |
+
{'ignore_hyphen': True}, [], [], False)
|
| 326 |
+
|
| 327 |
+
run_test("TEST-21 スペース削除 設定オン",
|
| 328 |
+
"abc def ghi", "abcdefghi",
|
| 329 |
+
{'ignore_space': True}, [], [], False)
|
| 330 |
+
|
| 331 |
+
run_test("TEST-21 スペース削除 設定オフ",
|
| 332 |
+
"abc def ghi", "abcdefghi",
|
| 333 |
+
OFF, [" ", " "], [], True)
|
| 334 |
+
|
| 335 |
+
run_test("TEST-22 改行削除 設定オン",
|
| 336 |
+
"abc\ndef\nghi", "abcdefghi",
|
| 337 |
+
{'ignore_newline': True}, [], [], False)
|
| 338 |
+
|
| 339 |
+
run_test("TEST-23 約物と括弧付き文字両方",
|
| 340 |
+
"吾輩は(猫)である。名前は【まだ】ない!",
|
| 341 |
+
"吾輩はである。名前はない!",
|
| 342 |
+
{'ignore_yakumono': True, 'ignore_brackets_with_content': True},
|
| 343 |
+
[], [], False)
|
| 344 |
+
|
| 345 |
+
run_test("TEST-25 全設定オフ",
|
| 346 |
+
"abc(def)123アイウ〔ghi〕fi\u2F00zero-test \nabc",
|
| 347 |
+
"xyz(www)789カキクケコ〔jkl〕fl\u2F01one+test \nxyz",
|
| 348 |
+
OFF,
|
| 349 |
+
["abc", "def", "123", "アイウ", "ghi", "fi", "\u2F00", "zero"],
|
| 350 |
+
["xyz", "www", "789", "カキクケコ", "jkl", "fl", "\u2F01", "one", "+"],
|
| 351 |
+
True)
|
| 352 |
+
|
| 353 |
+
run_test("TEST-25 全設定オン",
|
| 354 |
+
"abc(def)123アイウ〔ghi〕fi\u2F00zero-test \nabc",
|
| 355 |
+
"xyz(www)789カキクケコ〔jkl〕fl\u2F01one+test \nxyz",
|
| 356 |
+
ALL_ON, [], [], False)
|
| 357 |
+
|
| 358 |
+
run_test("TEST-30 スペースのみ相違 設定オフ",
|
| 359 |
+
"abc def", "abcdef",
|
| 360 |
+
OFF, [" "], [], True)
|
| 361 |
+
|
| 362 |
+
run_test("TEST-35 比較元削除箇所ハイライト",
|
| 363 |
+
"abcXYZdef", "abcdef",
|
| 364 |
+
OFF, ["XYZ"], [], True)
|
| 365 |
+
|
| 366 |
+
run_test("TEST-37 ハイフン全種 設定オン",
|
| 367 |
+
"abc-def\uFF0Dghi\u2010jkl\u2011mno\u2013pqr\u2014stu",
|
| 368 |
+
"abcdefghijklmnopqrstu",
|
| 369 |
+
{'ignore_hyphen': True}, [], [], False)
|
| 370 |
+
|
| 371 |
+
run_test("TEST-27 空文字",
|
| 372 |
+
"", "", OFF, [], [], False)
|
| 373 |
+
|
| 374 |
+
run_test("TEST-28 片方空文字",
|
| 375 |
+
"abcdef", "", OFF, ["abcdef"], [], True)
|
| 376 |
+
|
| 377 |
+
run_test("TEST-29 改行のみ相違 設定オフ",
|
| 378 |
+
"\n\n", "\n", OFF, ["\n"], [], True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
|
| 380 |
print("=" * 60)
|
| 381 |
print("完了")
|