Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/chardet/pipeline/postprocess.py: 20%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

197 statements  

1"""Stage 13: post-processing rank corrections. 

2 

3After statistical scoring produces a ranked list of candidates, a chain 

4of rank corrections fixes up the ranking when bigrams alone are 

5insufficient — see :func:`postprocess_results` for the order. The steps: 

6dead-heat priors (superset preference, era prevalence), rare-language 

7arbitration (ADR-0005), confusion-group resolution (delegated to 

8:mod:`chardet.pipeline.confusion`), niche Latin demotion, KOI8-T 

9promotion, classic-Mac line-ending promotion, and last of all the 

10decode-safety flip, which hands a winner whose only multi-byte evidence 

11is an undecodable trailing sequence to the best rival that can decode 

12the caller's complete input. 

13 

14Note: ``from __future__ import annotations`` is intentionally omitted because 

15this module is compiled with mypyc, which does not support PEP 563 string 

16annotations. 

17""" 

18 

19from chardet._utils import ( 

20 dangling_tail_with_ascii_prefix, 

21 decodes_without_error, 

22) 

23from chardet.models import ART_LANGUAGE, RARE_LANGUAGES, get_enc_index 

24from chardet.output_names import decodes_under_public_name 

25from chardet.pipeline import DetectionResult 

26from chardet.pipeline.confusion import ( 

27 CONFUSION_BAND, 

28 CONFUSION_FLOOR_RATIO, 

29 STRICT_TIER_MAX_CONF, 

30 _comparable_languages, 

31 arbitrate_distinguishing_bytes, 

32 confusion_pair_winner, 

33 differing_high_bytes, 

34 resolve_confusion_groups, 

35) 

36from chardet.registry import CJK_SUPERSETS, REGISTRY 

37 

38# Common Western Latin encodings that share the iso-8859-1 character 

39# repertoire for the byte values where iso-8859-10 is indistinguishable. 

40# Used as swap targets when demoting iso-8859-10 — we prefer these over 

41# iso-8859-10, but do not want to accidentally promote an unrelated encoding 

42# (e.g. windows-1254). 

43_COMMON_LATIN_ENCODINGS: frozenset[str] = frozenset( 

44 { 

45 "iso8859-1", 

46 "iso8859-15", 

47 "cp1252", 

48 } 

49) 

50 

51# Bytes where iso-8859-10 decodes to a different character than iso-8859-1. 

52# Computed programmatically via: 

53# {b for b in range(0x80, 0x100) 

54# if bytes([b]).decode('iso-8859-10') != bytes([b]).decode('iso-8859-1')} 

55_ISO_8859_10_DISTINGUISHING: frozenset[int] = frozenset( 

56 { 

57 0xA1, 

58 0xA2, 

59 0xA3, 

60 0xA4, 

61 0xA5, 

62 0xA6, 

63 0xA8, 

64 0xA9, 

65 0xAA, 

66 0xAB, 

67 0xAC, 

68 0xAE, 

69 0xAF, 

70 0xB1, 

71 0xB2, 

72 0xB3, 

73 0xB4, 

74 0xB5, 

75 0xB6, 

76 0xB8, 

77 0xB9, 

78 0xBA, 

79 0xBB, 

80 0xBC, 

81 0xBD, 

82 0xBE, 

83 0xBF, 

84 0xC0, 

85 0xC7, 

86 0xC8, 

87 0xCA, 

88 0xCC, 

89 0xD1, 

90 0xD2, 

91 0xD7, 

92 0xD9, 

93 0xE0, 

94 0xE7, 

95 0xE8, 

96 0xEA, 

97 0xEC, 

98 0xF1, 

99 0xF2, 

100 0xF7, 

101 0xF9, 

102 0xFF, 

103 } 

104) 

105 

106# Bytes where iso-8859-14 decodes to a different character than iso-8859-1. 

107# Computed programmatically via: 

108# {b for b in range(0x80, 0x100) 

109# if bytes([b]).decode('iso-8859-14') != bytes([b]).decode('iso-8859-1')} 

110_ISO_8859_14_DISTINGUISHING: frozenset[int] = frozenset( 

111 { 

112 0xA1, 

113 0xA2, 

114 0xA4, 

115 0xA5, 

116 0xA6, 

117 0xA8, 

118 0xAA, 

119 0xAB, 

120 0xAC, 

121 0xAF, 

122 0xB0, 

123 0xB1, 

124 0xB2, 

125 0xB3, 

126 0xB4, 

127 0xB5, 

128 0xB7, 

129 0xB8, 

130 0xB9, 

131 0xBA, 

132 0xBB, 

133 0xBC, 

134 0xBD, 

135 0xBE, 

136 0xBF, 

137 0xD0, 

138 0xD7, 

139 0xDE, 

140 0xF0, 

141 0xF7, 

142 0xFE, 

143 } 

144) 

