Skip to content

Commit e63170f

Browse files
authored
TIKA-4720 -- Move charset detection to byte-bigram Naive Bayes pipeline (#2784)
* Move charset detection to byte-bigram Naive Bayes pipeline
1 parent 7d34f9e commit e63170f

44 files changed

Lines changed: 3202 additions & 4901 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/modules/ROOT/pages/advanced/charset-detection-design.adoc

Lines changed: 296 additions & 445 deletions
Large diffs are not rendered by default.

docs/modules/ROOT/pages/configuration/encoding-detectors.adoc

Lines changed: 98 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -18,119 +18,113 @@
1818
= Configuring Encoding Detectors
1919

2020
Tika uses a chain of _encoding detectors_ to determine the character encoding
21-
of plain text and HTML content. The chain is controlled by
22-
`DefaultEncodingDetector`, which loads detectors via the Java service-provider
23-
interface (SPI) and runs them in registration order.
21+
of plain text and HTML content. `DefaultEncodingDetector` loads detectors
22+
via the Java service-provider interface (SPI) and runs them in registration
23+
order. See xref:../advanced/charset-detection-design.adoc[Charset Detection Pipeline]
24+
for design details.
2425

2526
== Default Detection Chain
2627

27-
The default chain when `tika-charset-detectors-core` is on the classpath:
28+
With the stock dependencies on the classpath:
2829

29-
[cols="1,2,1"]
30+
[cols="1,2,2"]
3031
|===
3132
|Step |Detector |Returns non-null when…
3233

3334
|1
34-
|`http-header-encoding-detector`
35-
|A `charset=` parameter is present in the `Content-Type` metadata field
36-
(e.g. populated from an HTTP response header).
35+
|`bom-detector`
36+
|A UTF-8, UTF-16 LE/BE, or UTF-32 LE/BE byte-order mark is present.
37+
Emits DECLARATIVE.
3738

3839
|2
39-
|`bom-encoding-detector`
40-
|A UTF-8, UTF-16 LE/BE, or UTF-32 LE/BE byte-order mark is present.
40+
|`metadata-charset-detector`
41+
|A `charset=` parameter is present in the `Content-Type` metadata field
42+
(populated from an HTTP response header or similar). Emits DECLARATIVE.
4143

4244
|3
43-
|`html-encoding-detector`
44-
|An HTML `<meta charset="…">` or `Content-Type` http-equiv tag is found
45-
(fast lenient regex matcher, curated WHATWG label aliases).
45+
|`naive-bayes-pipeline-encoding-detector`
46+
|A structural UTF-32 check, structural UTF-16 specialist, UTF-8 grammar
47+
gate, and 33-class byte-bigram Naive Bayes classifier. STRUCTURAL for
48+
structural hits; STATISTICAL for NB predictions.
4649

4750
|4
48-
|`ml-encoding-detector`
49-
|The built-in statistical model classifies the byte stream (~46 encodings,
50-
~185 KB model bundled as a resource).
51-
52-
|5 _(if present)_
53-
|`universal-encoding-detector`
54-
|State-machine structural prober (juniversalchardet fork). Automatically
55-
joins the chain when `tika-charset-detectors-universal` is on the classpath.
56-
Complements ML: excels at short or repetitive CJK byte sequences (ZIP entry
57-
names, single-word filenames) where statistical models lack sufficient texture.
51+
|`html-encoding-detector`
52+
|An HTML `<meta charset="…">` or `<meta http-equiv="Content-Type">` tag
53+
is found (fast lenient regex matcher, curated WHATWG label aliases).
54+
Emits DECLARATIVE.
5855

59-
|6 _(if present)_
56+
|5
6057
|`charsoup-encoding-detector`
61-
|A `MetaEncodingDetector` that runs after all base detectors. When they all
62-
agree it returns the unanimous result; when they disagree it uses language-
63-
detection scoring — with a junk-ratio fallback (fewest undefined codepoints
64-
wins) for content too short for reliable language detection.
58+
|`MetaEncodingDetector` — arbitrates across all base-detector candidates
59+
using a character-bigram language model. When one or more base detectors
60+
return a result, CharSoup decodes the probe with each candidate charset
61+
and picks the one whose decoded text scores best for any supported
62+
language. Always runs last.
6563
|===
6664

67-
NOTE: `universal-encoding-detector` and `charsoup-encoding-detector` are
68-
supplied by separate optional modules (`tika-charset-detectors-universal` and
69-
`tika-langdetect-charsoup` respectively). Each is loaded automatically via
70-
SPI when its module is on the classpath and requires no extra configuration.
71-
72-
== Design Rationale
73-
74-
The chain combines two complementary detection strategies:
75-
76-
* **Statistical (ML)** — learns byte-bigram distributions from training data.
77-
Works well for documents with enough varied content (~100+ bytes).
78-
* **Structural (Universal)** — applies encoding-spec constraints (is this a
79-
valid lead+trail byte pair for Shift_JIS / EUC-JP / Big5 / GBK?). Works
80-
on as few as two bytes and is unaffected by content length.
81-
82-
Rules beat statistics at the extremes (very short or highly structured input);
83-
statistics beat rules in the ambiguous middle where distributions are rich.
84-
`charsoup-encoding-detector` arbitrates when they disagree.
65+
NOTE: `charsoup-encoding-detector` is supplied by
66+
`tika-encoding-detector-charsoup` and loads automatically via SPI when
67+
the module jar is present. Omit it to get strict first-match-wins
68+
behaviour that honours declarations authoritatively — see
69+
<<opting-out-of-charsoup>>.
8570

8671
== Available Detectors
8772

8873
All detectors implement `org.apache.tika.detect.EncodingDetector` and can be
89-
referenced by name in JSON configuration.
74+
referenced by their SPI name in JSON configuration.
9075

9176
[cols="2,2,3"]
9277
|===
9378
|Name |Module |Description
9479

95-
|`http-header-encoding-detector`
96-
|`tika-charset-detectors-core`
97-
|Reads `charset=` from the `Content-Type` metadata field. In the default chain.
80+
|`bom-detector`
81+
|`tika-core`
82+
|Reads the first 4 bytes for BOM signatures. In the default chain.
83+
84+
|`metadata-charset-detector`
85+
|`tika-core`
86+
|Reads declarative hints (`Content-Type` charset, `Content-Encoding`) from
87+
the `Metadata` object. Applies WHATWG label normalization
88+
(ISO-8859-1 and US-ASCII → windows-1252). In the default chain.
9889

99-
|`bom-encoding-detector`
100-
|`tika-charset-detectors-core`
101-
|Byte-order mark detection (UTF-8/16/32). In the default chain.
90+
|`naive-bayes-pipeline-encoding-detector`
91+
|`tika-encoding-detector-mojibuster`
92+
|Byte-bigram Naive Bayes classifier plus structural detectors for UTF-32
93+
and UTF-16 and a UTF-8 grammar gate. 33 classes including CJK
94+
multi-byte, EBCDIC variants, DOS code pages, Cyrillic, Windows single-byte,
95+
ISO-8859-3/16, Mac, and UTF-8. In the default chain. See
96+
xref:../advanced/charset-detection-design.adoc[the design doc].
10297

10398
|`html-encoding-detector`
104-
|`tika-charset-detectors-core`
99+
|`tika-encoding-detector-html`
105100
|Fast lenient regex matcher for `<meta charset>` / `http-equiv` tags, with a
106101
curated subset of WHATWG label aliases. In the default chain.
107102

108-
|`ml-encoding-detector`
109-
|`tika-charset-detectors-core`
110-
|Statistical multinomial logistic regression model (~46 encodings).
111-
In the default chain.
112-
113-
|`universal-encoding-detector`
114-
|`tika-charset-detectors-universal`
115-
|State-machine structural prober; wraps the `com.github.albfernandez:juniversalchardet`
116-
fork. Auto-registers when the module jar is on the classpath.
117-
118103
|`standard-html-encoding-detector`
119-
|`tika-charset-detectors-core`
104+
|`tika-encoding-detector-html`
120105
|Spec-strict WHATWG prescan algorithm. Not in the default chain — opt in
121106
explicitly if you need strict WHATWG tokenisation (e.g. ignoring charset
122107
declarations inside comments or other contexts the lenient regex may match).
123108

109+
|`charsoup-encoding-detector`
110+
|`tika-encoding-detector-charsoup`
111+
|Language-aware arbitrator (`MetaEncodingDetector`). In the default
112+
chain when the module jar is on the classpath; always runs last.
113+
114+
|`mojibuster-encoding-detector`
115+
|`tika-encoding-detector-mojibuster`
116+
|Legacy maxent charset classifier. Still available for explicit opt-in;
117+
not auto-registered.
118+
124119
|`icu4j-encoding-detector`
125-
|`tika-charset-detectors-icu4j`
126-
|Wraps ICU4J `CharsetDetector`. _Legacy_ — the ML + Universal chain
127-
supersedes it for most use cases. Available for explicit opt-in when
128-
`com.ibm.icu:icu4j` is already on the classpath.
120+
|`tika-encoding-detector-icu4j`
121+
|Wraps ICU4J `CharsetDetector`. Legacy — the NB pipeline supersedes it
122+
for most cases. Not auto-registered.
129123

130-
|`charsoup-encoding-detector`
131-
|`tika-langdetect-charsoup`
132-
|Language-aware arbitrator (`MetaEncodingDetector`). Auto-registers when
133-
the module jar is on the classpath; always runs last.
124+
|`universal-encoding-detector`
125+
|`tika-encoding-detector-universal`
126+
|State-machine structural prober (juniversalchardet fork). Not
127+
auto-registered; opt in if you specifically need it.
134128
|===
135129

136130
== Configuration Examples
@@ -143,63 +137,76 @@ the module jar is on the classpath; always runs last.
143137
"encoding-detectors": [
144138
{
145139
"default-encoding-detector": {
146-
"exclude": ["bom-encoding-detector"]
140+
"exclude": ["bom-detector"]
147141
}
148142
}
149143
]
150144
}
151145
----
152146

