|
25 | 25 | import java.nio.file.Files; |
26 | 26 | import java.nio.file.Path; |
27 | 27 | import java.util.ArrayList; |
| 28 | +import java.util.Arrays; |
28 | 29 | import java.util.List; |
29 | 30 | import java.util.Locale; |
| 31 | +import java.util.Map; |
30 | 32 | import java.util.regex.Matcher; |
31 | 33 | import java.util.regex.Pattern; |
32 | 34 |
|
@@ -159,6 +161,9 @@ public List<Metadata> loadExtract(Path extractFile) throws ExtractReaderExceptio |
159 | 161 | try { |
160 | 162 | if (fileSuffixes.format == FileSuffixes.FORMAT.JSON) { |
161 | 163 | metadataList = JsonMetadataList.fromJson(reader); |
| 164 | + for (Metadata m : metadataList) { |
| 165 | + normalizeLegacyKeys(m); |
| 166 | + } |
162 | 167 | if (alterMetadataList.equals(ALTER_METADATA_LIST.FIRST_ONLY) && metadataList.size() > 1) { |
163 | 168 | while (metadataList.size() > 1) { |
164 | 169 | metadataList.remove(metadataList.size() - 1); |
@@ -214,6 +219,77 @@ private List<Metadata> generateListFromTextFile(Reader reader, FileSuffixes file |
214 | 219 |
|
215 | 220 | } |
216 | 221 |
|
| 222 | + // Pre-4.0 extract key -> 4.0 key, for the Tika-native fields tika-eval reads. Digest keys are |
| 223 | + // handled by the prefix rule in normalizeLegacyKeys; Content-Type/Content-Length are standard |
| 224 | + // names (unchanged) so they are not listed. New-side keys come from the live constants so this |
| 225 | + // can't drift from the 4.0 declarations. |
| 226 | + private static final Map<String, String> LEGACY_KEY_MAP = Map.ofEntries( |
| 227 | + Map.entry("X-TIKA:content", TikaCoreProperties.TIKA_CONTENT.getName()), |
| 228 | + Map.entry("X-TIKA:content_handler", TikaCoreProperties.TIKA_CONTENT_HANDLER.getName()), |
| 229 | + Map.entry("X-TIKA:embedded_depth", TikaCoreProperties.EMBEDDED_DEPTH.getName()), |
| 230 | + Map.entry("X-TIKA:embedded_resource_path", TikaCoreProperties.EMBEDDED_RESOURCE_PATH.getName()), |
| 231 | + Map.entry("X-TIKA:final_embedded_resource_path", TikaCoreProperties.FINAL_EMBEDDED_RESOURCE_PATH.getName()), |
| 232 | + Map.entry("X-TIKA:parse_time_millis", TikaCoreProperties.PARSE_TIME_MILLIS.getName()), |
| 233 | + Map.entry("X-TIKA:resourceName", TikaCoreProperties.RESOURCE_NAME_KEY.getName()), |
| 234 | + Map.entry("X-TIKA:detectedEncoding", TikaCoreProperties.DETECTED_ENCODING.getName()), |
| 235 | + Map.entry("X-TIKA:encodingDetector", TikaCoreProperties.ENCODING_DETECTOR.getName()), |
| 236 | + Map.entry("Content-Type-Hint", TikaCoreProperties.CONTENT_TYPE_HINT.getName()), |
| 237 | + Map.entry("embeddedResourceType", TikaCoreProperties.EMBEDDED_RESOURCE_TYPE.getName()), |
| 238 | + Map.entry("X-TIKA:EXCEPTION:container_exception", TikaCoreProperties.CONTAINER_EXCEPTION.getName()), |
| 239 | + Map.entry("X-TIKA:EXCEPTION:embedded_exception", TikaCoreProperties.EMBEDDED_EXCEPTION.getName())); |
| 240 | + |
| 241 | + private static final String LEGACY_DIGEST_PREFIX = TikaCoreProperties.LEGACY_TIKA_META_PREFIX |
| 242 | + + "digest" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; |
| 243 | + |
| 244 | + /** |
| 245 | + * Pre-4.0 extracts (e.g. 4.0.0-beta-1) key Tika-native fields under X-TIKA:/camelCase names. |
| 246 | + * Normalize the fields tika-eval reads to their 4.0 tk: keys so a cross-version compare reflects |
| 247 | + * real diffs, not the rename. Harmless on 4.0 extracts: the legacy keys are simply absent. |
| 248 | + */ |
| 249 | + private static void normalizeLegacyKeys(Metadata m) { |
| 250 | + m.setTrusted(true); // sanctioned trusted transformation: may write reserved tk: keys |
| 251 | + try { |
| 252 | + for (Map.Entry<String, String> e : LEGACY_KEY_MAP.entrySet()) { |
| 253 | + remapLegacyKey(m, e.getKey(), e.getValue()); |
| 254 | + } |
| 255 | + // digest keys: X-TIKA:digest:<alg> -> tk:digest:<alg> (algorithm unchanged; MD5 drives |
| 256 | + // embedded-doc matching). names() is a snapshot, so remapping while iterating is safe. |
| 257 | + for (String name : m.names()) { |
| 258 | + if (name.startsWith(LEGACY_DIGEST_PREFIX)) { |
| 259 | + remapLegacyKey(m, name, TikaCoreProperties.TIKA_META_PREFIX |
| 260 | + + name.substring(TikaCoreProperties.LEGACY_TIKA_META_PREFIX.length())); |
| 261 | + } |
| 262 | + } |
| 263 | + } finally { |
| 264 | + m.setTrusted(false); |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + private static void remapLegacyKey(Metadata m, String legacyKey, String modernKey) { |
| 269 | + String[] legacyVals = m.getValues(legacyKey); |
| 270 | + if (legacyVals.length == 0) { |
| 271 | + return; |
| 272 | + } |
| 273 | + String[] modernVals = m.getValues(modernKey); |
| 274 | + if (modernVals.length > 0) { |
| 275 | + // Both present: safe only if identical. Fail loud rather than silently clobber a value. |
| 276 | + if (!Arrays.equals(legacyVals, modernVals)) { |
| 277 | + throw new IllegalStateException("Extract has both legacy key '" + legacyKey |
| 278 | + + "' and modern key '" + modernKey + "' with different values; legacy-key " |
| 279 | + + "normalization would clobber. Extract is inconsistent."); |
| 280 | + } |
| 281 | + } else { |
| 282 | + for (int i = 0; i < legacyVals.length; i++) { |
| 283 | + if (i == 0) { |
| 284 | + m.set(modernKey, legacyVals[i]); |
| 285 | + } else { |
| 286 | + m.add(modernKey, legacyVals[i]); |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | + m.remove(legacyKey); |
| 291 | + } |
| 292 | + |
217 | 293 | public enum ALTER_METADATA_LIST { |
218 | 294 | AS_IS, //leave the metadata list as is |
219 | 295 | FIRST_ONLY, //take only the metadata list for the "container" document |
|
0 commit comments