145 

146# Bytes where windows-1254 has Turkish-specific characters that differ from 

147# windows-1252. Windows-1254 differs from windows-1252 at 8 byte positions. 

148# Two (0x8E, 0x9E) are undefined in Windows-1254 but defined in Windows-1252; 

149# these are excluded here because undefined bytes are not useful for 

150# identifying Turkish text. The remaining six positions map to 

151# Turkish-specific letters and are the primary distinguishing signal. 

152_WINDOWS_1254_DISTINGUISHING: frozenset[int] = frozenset( 

153 {0xD0, 0xDD, 0xDE, 0xF0, 0xFD, 0xFE} 

154) 

155 

156# Bytes where HP-Roman8 maps to lowercase accented letters but ISO-8859-1 

157# maps to uppercase letters. Real HP-Roman8 text (from HP-UX terminals) 

158# contains these bytes; data misdetected as HP-Roman8 typically does not. 

159# {b for b in range(0x80, 0x100) 

160# if (unicodedata.category(bytes([b]).decode('hp-roman8')) == 'Ll' 

161# and unicodedata.category(bytes([b]).decode('iso-8859-1')) == 'Lu')} 

162_HP_ROMAN8_DISTINGUISHING: frozenset[int] = frozenset( 

163 { 

164 0xC0, 

165 0xC1, 

166 0xC2, 

167 0xC3, 

168 0xC4, 

169 0xC5, 

170 0xC6, 

171 0xC7, 

172 0xC8, 

173 0xC9, 

174 0xCA, 

175 0xCB, 

176 0xCC, 

177 0xCD, 

178 0xCE, 

179 0xCF, 

180 0xD1, 

181 0xD4, 

182 0xD5, 

183 0xD6, 

184 0xD9, 

185 0xDD, 

186 0xDE, 

187 } 

188) 

189 

190# Encodings that are often false positives when their distinguishing bytes 

191# are absent. Keyed by encoding name -> frozenset of byte values where 

192# that encoding differs from iso-8859-1 (or windows-1252 in the case of 

193# windows-1254). 

194_DEMOTION_CANDIDATES: dict[str, frozenset[int]] = { 

195 "iso8859-10": _ISO_8859_10_DISTINGUISHING, 

196 "iso8859-14": _ISO_8859_14_DISTINGUISHING, 

197 "cp1254": _WINDOWS_1254_DISTINGUISHING, 

198 "hp-roman8": _HP_ROMAN8_DISTINGUISHING, 

199} 

200 

201# Bytes where KOI8-T maps to Tajik-specific Cyrillic letters but KOI8-R 

202# maps to box-drawing characters. Presence of any of these bytes is strong 

203# evidence for KOI8-T over KOI8-R. 

204_KOI8_T_DISTINGUISHING: frozenset[int] = frozenset( 

205 {0x80, 0x81, 0x83, 0x8A, 0x8C, 0x8D, 0x8E, 0x90, 0xA1, 0xA2, 0xA5, 0xB5} 

206) 

207 

208 

209# Deletion tables for bytes.translate: length changes iff a distinguishing 

210# byte occurs in the data. A translate scan runs at C speed where the 

211# equivalent generator expression iterates the whole (up to max_bytes) input 

212# with boxed set-membership tests. All distinguishing bytes are > 0x7F, so 

213# the old per-byte high-bit filter is subsumed by set membership. 

214_DEMOTION_DELETE: dict[str, bytes] = { 

215 enc: bytes(byte_set) for enc, byte_set in _DEMOTION_CANDIDATES.items() 

216} 

217_KOI8_T_DELETE: bytes = bytes(_KOI8_T_DISTINGUISHING) 

218 

219 

220def _should_demote(data: bytes, top: DetectionResult, target: DetectionResult) -> bool: 