153-
=== Restrict to a lightweight chain (no Universal, no CharSoup)
147+
[[opting-out-of-charsoup]]
148+
=== Strict declared-charset honoring (no CharSoup arbitration)
154149

155-
Useful in resource-constrained environments when you only need the core
156-
statistical chain:
150+
By default, CharSoup arbitrates across all candidates including DECLARATIVE
151+
ones — which means a `<meta charset>` tag is not automatically
152+
authoritative. If you want declarations to win unconditionally (HTML5-spec
153+
behaviour), omit CharSoup:
157154

158155
[source,json]
159156
----
160157
{
161158
"encoding-detectors": [
162-
{"http-header-encoding-detector": {}},
163-
{"bom-encoding-detector": {}},
159+
{"bom-detector": {}},
160+
{"metadata-charset-detector": {}},
164161
{"html-encoding-detector": {}},
165-
{"ml-encoding-detector": {}}
162+
{"naive-bayes-pipeline-encoding-detector": {}}
166163
]
167164
}
168165
----
169166

167+
Without CharSoup, `CompositeEncodingDetector` runs in first-match-wins
168+
mode. Trade-off: lying declarations (common on the legacy web)
169+
propagate unfiltered. See
170+
xref:../advanced/charset-detection-design.adoc#opting-out-of-arbitration[the design doc]
171+
for details.
172+
170173
=== Configure the HTML detector's read limit
171174