221 """Return True if *top*, a demotion candidate, has no byte evidence over *target*. 

222 

223 Callers guarantee ``top.encoding`` is in :data:`_DEMOTION_CANDIDATES`. 

224 Two questions, cheapest first. Does *data* contain any byte the 

225 candidate decodes differently from ISO-8859-1? If not, the data is 

226 equally valid under both encodings, nothing at the byte level favors 

227 the candidate, and it is demoted. 

228 

229 If such bytes are present, do they favor the candidate? Presence 

230 alone is symmetric evidence: a Windows-1252 file whose only non-ASCII 

231 letter is an ``Ö`` carries 0xD6, which HP-Roman8 reads as ``ø``, so 

232 both candidates "contain" the byte and the question is which reading 

233 holds up. That is a confusion-style arbitration between the candidate 

234 and its swap target on the distinguishing bytes alone (see 

235 :func:`~chardet.pipeline.confusion.arbitrate_distinguishing_bytes`), 

236 each side scored under the variant that actually won its slot. The 

237 models decide when they can: one Welsh ``ŵ`` keeps ISO-8859-14 because 

238 the Welsh model knows that bigram and the Windows-1252 reading is a 

239 ``ð`` no Welsh model has seen. When the models are silent, word shape 

240 decides: a Kven ``đ`` keeps a Finnish file on ISO-8859-10 because a 

241 letter between letters beats the superscript ``¹`` Windows-1252 reads 

242 there. A lone ``Ö`` mid-word decides nothing either way, and the more 

243 prevalent encoding takes the evidence-free tie. 

244 

245 The arbitration is only asked when the candidate's lead over the swap 

246 target is within :data:`~chardet.pipeline.confusion.CONFUSION_BAND`. 

247 A win by more than the band was decided on the full statistics, and 

248 re-litigating it on a handful of bytes is neither sound nor free: the 

249 scan is Python level, and mainstream Turkish text tops as windows-1254 

250 with hundreds of distinguishing bytes and a lead of 0.2. 

251 """ 

252 encoding = top.encoding or "" 

253 if len(data.translate(None, _DEMOTION_DELETE[encoding])) == len(data): 

254 return True 

255 if top.confidence - target.confidence > CONFUSION_BAND: 

256 return False 

257 winner = arbitrate_distinguishing_bytes( 

258 data, 

259 encoding, 

260 target.encoding or "", 

261 _DEMOTION_CANDIDATES[encoding], 

262 languages_a=None if top.language is None else frozenset((top.language,)), 

263 languages_b=None if target.language is None else frozenset((target.language,)), 

264 ) 

265 return winner != encoding 

266 

267 

268def _swap_target(candidates: list[DetectionResult]) -> DetectionResult: 

269 """Pick the common Latin candidate that replaces a demoted top. 

270 

271 Among the candidates within :data:`_DEAD_HEAT_EPSILON` of the 

272 highest-scoring one, era prevalence chooses (windows-1252 over 

273 iso-8859-1): inside that band the confidence order is noise, the very 

274 premise of the demotion. A candidate trailing the best common Latin 

275 by more than the epsilon lost to it on real evidence and stays put. 

276 Equal era ranks (iso-8859-1 against iso-8859-15, both legacy ISO) keep 

277 confidence order, since ``min`` returns the first of equals and the 

278 candidates arrive ranked. 

279 """ 

280 lead_conf = max(r.confidence for r in candidates) 

281 in_band = [r for r in candidates if lead_conf - r.confidence <= _DEAD_HEAT_EPSILON] 

282 return min(in_band, key=lambda r: _era_rank(r.encoding or "")) 

283 

284 

285def _demote_niche_latin( 

286 data: bytes, 

287 results: list[DetectionResult], 

288) -> list[DetectionResult]: 

289 """Demote a niche Latin top that its distinguishing bytes do not support. 

290 

291 Some bigram models (iso-8859-10, iso-8859-14, windows-1254, hp-roman8) 

292 can win on data that contains only bytes shared with the common Western 

293 Latin encodings, or on a lone shared byte the models cannot arbitrate. 

294 When :func:`_should_demote` finds no byte-level evidence for the 

295 winning encoding, promote the swap target :func:`_swap_target` picks 

296 among the common Latin candidates and push the demoted encoding to 

297 last. 

298 

299 The demoted entries take the confidence of the candidate they now sit 

300 behind. Rank position alone does not survive the trip out to callers: 

301 ``detect_all`` re-sorts by confidence, and a stable sort hands an entry 

302 that kept the top score its old place back. 

303 

304 :param data: The raw byte data the results were produced from. 

305 :param results: A list of :class:`DetectionResult` ranked by confidence. 

306 :returns: A new list (or the same list) with the demotion applied. 

307 """ 

308 if len(results) < 2 or results[0].encoding not in _DEMOTION_CANDIDATES: 

309 return results 

310 candidates = [r for r in results[1:] if r.encoding in _COMMON_LATIN_ENCODINGS] 

311 if not candidates: 

312 return results 

313 target = _swap_target(candidates) 

314 if not _should_demote(data, results[0], target): 

315 return results 

316 demoted_encoding = results[0].encoding 

317 top_conf = results[0].confidence 

318 promoted = DetectionResult( 

319 target.encoding, top_conf, target.language, target.mime_type 

320 ) 

321 others = [x for x in results if x.encoding != demoted_encoding and x is not target] 

322 tail_conf = others[-1].confidence if others else top_conf 

323 demoted_entries = [ 

324 DetectionResult( 

325 x.encoding, 

326 min(x.confidence, tail_conf), 

327 x.language, 

328 x.mime_type, 

329 ) 

330 for x in results 

331 if x.encoding == demoted_encoding 

332 ] 

333 return [promoted, *others, *demoted_entries] 

334 

335 

336def _promote_koi8t( 

337 data: bytes, 

338 results: list[DetectionResult], 

339) -> list[DetectionResult]: 

340 """Promote KOI8-T over KOI8-R when Tajik-specific bytes are present. 

341 

342 KOI8-T and KOI8-R share the entire 0xC0-0xFF Cyrillic letter block, 

343 making statistical discrimination difficult. However, KOI8-T maps 12 

344 bytes in 0x80-0xBF to Tajik-specific Cyrillic letters where KOI8-R has 

345 box-drawing characters. If any of these bytes appear, KOI8-T is the 

346 better match. 

347 """ 

348 if not results or results[0].encoding != "koi8-r": 

349 return results 

350 # Check if KOI8-T is anywhere in the results 

351 koi8t_idx = next((i for i, r in enumerate(results) if r.encoding == "koi8-t"), None) 

352 if koi8t_idx is None: 

353 return results 

354 # Check for Tajik-specific bytes 

355 if len(data.translate(None, _KOI8_T_DELETE)) != len(data): 

356 return _promote_to_top(results, koi8t_idx) 

357 return results 

358 

359 

360# Confidence gap below which two candidates are a statistical dead heat: 

361# their scores differ only through model-norm noise on bigrams that carry no 

362# real evidence (observed dead heats sit within ~1e-5; genuinely decided 

363# rankings lead by >= 2e-3). 

364_DEAD_HEAT_EPSILON = 1e-4 

365 

366# On a dead heat between an encoding and its Windows superset, prefer the 

367# superset: it decodes everything the base encoding does, so it is never a 

368# worse answer when the statistics cannot separate them. The pairs are 

369# the registry's ``CJK_SUPERSETS``, shared with ``markup``'s promotion: 

370# reaching the ISO/Windows pairs would be an accuracy change to measure on 

371# its own (ADR-0007). 

372 

373# Confidence band for the classic-Mac line-ending promotion. Wider than the 

374# dead-heat epsilon because bare-\r line endings are decisive platform 

375# evidence, not just a prior. Structurally the confusion band: retuning 

376# ``CONFUSION_BAND`` carries this promotion's reach with it, keeping the 

377# band inside ``_CORRECTION_REACH`` so pruning always scores what it scans. 

378_CR_MAC_BAND = CONFUSION_BAND 

379 

380# Minimum number of \r line endings before the classic-Mac promotion fires. 

381_CR_MAC_MIN_LINES = 3 

382 

383# EncodingEra.LEGACY_MAC — value inlined to avoid importing the enum into 

384# this mypyc-compiled hot path for a single constant. 

385_LEGACY_MAC_ERA = 4 

386 

387 

388# Cap on the data scanned for high-byte bigram evidence — matches the 

389# window statistical scoring uses (``orchestrator._STAT_SCORE_MAX_BYTES``), 

390# so the evidence check sees the same bytes the scores were computed from. 

391_EVIDENCE_SCAN_MAX_BYTES = 16384 

392 

393 

394def _era_rank(encoding: str) -> int: 

395 """Return the lowest era bit for *encoding* (lower = more prevalent today).""" 

396 info = REGISTRY.get(encoding) 

397 if info is None: 

398 return 1 << 30 

399 era = int(info.era) 

400 return era & -era 

401 

402 

403def _has_high_byte_evidence(data: bytes, encoding: str, language: "str | None") -> bool: 

404 """Return True if *encoding*'s winning model weights a high-byte bigram present in *data*. 

405 

406 A candidate whose model assigns zero weight to every non-ASCII bigram in 

407 the data earned its statistical score purely from ASCII bigrams — noise 

408 that cannot distinguish encodings. Only the variant that actually won 

409 (*language*) counts: another language's variant having weight for those 

410 bytes says nothing about why *this* result is on top. Only called on 

411 dead heats, so the Python-level scan of the (capped) data is off the 

412 hot path. 

413 """ 