172-
The default limit is 8 192 bytes. Raise it if your HTML documents embed
173-
large `<script>` blocks before the `<meta charset>` declaration.
175+
`html-encoding-detector` reads up to 65 536 bytes by default when
176+
scanning for the `<meta charset>` tag. Raise it if your documents embed
177+
large `<script>` blocks before the meta tag (TIKA-2485):
174178

175179
[source,json]
176180
----
177181
{
178182
"encoding-detectors": [
179-
{"http-header-encoding-detector": {}},
180-
{"bom-encoding-detector": {}},
183+
{"bom-detector": {}},
184+
{"metadata-charset-detector": {}},
181185
{
182186
"html-encoding-detector": {
183-
"markLimit": 65536
187+
"markLimit": 131072
184188
}
185189
},
186-
{"ml-encoding-detector": {}}
190+
{"naive-bayes-pipeline-encoding-detector": {}},
191+
{"charsoup-encoding-detector": {}}
187192
]
188193
}
189194
----
190195

191-
=== Recreate the pre-4.x default (HTML + juniversalchardet + ICU4J)
196+
=== Legacy chain (ICU4J + juniversalchardet)
192197

193-
Not recommended — the new chain is strictly better — but possible for
194-
regression testing or comparison:
198+
Not recommended — the NB pipeline is strictly better on accuracy and
199+
latency — but available for regression testing or comparison:
195200