414 variants = get_enc_index().get(encoding) 

415 if not variants: 

416 return False 

417 window = data[:_EVIDENCE_SCAN_MAX_BYTES] 

418 seen: set[int] = set() 

419 prev = window[0] 

420 for i in range(1, len(window)): 

421 b = window[i] 

422 if prev >= 0x80 or b >= 0x80: 

423 seen.add((prev << 8) | b) 

424 prev = b 

425 if not seen: 

426 return False 

427 for lang, table, _key in variants: 

428 if language is not None and lang != language: 

429 continue 

430 for idx in seen: 

431 if table[idx]: 

432 return True 

433 return False 

434 

435 

436def _prefer_prevalent_on_dead_heat( 

437 data: bytes, 

438 results: list[DetectionResult], 

439) -> list[DetectionResult]: 

440 """Break statistical dead heats in favor of the more prevalent era. 

441 

442 When several encodings score within :data:`_DEAD_HEAT_EPSILON` of the 

443 top result, the ranking among them is mostly an artifact of 

444 ASCII-bigram noise. Promote the candidate from the most prevalent era 

445 (modern web > legacy ISO > Mac > regional > DOS > mainframe) so 

446 evidence-free dead heats resolve to the likeliest real-world answer. 

447 

448 A top result whose models carry no weight for any high-byte bigram in 

449 the data has no evidence at all and yields outright. One whose models 

450 do weight an observed bigram is not thereby safe: an English file with 

451 one capital ``É`` ranks MacRoman first because the MacRoman model 

452 reads 0xC9 as the ellipsis English text is full of, a lead worth 

453 2e-5. Such a top is arbitrated against the prevalent candidate on the 

454 bytes the two read differently, under the languages the two can be 

455 compared in (see :func:`~chardet.pipeline.confusion.arbitrate_distinguishing_bytes` 

456 and confusion's ``_comparable_languages``): the Windows-1252 French 

457 model knows ``École`` even when the English one does not, while its 

458 Icelandic model may not read a Welsh ``dŵr`` as ``dðr`` against an 

459 encoding that models no Icelandic. The prevalent candidate is 

460 promoted only when it wins outright; a tie keeps the top, so an 

461 ISO-8859-1 result tied with Windows-1252 on data without C1 bytes 

462 stays where the statistics put it. Genuine MacRoman text never 

463 reaches the arbitration, since its hundreds of distinguishing bytes 

464 put Windows-1252 far outside the band. 

465 """ 

466 top = results[0] if results else None 

467 if top is None or top.encoding is None or len(results) < 2: 

468 return results 

469 best_idx = 0 

470 best_rank = _era_rank(top.encoding) 

471 for i in range(1, len(results)): 

472 r = results[i] 

473 if r.encoding is None: 

474 continue 

475 if top.confidence - r.confidence > _DEAD_HEAT_EPSILON: 

476 break 

477 rank = _era_rank(r.encoding) 

478 if rank < best_rank: 

479 best_rank = rank 

480 best_idx = i 

481 if best_idx == 0: 

482 return results 

483 if not _has_high_byte_evidence(data, top.encoding, top.language): 

484 return _promote_to_top(results, best_idx) 

485 rival = results[best_idx].encoding or "" 

486 comparable = _comparable_languages( 

487 top.encoding, 

488 rival, 

489 frozenset( 

490 lang 

491 for lang in (top.language, results[best_idx].language) 

492 if lang is not None 

493 ), 

494 ) 

495 winner = arbitrate_distinguishing_bytes( 

496 data, 

497 top.encoding, 

498 rival, 

499 differing_high_bytes(top.encoding, rival), 

500 languages_a=comparable, 

501 languages_b=comparable, 

502 ) 

503 if winner == rival: 

504 return _promote_to_top(results, best_idx) 

505 return results 

506 

507 

508def _promote_to_top(results: list[DetectionResult], i: int) -> list[DetectionResult]: 

509 """Move ``results[i]`` to the top, carrying the current top confidence.""" 

510 r = results[i] 

511 promoted = DetectionResult( 

512 r.encoding, results[0].confidence, r.language, r.mime_type 

513 ) 

514 rest = [x for j, x in enumerate(results) if j != i] 

515 return [promoted, *rest] 

516 

517 

518def _promote_superset_on_dead_heat( 

519 data: bytes, 

520 results: list[DetectionResult], 

521) -> list[DetectionResult]: 

522 """Promote a Windows superset over its base encoding on a dead heat.""" 

523 top = results[0] if results else None 

524 if top is None or top.encoding is None or len(results) < 2: 

525 return results 

526 superset = CJK_SUPERSETS.get(top.encoding) 

527 if superset is None: 

528 return results 

529 for i in range(1, len(results)): 

530 r = results[i] 

531 if top.confidence - r.confidence > _DEAD_HEAT_EPSILON: 

532 break 

533 if r.encoding == superset and decodes_without_error(data, superset): 

534 return _promote_to_top(results, i) 

535 return results 

536 

537 

538# Languages whose (language, encoding) variants never accumulated a 

539# measurable legacy document population. Not a judgment about the 

540# languages — a graded prior about legacy-era *bytes*: iso8859-14 (Latin-8, 

541# the Celtic code page) was standardized in 1998 but Celtic text lived in 

542# latin-1 and moved to UTF-8; this project's wild-page mining has found no 

543# native specimen, and web surveys place the encoding at noise level. The 

544# one documented genuine niche — Irish gettext .po catalogues that declare 

545# ISO-8859-14 (Scannell's vim/gettext translations, in the test suite) — 

546# measures safely outside the arbitration gate: genuine Celtic text has no 

547# prevalent-language rival anywhere near it. Revision protocol per 

548# ADR-0005: any new genuine specimen goes into test-data and forces a 

549# re-audit of the set. The set itself is :data:`chardet.models.RARE_LANGUAGES` 

550# — one definition, used by this gate and by the language fill's thin-margin 

551# band, so the two can never drift apart. Membership changes happen there. 

552 

553# Maximum lead over the best prevalent-language candidate for a 

554# rare-language winner to count as a coin flip rather than evidence. 

555_RARE_ARBITRATION_MARGIN = 0.02 

556 

557# Maximum absolute confidence for arbitration to apply: genuine 

558# rare-language text scores well above this even when short, so the gate 

559# only opens in the evidence-free zone. 

560_RARE_ARBITRATION_MAX_CONFIDENCE = 0.15 

561 

562 

563def _arbitrate_rare_language( 

564 results: list[DetectionResult], 

565) -> list[DetectionResult]: 

566 """Demote a rare-language winner that leads a prevalent rival by a coin flip. 

567 

568 Fires only when the winner's language is in 

569 :data:`~chardet.models.RARE_LANGUAGES`, its absolute confidence is 

570 inside the evidence-free zone, and a prevalent-language candidate sits 

571 within :data:`_RARE_ARBITRATION_MARGIN`. Genuine rare-language text fails 

572 both gates: even short files score confidently, and their entire 

573 neighborhood is same-language variants. 

574 """ 

575 top = results[0] if results else None 

576 if ( 

577 top is None 

578 or top.encoding is None 

579 or top.language not in RARE_LANGUAGES 

580 or top.confidence >= _RARE_ARBITRATION_MAX_CONFIDENCE 

581 or len(results) < 2 

582 ): 

583 return results 

584 for i in range(1, len(results)): 

585 r = results[i] 

586 if top.confidence - r.confidence > _RARE_ARBITRATION_MARGIN: 

587 break 

588 if r.encoding is None or r.language is None: 

589 continue 

590 if r.language not in RARE_LANGUAGES: 

591 return _promote_to_top(results, i) 

592 return results 

593 

594 

595def _promote_mac_on_cr_line_endings( 

596 data: bytes, 

597 results: list[DetectionResult], 

598) -> list[DetectionResult]: 

599 r"""Promote a classic-Mac candidate when line endings are bare ``\r``. 

600 

601 Classic Mac OS is the only platform that terminated lines with a lone 

602 carriage return, so data with several ``\r`` bytes and no ``\n`` is 

603 near-certainly Mac-era text. When a LEGACY_MAC candidate scores within 

604 :data:`_CR_MAC_BAND` of a non-Mac top result, promote it — unless the 

605 pair has a distinguishing-byte map and the byte-level evidence says the 

606 current top wins: a platform prior must not overturn direct evidence 

607 that confusion resolution may have just used to establish the top. 

608 """ 

609 top = results[0] if results else None 

610 if top is None or top.encoding is None or len(results) < 2: 

611 return results 

612 if _era_rank(top.encoding) == _LEGACY_MAC_ERA: 

613 return results 

614 # An art-model win is not up for prose-based review: the pairwise 

615 # veto below reasons about word shapes and prose bigrams, which 

616 # box-drawing data is not, and old ANSI art legitimately carries 

617 # bare-CR line endings. 

618 if top.language == ART_LANGUAGE: 

619 return results 

620 if data.find(b"\n") >= 0 or data.count(b"\r") < _CR_MAC_MIN_LINES: 