196201
[source,json]
197202
----
198203
{
199204
"encoding-detectors": [
205+
{"bom-detector": {}},
206+
{"metadata-charset-detector": {}},
200207
{"html-encoding-detector": {}},
201-
{"universal-encoding-detector": {}},
202-
{"icu4j-encoding-detector": {}}
208+
{"icu4j-encoding-detector": {}},
209+
{"universal-encoding-detector": {}}
203210
]
204211
}
205212
----

tika-core/src/main/java/org/apache/tika/detect/BOMDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
*
3939
* <p>SPI-loaded first in the default encoding-detector chain so that BOM evidence
4040
* reaches {@code CharSoupEncodingDetector} before any statistical detector runs.
41-
* {@code MojibusterEncodingDetector} strips the BOM from its own probe independently
41+
* {@code NaiveBayesPipelineEncodingDetector} strips the BOM from its own probe independently
4242
* to ensure consistent model inference (BOMs are excluded from training data).</p>
4343
*
4444
* @since Apache Tika 0.x (moved to org.apache.tika.detect in 4.0)

tika-encoding-detectors/tika-encoding-detector-charsoup/src/main/resources/META-INF/services/org.apache.tika.detect.EncodingDetector

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
# It must be loaded last; DefaultEncodingDetector's class-name sort puts
1818
# org.apache.tika.parser.* after org.apache.tika.ml.* after
1919
# org.apache.tika.detect.*, which guarantees the correct order:
20-
# MojibusterEncodingDetector
20+
# NaiveBayesPipelineEncodingDetector
2121
# StandardHtmlEncodingDetector → CharSoupEncodingDetector
2222
org.apache.tika.langdetect.charsoup.CharSoupEncodingDetector
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
SLF4J(W): No SLF4J providers were found.
2+
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
3+
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
4+
5+
=== Probe length: 20B ===
6+
N | --- ML ablation --------------------------------------------------- | --- Baselines --------------------------------- |
7+
Charset | ICU4J R% S% T3% D% A% | juniv R% S% T3% D% A% | NB R% S% T3% D% A% |
8+
----------------------------------------------------------------------------------------------------------------------------
9+
Big5-HKSCS 30073 | 0.0 13.9 71.6 16.4 16.6 | 0.0 44.2 44.2 46.6 46.9 | 61.1 61.1 61.2 65.1 65.1 |
10+
EUC-JP 37047 | 0.0 0.0 14.7 6.6 7.6 | 64.1 64.1 64.1 71.1 72.1 | 75.9 75.9 81.9 83.2 83.9 |
11+
GB18030 36859 | 0.2 0.2 7.8 6.0 6.7 | 45.8 45.8 45.8 51.7 52.5 | 77.5 77.5 78.0 83.5 83.7 |
12+
IBM1047 31450 | 0.0 92.0 98.3 90.9 91.5 | 0.0 0.0 0.0 0.0 0.0 | 19.5 71.4 71.8 71.0 71.1 |
13+
IBM420-ltr 36865 | 0.0 83.6 91.1 0.0 0.0 | 0.0 0.0 0.0 0.0 0.0 | 67.7 70.5 70.9 67.7 67.7 |
14+
IBM420-rtl 37022 | 0.0 88.2 93.6 0.0 0.0 | 0.0 0.0 0.0 0.0 0.0 | 70.9 73.0 73.4 70.9 70.9 |
15+
IBM424-ltr 35011 | 0.0 74.6 85.3 0.0 0.0 | 0.0 0.0 0.0 0.0 0.0 | 13.4 14.4 15.5 13.4 13.4 |
16+
IBM424-rtl 33092 | 0.0 78.8 87.3 0.0 0.0 | 0.0 0.0 0.0 0.0 0.0 | 13.5 14.6 15.5 13.5 13.5 |
17+
IBM500 31419 | 92.0 92.0 98.2 92.0 92.0 | 0.0 0.0 0.0 0.0 0.0 | 51.9 71.1 71.5 70.9 71.1 |
18+
IBM850 30465 | 0.0 0.0 0.0 57.1 57.9 | 0.0 0.0 0.0 58.0 58.9 | 5.8 5.8 7.5 64.2 64.2 |
19+
IBM852 35340 | 0.0 0.0 0.0 39.8 39.9 | 0.0 0.0 0.0 41.6 41.9 | 11.8 11.8 13.6 53.5 53.6 |
20+
IBM855 36704 | 0.0 0.0 0.0 1.7 1.7 | 93.2 93.2 93.2 94.9 95.0 | 71.0 71.0 72.9 72.8 72.8 |
21+
IBM866 36979 | 52.3 52.3 79.4 54.5 54.5 | 94.6 94.6 94.6 96.9 96.9 | 29.3 29.3 29.8 31.6 31.6 |
22+
ISO-8859-16 32896 | 0.0 0.0 0.0 84.2 84.6 | 0.0 0.0 0.0 82.3 82.9 | 4.2 4.2 5.8 61.6 61.6 |
23+
ISO-8859-3 35637 | 0.0 0.0 0.0 50.9 50.9 | 0.0 0.0 0.0 53.3 53.3 | 9.5 9.5 10.2 61.8 61.8 |
24+
KOI8-R 36870 | 66.8 66.8 77.9 69.0 69.0 | 95.9 95.9 95.9 98.2 98.2 | 52.9 55.7 80.2 58.0 58.0 |
25+
KOI8-U 36845 | 0.0 59.5 73.6 19.1 19.2 | 0.0 97.1 97.1 33.0 33.1 | 59.3 64.5 82.7 65.7 65.7 |
26+
Shift_JIS 36883 | 0.0 0.0 2.7 7.0 7.6 | 66.9 66.9 66.9 74.2 74.8 | 39.4 39.4 39.5 46.7 46.7 |
27+
UTF-16-BE 36789 | 36.0 36.0 38.3 36.0 36.0 | 0.0 0.0 0.0 0.0 0.0 | 94.9 95.0 95.0 94.9 94.9 |
28+
UTF-16-LE 36730 | 36.8 36.8 39.0 36.8 36.8 | 0.0 0.0 0.0 0.0 0.0 | 94.8 94.9 94.9 94.8 94.8 |
29+
UTF-32-BE 36751 | 100.0 100.0 100.0 100.0 100.0 | 0.0 0.0 0.0 0.0 0.0 | 0.0 0.0 23.7 0.0 0.0 |
30+
UTF-32-LE 37012 | 100.0 100.0 100.0 100.0 100.0 | 0.0 0.0 0.0 0.0 0.0 | 0.0 0.0 23.1 0.0 0.0 |
31+
UTF-8 36283 | 82.3 82.3 91.8 97.7 97.7 | 82.2 82.2 82.2 98.9 98.9 | 82.6 82.6 82.6 99.3 99.3 |
32+
windows-1250 34472 | 11.1 53.5 88.8 83.8 83.9 | 0.0 0.0 0.0 58.8 62.5 | 6.9 6.9 9.9 53.2 53.4 |
33+
windows-1251 36856 | 60.4 60.6 76.1 62.2 62.2 | 74.6 74.7 74.7 76.5 76.6 | 58.8 58.8 65.7 60.6 60.6 |
34+
windows-1252 25636 | 3.8 65.2 94.7 88.2 88.2 | 0.0 98.5 98.5 93.1 98.5 | 67.5 67.5 68.8 67.5 67.6 |
35+
windows-1253 36846 | 2.0 72.1 86.9 74.6 74.6 | 0.1 89.5 89.5 89.1 92.0 | 77.3 77.3 80.7 80.5 80.5 |
36+
windows-1254 36688 | 5.3 57.8 84.0 79.3 79.3 | 0.0 0.0 0.0 39.1 43.5 | 23.3 23.3 24.4 49.3 49.3 |
37+
windows-1255 31171 | 6.9 34.4 48.5 34.8 35.9 | 93.9 95.7 95.7 96.7 97.3 | 76.0 76.0 79.0 77.3 77.3 |
38+
windows-1256 41955 | 36.2 60.3 80.0 37.6 37.6 | 0.0 0.0 0.0 1.3 1.4 | 62.2 62.2 72.4 63.5 63.5 |
39+
windows-1257 30902 | 0.0 0.0 0.0 47.4 47.6 | 0.0 0.0 0.0 43.7 49.4 | 13.9 13.9 16.2 50.3 50.4 |
40+
windows-1258 36850 | 0.0 0.0 0.0 5.3 5.4 | 0.0 0.0 0.0 5.2 5.5 | 57.3 57.3 57.4 62.5 62.5 |
41+
windows-874 31260 | 0.0 0.0 0.0 9.1 9.1 | 0.0 0.0 0.0 83.7 94.7 | 60.4 60.4 69.3 69.1 69.1 |
42+
x-EUC-TW 26571 | 0.0 0.0 0.0 4.2 4.4 | 43.8 43.8 43.8 48.1 48.3 | 83.9 83.9 85.1 88.1 88.3 |
43+
x-MacRoman 1963 | 0.0 0.0 0.0 53.9 54.2 | 0.0 0.0 0.0 54.6 54.9 | 6.0 6.0 8.3 60.3 60.8 |
44+
x-mac-cyrillic 1742 | 0.0 0.0 0.0 0.0 0.0 | 52.0 52.0 52.0 52.0 52.0 | 36.0 36.0 39.1 36.0 36.0 |
45+
x-windows-949 36726 | 0.0 0.0 28.6 2.8 3.0 | 0.0 83.7 83.7 86.5 86.8 | 74.8 74.8 91.2 77.7 77.9 |
46+
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
47+
OVERALL 1220660 | 20.5 42.2 52.9 42.3 42.5 | 22.0 33.4 33.4 45.8 46.8 | 47.8 50.1 55.1 61.0 61.0 |
48+
Stat=model only | +ISO=+C1-correction | +CJK=+grammar | All=ML+rules | R%=strict | S%=soft | T3%=top-3 hit | D%=decode-match | A%=alpha-match
49+
µs/sample | 26.1 | 7.5 | 2.6 |
50+

0 commit comments

Comments
 (0)