621 return results 

622 for i in range(1, len(results)): 

623 r = results[i] 

624 if top.confidence - r.confidence > _CR_MAC_BAND: 

625 break 

626 if r.encoding is not None and _era_rank(r.encoding) == _LEGACY_MAC_ERA: 

627 # Pass both languages so the veto arbitrates this pair under 

628 # the same rule confusion resolution just applied to it. 

629 langs = frozenset( 

630 lang for lang in (top.language, r.language) if lang is not None 

631 ) 

632 if ( 

633 confusion_pair_winner(data, top.encoding, r.encoding, langs) 

634 == top.encoding 

635 ): 

636 # Byte-level evidence says the top beats the best-ranked 

637 # Mac candidate: stop entirely rather than letting a 

638 # lower-ranked sibling take the promotion just because it 

639 # has no distinguishing-byte map to be checked against. 

640 break 

641 return _promote_to_top(results, i) 

642 return results 

643 

644 

645def _prefer_decodable_on_tie( 

646 data: bytes, 

647 results: list[DetectionResult], 

648 *, 

649 input_truncated: bool, 

650) -> list[DetectionResult]: 

651 """Promote a strictly decoding rival over a winner with no real evidence. 

652 

653 Byte-validity filtering runs incremental decoders with ``final=False``, 

654 tolerating an incomplete multi-byte sequence at the end because detection 

655 input is often a prefix of a larger whole. When chardet examined the 

656 caller's *entire* input, that tolerance can hand back an encoding the 

657 caller's very next ``data.decode()`` will reject --- a four-byte 

658 ``iso-8859-1`` word ending in ``0xE1`` detected as utf-8 (issue #380). 

659 

660 Fires only when *data* is the whole of what the caller handed over --- 

661 *input_truncated* is False --- the tail can actually hold a 

662 dangling sequence (a high byte in the final four, multi-byte winner), 

663 and the winner's tolerant decode is **non-empty pure ASCII** --- its 

664 only multi-byte evidence is the dangling tail itself. An empty 

665 tolerant decode (the whole input is one clipped sequence) is zero 

666 evidence, not ASCII evidence, and disqualifies the flip. The 

667 best-ranked rival that decodes the input completely under both its 

668 internal and public names (``compat_names=True``, the default, can 

669 remap to a strictly narrower codec: ``euc_jis_2004`` is reported as 

670 ``EUC-JP``) then takes the top slot, regardless of the 

671 confidence gap: an all-ASCII-evidence winner detected nothing the 

672 rival did not also detect, and any statistical lead it holds comes 

673 from ASCII bigrams the rival matched equally well. The scan sees the 

674 ranking as given, which under ``full_ranking=False`` is pruned; if no 

675 listed rival decodes, the winner stands (measured across a 648-case 

676 accent-final sweep, the pruned ranking always carried a decodable 

677 rival). 

678 

679 The pure-ASCII condition is what makes the unconditional flip safe. A 

680 short mid-character CJK cut has a correct answer that cannot decode the 

681 input --- flipping it to whichever single-byte codec happens to decode 

682 the bytes trades a right answer for a wrong one, and a 5-40 byte sweep 

683 measured exactly that under a gap-based rule (34 correct CJK answers 

684 lost, Big5 becoming cp1125). Such a winner has decoded real multi-byte 

685 characters and keeps its ranking; with the pure-ASCII condition in 

686 place, the same sweep measures zero lost answers at any gap. 

687 """ 

688 top = results[0] if results else None 

689 if ( 

690 input_truncated 

691 or top is None 

692 or top.encoding is None 

693 or len(results) < 2 

694 # A dangling multi-byte tail needs a high byte among the final 

695 # bytes (empty data trivially has none). No cheaper winner gate 

696 # exists: the registry's is_multibyte_cjk means CJK-style structural 

697 # multibyte and is False for utf-8, the main deferring codec. The 

698 # helper below is a single tolerant decode; validity already ran 

699 # the same decode once per candidate, so this adds at most one 

700 # more, and only for high-byte-tailed winners. 

701 or not any(b >= 0x80 for b in data[-4:]) 

702 or not dangling_tail_with_ascii_prefix(data, top.encoding) 

703 ): 

704 return results 

705 for i in range(1, len(results)): 

706 r = results[i] 

707 if r.encoding is None or not decodes_under_public_name( 

708 data, r.encoding, complete=True 

709 ): 

710 continue 

711 return _promote_to_top(results, i) 

712 return results 

713 

714 

715# --------------------------------------------------------------------------- 

716# The pruning contract: what statistical pruning must score exactly 

717# --------------------------------------------------------------------------- 

718 

719#: How far below the running second-best score a candidate can sit and still 

720#: be examined by a rank correction: rare-language arbitration reads margins 

721#: up to ``_RARE_ARBITRATION_MARGIN`` from the top, and confusion resolution 

722#: examines the band, kept with a 2x cushion for float noise. 

723_CORRECTION_REACH = _RARE_ARBITRATION_MARGIN + 2 * CONFUSION_BAND 

724 

725 

726def scoring_floor(top1: float, top2: float) -> float: 

727 """Return the score below which the rank corrections cannot examine a candidate. 

728 

729 One half of the pruning contract statistical scoring consumes: given 

730 the running top two encoding scores, every candidate at or above this 

731 floor must carry its exact full-ranking score, or a correction could 

732 fire on an understated (or missing) entry and ``detect()`` would 

733 diverge from the unpruned full ranking. The floor trails the 

734 second-best score by the corrections' reach; while the top is low 

735 enough for confusion resolution's strict tier to open, it extends down 

736 to that tier's floor, because a strict-tier promotion may raise any 

737 candidate above the tier floor into position 0 before the other 

738 corrections evaluate their triggers. 

739 

740 One correction sits deliberately outside this floor: the decode-safety 

741 flip (:func:`_prefer_decodable_on_tie`) scans the ranking *as given*, 

742 including pruned-path tails whose entries may be understated or absent 

743 — a measured trade-off documented in its own docstring, not a floor 

744 violation to fix by widening the reach. 

745 """ 

746 floor = top2 - _CORRECTION_REACH 

747 if top1 < STRICT_TIER_MAX_CONF: 

748 floor = min(floor, top1 * CONFUSION_FLOOR_RATIO) 

749 return floor 

750 

751 

752def forced_encodings(near_top: list[str]) -> list[str]: 

753 """Return the encodings the corrections look up by name, given the near-top set. 

754 

755 The other half of the pruning contract: :func:`postprocess_results` 

756 inspects some encodings wherever they rank (the common Western Latin 

757 trio for niche Latin demotion, KOI8-T for the KOI8-R promotion), so 

758 pruning must score every variant of these whenever a trigger encoding 

759 sits at or above the :func:`scoring_floor` — confusion resolution may 

760 promote any such candidate to the top before those triggers are 

761 evaluated. 

762 """ 

763 forced: list[str] = [] 

764 if any(e in _DEMOTION_CANDIDATES for e in near_top): 

765 forced.extend(_COMMON_LATIN_ENCODINGS) 

766 if "koi8-r" in near_top: 

767 forced.append("koi8-t") 

768 return forced 

769 

770 

771def postprocess_results( 

772 data: bytes, 

773 results: list[DetectionResult], 

774 *, 

775 input_truncated: bool = False, 

776) -> list[DetectionResult]: 

777 """Apply rank corrections to the statistically scored results. 

778 

779 Steps run in sequence, weakest evidence first: dead-heat priors 

780 (superset preference, era prevalence), then confusion-group resolution, 

781 niche Latin demotion, and KOI8-T promotion (byte-level evidence), and 

782 finally the classic-Mac line-ending promotion (platform evidence that 

783 should override the priors). The decode-safety tiebreak runs last of 

784 all: whatever the ranking settled on, a winner that cannot decode the 

785 caller's complete input, and whose own evidence is nothing but the 

786 undecodable tail, yields to the best-ranked rival that can decode it. 

787 

788 :param data: The raw byte data the results were produced from. 

789 :param results: A list of :class:`DetectionResult` ranked by confidence. 

790 :param input_truncated: True when *data* is a chardet-made slice rather 

791 than the caller's whole input --- the ``max_bytes`` slice, the 

792 evidence-cap slice, or ``UniversalDetector``'s buffer cap. Any of 

793 them means the bytes here are not the whole story, so the 

794 decode-safety tiebreak, which reasons about what the caller will 

795 decode, stands down. 

796 :returns: A new list (or the same list) with rank corrections applied. 

797 """ 

798 results = _promote_superset_on_dead_heat(data, results) 

799 results = _prefer_prevalent_on_dead_heat(data, results) 

800 results = _arbitrate_rare_language(results) 

801 results = resolve_confusion_groups(data, results) 

802 results = _demote_niche_latin(data, results) 

803 results = _promote_koi8t(data, results) 

804 results = _promote_mac_on_cr_line_endings(data, results) 

805 return _prefer_decodable_on_tie(data, results, input_truncated=input_truncated)