This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch metadata-key-passthrough in repository https://gitbox.apache.org/repos/asf/tika.git
commit e8f366f867d68df002e89cbdf37521af2b3f0ae9 Author: tallison <[email protected]> AuthorDate: Mon Jul 27 16:00:36 2026 -0400 property prefix --- .../main/java/org/apache/tika/metadata/DWG.java | 3 + .../main/java/org/apache/tika/metadata/HTML.java | 3 + .../main/java/org/apache/tika/metadata/MAPI.java | 1 + .../java/org/apache/tika/metadata/Message.java | 3 + .../main/java/org/apache/tika/metadata/Office.java | 3 + .../main/java/org/apache/tika/metadata/PDF.java | 3 + .../apache/tika/metadata/PassthroughPrefix.java | 79 +++++++++++ tika-metadata-schema/README.md | 41 ++++-- .../tika/metadata/schema/SchemaGenerator.java | 33 ++++- .../tika/metadata/metadata-open-namespaces.json | 60 +++------ .../tika/metadata/schema/MetadataCoverageTest.java | 150 +++++++++++++++++++++ .../tika/metadata/schema/MetadataSchemaTest.java | 37 +++-- .../apache/tika/parser/envi/EnviHeaderParser.java | 11 +- .../org/apache/tika/parser/grib/GribParser.java | 5 +- .../apache/tika/parser/netcdf/NetCDFParser.java | 8 +- .../tika/parser/ctakes/CTAKESContentHandler.java | 7 +- .../tika/parser/journal/GrobidRESTParser.java | 6 +- .../apache/tika/parser/ner/NamedEntityParser.java | 5 +- .../tika/parser/iwork/NumbersContentHandler.java | 6 +- .../parser/iwork/iwana/IWork13PackageParser.java | 5 +- .../org/apache/tika/parser/ogg/OggAudioParser.java | 6 +- .../org/apache/tika/parser/video/FLVParser.java | 5 +- .../java/org/apache/tika/parser/dwg/DWGParser.java | 9 +- .../org/apache/tika/parser/dwg/DWGReadParser.java | 4 +- .../tika/parser/image/ImageMetadataExtractor.java | 11 +- .../tika/parser/mail/MailContentHandler.java | 4 +- .../org/apache/tika/parser/mbox/MboxParser.java | 5 +- .../tika/parser/microsoft/JackcessExtractor.java | 6 +- .../tika/parser/microsoft/JackcessParser.java | 7 + .../tika/parser/microsoft/SummaryExtractor.java | 3 +- .../microsoft/msg/ExtendedMetadataExtractor.java | 2 +- .../tika/parser/odf/OpenDocumentMetaParser.java | 2 +- .../java/org/apache/tika/parser/pdf/PDFParser.java | 2 +- .../org/apache/tika/parser/warc/WARCParser.java | 10 +- .../org/apache/tika/parser/xmp/XmpExtractor.java | 4 +- 35 files changed, 443 insertions(+), 106 deletions(-) diff --git a/tika-core/src/main/java/org/apache/tika/metadata/DWG.java b/tika-core/src/main/java/org/apache/tika/metadata/DWG.java index 99f5946c70..6d53912dd8 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/DWG.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/DWG.java @@ -23,6 +23,9 @@ public interface DWG { String DWG_PREFIX = "dwg" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + PassthroughPrefix RAW_FIELD = + PassthroughPrefix.file(DWG_PREFIX, "DWGRead JSON header/summary field names"); + Property APPLICATION_NAME = Property.externalText(DWG_PREFIX + "applicationName"); Property APPLICATION_VERSION = Property.externalText(DWG_PREFIX + "applicationVersion"); diff --git a/tika-core/src/main/java/org/apache/tika/metadata/HTML.java b/tika-core/src/main/java/org/apache/tika/metadata/HTML.java index a25b949302..a26c979479 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/HTML.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/HTML.java @@ -19,6 +19,9 @@ package org.apache.tika.metadata; public interface HTML { String PREFIX_HTML_META = "html" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + PassthroughPrefix SCRAPED_META = PassthroughPrefix.file(PREFIX_HTML_META, + "scraped <meta>/http-equiv/OpenGraph names not mapped to a Property"); + /** * If a script element contains a src value, this value diff --git a/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java b/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java index c8f81a980d..92722afdbd 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java @@ -27,6 +27,7 @@ public interface MAPI { String PREFIX_MAPI_META = "mapi" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; String PREFIX_MAPI_ATTACH_META = "mapi:attach" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; String PREFIX_MAPI_PROPERTY = PREFIX_MAPI_META + "property" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + PassthroughPrefix PROPERTY = PassthroughPrefix.file(PREFIX_MAPI_PROPERTY, "MAPI named properties"); /** * MAPI message class. What type of .msg/MAPI file is it? diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Message.java b/tika-core/src/main/java/org/apache/tika/metadata/Message.java index fcb1421f3c..bee626e0f6 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/Message.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/Message.java @@ -27,6 +27,9 @@ public interface Message { String MESSAGE_RAW_HEADER_PREFIX = MESSAGE_PREFIX + "Raw-Header" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + PassthroughPrefix RAW_HEADER = PassthroughPrefix.file(MESSAGE_RAW_HEADER_PREFIX, + "RFC822 / Outlook raw email header names"); + String MESSAGE_RECIPIENT_ADDRESS = "Message-Recipient-Address"; String MESSAGE_FROM = "Message-From"; diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Office.java b/tika-core/src/main/java/org/apache/tika/metadata/Office.java index 51e4ebb0d9..7240e241dc 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/Office.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/Office.java @@ -37,6 +37,9 @@ public interface Office { */ String USER_DEFINED_METADATA_NAME_PREFIX = "custom:"; + PassthroughPrefix USER_DEFINED = PassthroughPrefix.file(USER_DEFINED_METADATA_NAME_PREFIX, + "OOXML/OLE2/ODF user-defined document properties"); + /** * Keywords pertaining to a document. Also populates {@link DublinCore#SUBJECT}. diff --git a/tika-core/src/main/java/org/apache/tika/metadata/PDF.java b/tika-core/src/main/java/org/apache/tika/metadata/PDF.java index 1f6d9c3284..34df110e43 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/PDF.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/PDF.java @@ -51,6 +51,9 @@ public interface PDF { String PDF_DOC_INFO_CUSTOM_PREFIX = PDF_DOC_INFO_PREFIX + "custom" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + PassthroughPrefix DOC_INFO_CUSTOM = + PassthroughPrefix.file(PDF_DOC_INFO_CUSTOM_PREFIX, "PDF Info-dict custom keys"); + Property DOC_INFO_CREATED = Property.internalDate(PDF_DOC_INFO_PREFIX + "created"); Property DOC_INFO_CREATOR = Property.internalText(PDF_DOC_INFO_PREFIX + "creator"); diff --git a/tika-core/src/main/java/org/apache/tika/metadata/PassthroughPrefix.java b/tika-core/src/main/java/org/apache/tika/metadata/PassthroughPrefix.java new file mode 100644 index 0000000000..ea17d3b555 --- /dev/null +++ b/tika-core/src/main/java/org/apache/tika/metadata/PassthroughPrefix.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.metadata; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A Tika-owned prefix under which keys are passed through from the source: the prefix is fixed, but + * each key name comes verbatim from the document or tool, so keys are unbounded and can't be + * {@link Property} constants. Declaring one self-registers it, so the open set is enumerable (as + * {@link Property} makes the closed set) and lintable: a String write is legitimate iff its key is a + * registered {@link Property} or its prefix is a registered {@code PassthroughPrefix}. + * + * @since Apache Tika 4.0.0 + */ +public final class PassthroughPrefix { + + public enum Provenance { FILE, TOOL } + + private static final Map<String, PassthroughPrefix> REGISTRY = new ConcurrentHashMap<>(); + + private final String prefix; + private final Provenance provenance; + private final String description; + + private PassthroughPrefix(String prefix, Provenance provenance, String description) { + this.prefix = prefix; + this.provenance = provenance; + this.description = description; + REGISTRY.put(prefix, this); + } + + public static PassthroughPrefix file(String prefix, String description) { + return new PassthroughPrefix(prefix, Provenance.FILE, description); + } + + public static PassthroughPrefix tool(String prefix, String description) { + return new PassthroughPrefix(prefix, Provenance.TOOL, description); + } + + /** The full key for a source-derived {@code suffix}. */ + public String key(String suffix) { + return prefix + suffix; + } + + public String prefix() { + return prefix; + } + + public Provenance provenance() { + return provenance; + } + + public String description() { + return description; + } + + /** Declared prefixes, from loaded classes only. */ + public static Collection<PassthroughPrefix> registered() { + return Collections.unmodifiableCollection(REGISTRY.values()); + } +} diff --git a/tika-metadata-schema/README.md b/tika-metadata-schema/README.md index e58f2a7083..105b4de77a 100644 --- a/tika-metadata-schema/README.md +++ b/tika-metadata-schema/README.md @@ -17,7 +17,14 @@ # tika-metadata-schema A machine-readable schema of Apache Tika's metadata keys. Two registries, because Tika has two -kinds of keys: +kinds of keys. + +**Scope: `tika-core` + the standard parser bundle.** The heavier/optional parser families +(scientific, sqlite3, nlp, vlm) are *not* scanned — pulling their runtime deps (netcdf, grib, +opennlp, DL4J, sqlite-jdbc) into a build-time schema module isn't worth it. Their keys are the only +ones absent (e.g. `sqlite3:`, `vlm:`, `grib:`, `netcdf:`, `ctakes:`, `NER_`). `MetadataCoverageTest` +enforces this: any module declaring keys that is neither scanned nor on its explicit out-of-scope +list fails the build, so nothing escapes *silently*. ## `metadata-keys.json` — the closed set (generated + gated) Every key Tika declares as a `Property` constant, plus the bounded digest cross-product @@ -29,22 +36,30 @@ declare a `Property` field, force-loads them, reads the global `Property` table, sorted JSON. `MetadataSchemaTest` regenerates in-memory and asserts it matches the committed file, so the registry can never drift from the declarations. -Regenerate after adding/changing a `Property`: +Regenerate after adding/changing a `Property` **or** a `PassthroughPrefix` (writes both files): ``` java -cp <tika-metadata-schema + deps classpath> \ org.apache.tika.metadata.schema.SchemaGenerator \ - src/main/resources/org/apache/tika/metadata/metadata-keys.json + src/main/resources/org/apache/tika/metadata/metadata-keys.json \ + src/main/resources/org/apache/tika/metadata/metadata-open-namespaces.json ``` -## `metadata-open-namespaces.json` — the open sets (curated) -Keys minted at **runtime** whose names are not `Property` constants, so they cannot be generated: -- **open/passthrough namespaces** — file-controlled key names (scraped HTML `<meta>` under `html:`, - OOXML `custom:`, email `Message:Raw-Header:`, Access `MDB_PROP:`, Vorbis comments, GRIB/NetCDF/FLV - attributes, …); -- **templates** — e.g. XMP `rdf:Alt` language variants `<base-key>:<lang>` (`dc:title:fr`). +## `metadata-open-namespaces.json` — the open sets (generated + gated) +The **prefixes** under which parsers mint file-controlled key names at runtime — names that are not +`Property` constants, so the individual keys cannot be enumerated (scraped HTML `<meta>` under +`html:`, OOXML `custom:`, email `Message:Raw-Header:`, Access `MDB_PROP:`, Vorbis comments, FLV +attributes, unmapped image/XMP tags, …). Each record: `{ prefix, provenance, description }`. + +**Generated from the `PassthroughPrefix` declarations, never hand-edited.** Every such prefix is a +registered `PassthroughPrefix` constant; `SchemaGenerator` reads that registry the same way it reads +the `Property` table, and `MetadataSchemaTest` gates it identically. Adding a passthrough prefix in a +parser and forgetting to regenerate fails the build. -**Curated (hand-maintained, reviewed), not generated**, and possibly not exhaustive; the -closed-namespace lint (a follow-up) is the intended completeness backstop. +Not covered here: **templates** — parameterized key families like XMP `rdf:Alt` language variants +`<base-key>:<lang>` (`dc:title:fr`), where the *suffix* rather than the prefix is open. These are +documented by rule, not enumerated. -Together the two files describe the whole key space: closed keys are enumerated and gated; open keys -are described by rule. +Together the files describe the key space of the scanned bundle: closed keys are enumerated and +gated; open namespaces are enumerated by prefix and gated; templates are described by rule; and +`MetadataCoverageTest` guarantees no scanned-bundle module is silently missed. Keys from the +out-of-scope families above are excluded by design. diff --git a/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java b/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java index 4b4d189491..cbfd598c7c 100644 --- a/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java +++ b/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java @@ -29,6 +29,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.tika.digest.DigestDef; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Property; /** @@ -42,9 +43,11 @@ import org.apache.tika.metadata.Property; */ public final class SchemaGenerator { - // Field/parameter descriptor for org.apache.tika.metadata.Property in a .class constant pool. + // Field/parameter descriptors in a .class constant pool: a class referencing one is force-loaded. private static final byte[] PROP_DESC = "Lorg/apache/tika/metadata/Property;".getBytes(StandardCharsets.ISO_8859_1); + private static final byte[] PASSTHROUGH_DESC = + "Lorg/apache/tika/metadata/PassthroughPrefix;".getBytes(StandardCharsets.ISO_8859_1); private SchemaGenerator() { } @@ -105,7 +108,8 @@ public final class SchemaGenerator { } private static void maybeLoad(String classPath, byte[] bytes, ClassLoader cl) { - if (!classPath.startsWith("org/apache/tika/") || !contains(bytes, PROP_DESC)) { + if (!classPath.startsWith("org/apache/tika/") + || (!contains(bytes, PROP_DESC) && !contains(bytes, PASSTHROUGH_DESC))) { return; } String cn = classPath.substring(0, classPath.length() - 6).replace('/', '.'); @@ -149,9 +153,28 @@ public final class SchemaGenerator { return '"' + s.replace("\\", "\\\\").replace("\"", "\\\"") + '"'; } + /** Declared passthrough prefixes as stable JSON. Call after {@link #generate()} has loaded classes. */ + public static String passthroughJson() { + TreeMap<String, String[]> m = new TreeMap<>(); + for (PassthroughPrefix p : PassthroughPrefix.registered()) { + m.put(p.prefix(), new String[]{p.provenance().name(), p.description()}); + } + StringBuilder sb = new StringBuilder("[\n"); + int i = 0; + int n = m.size(); + for (Map.Entry<String, String[]> e : m.entrySet()) { + sb.append(" {\"prefix\":").append(quote(e.getKey())) + .append(",\"provenance\":\"").append(e.getValue()[0]) + .append("\",\"description\":").append(quote(e.getValue()[1])) + .append("}").append(++i < n ? "," : "").append('\n'); + } + return sb.append("]\n").toString(); + } + public static void main(String[] args) throws Exception { - Path out = Path.of(args[0]); - Files.writeString(out, generate()); - System.out.println("wrote " + out); + Files.writeString(Path.of(args[0]), generate()); // triggers the classpath scan + if (args.length > 1) { + Files.writeString(Path.of(args[1]), passthroughJson()); + } } } diff --git a/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-open-namespaces.json b/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-open-namespaces.json index 40eae02925..e2360ae6af 100644 --- a/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-open-namespaces.json +++ b/tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-open-namespaces.json @@ -1,38 +1,22 @@ -{ - "_note": "Open / passthrough namespaces and templates. These keys are minted at RUNTIME (their names come from the document, an external tool, or a per-item qualifier), so they are NOT registered Property constants and do NOT appear in metadata-keys.json. This file is CURATED (hand-maintained, reviewed), not generated, and may not be exhaustive; the closed-namespace lint is the intended completeness backstop. provenance: 'file' = key name taken from document content; 'tool' = key name [...] - - "open-namespaces": [ - {"prefix": "html:", "provenance": "file", "note": "scraped HTML <meta>/http-equiv/OpenGraph names"}, - {"prefix": "custom:", "provenance": "file", "note": "OOXML/OLE2/ODF user-defined document properties"}, - {"prefix": "pdf:docinfo:custom:", "provenance": "file", "note": "PDF Info-dictionary custom keys"}, - {"prefix": "Message:Raw-Header:", "provenance": "file", "note": "RFC822 / Outlook raw email header names"}, - {"prefix": "MboxParser-", "provenance": "file", "note": "mbox header names"}, - {"prefix": "warc:", "provenance": "file", "note": "WARC record + HTTP response header names (also warc:http:)"}, - {"prefix": "mapi:property:", "provenance": "file", "note": "MSG MAPI named properties"}, - {"prefix": "MDB_PROP:", "provenance": "file", "note": "MS Access document properties (also MDB_SUMMARY_PROP:, MDB_USER_PROP:)"}, - {"prefix": "img:", "provenance": "file", "note": "ImageIO/drewnoakes unrecognized image tag names (also ICC:)"}, - {"prefix": "dwg-custom:", "provenance": "file", "note": "DWG custom properties (DWGParser)"}, - {"prefix": "dwg:", "provenance": "file", "note": "DWGRead JSON field passthrough"}, - {"prefix": "iworks:", "provenance": "file", "note": "iWork '13 package plist keys"}, - {"prefix": "numbers:", "provenance": "file", "note": "iWork Numbers element names"}, - {"prefix": "flv:", "provenance": "file", "note": "FLV onMetaData ECMA-array keys"}, - {"prefix": "vorbis:", "provenance": "file", "note": "Vorbis/FLAC/Opus/Speex comment field names"}, - {"prefix": "streams-", "provenance": "file", "note": "Ogg per-stream metadata"}, - {"prefix": "grib:", "provenance": "file", "note": "GRIB global attribute names"}, - {"prefix": "netcdf:", "provenance": "file", "note": "NetCDF global attribute names"}, - {"prefix": "envi.", "provenance": "file", "note": "ENVI header field names"}, - {"prefix": "rtf_pict:", "provenance": "file", "note": "RTF embedded-object \\sn/\\sv keys"}, - {"prefix": "grobid:header_", "provenance": "tool", "note": "GROBID TEI header fields"}, - {"prefix": "NER_", "provenance": "tool", "note": "named-entity types (NamedEntityParser)"}, - {"prefix": "ctakes:", "provenance": "tool", "note": "cTAKES/UIMA type names"} - ], - - "templates": [ - { - "pattern": "<base-key>:<BCP-47-language-tag>", - "provenance": "file", - "note": "XMP rdf:Alt language variants, e.g. dc:title:x-default, dc:title:fr. The unqualified base key holds the x-default value; to type a variant, resolve to its base key. NOTE: the language qualifier reuses ':' so 'dc:title:fr' is only disambiguated from a sub-namespace by this rule.", - "example-base-keys": ["dc:title", "dc:description", "dc:rights", "xmpRights:UsageTerms"] - } - ] -} +[ + {"prefix":"ICC:","provenance":"FILE","description":"unrecognized ICC profile tag names"}, + {"prefix":"MDB_PROP:","provenance":"FILE","description":"MS Access database properties"}, + {"prefix":"MDB_SUMMARY_PROP:","provenance":"FILE","description":"MS Access summary properties"}, + {"prefix":"MDB_USER_PROP:","provenance":"FILE","description":"MS Access user-defined properties"}, + {"prefix":"MboxParser-","provenance":"FILE","description":"mbox email header names"}, + {"prefix":"Message:Raw-Header:","provenance":"FILE","description":"RFC822 / Outlook raw email header names"}, + {"prefix":"custom:","provenance":"FILE","description":"OOXML/OLE2/ODF user-defined document properties"}, + {"prefix":"dwg-custom:","provenance":"FILE","description":"DWG custom document properties"}, + {"prefix":"dwg:","provenance":"FILE","description":"DWGRead JSON header/summary field names"}, + {"prefix":"flv:","provenance":"FILE","description":"FLV onMetaData tag keys"}, + {"prefix":"html:","provenance":"FILE","description":"scraped <meta>/http-equiv/OpenGraph names not mapped to a Property"}, + {"prefix":"img:","provenance":"FILE","description":"unrecognized image tag names"}, + {"prefix":"iworks:","provenance":"FILE","description":"iWork '13 plist document properties"}, + {"prefix":"mapi:property:","provenance":"FILE","description":"MAPI named properties"}, + {"prefix":"numbers:","provenance":"FILE","description":"iWork Numbers document metadata element names"}, + {"prefix":"pdf:docinfo:custom:","provenance":"FILE","description":"PDF Info-dict custom keys"}, + {"prefix":"vorbis:","provenance":"FILE","description":"Vorbis comment field names"}, + {"prefix":"warc:","provenance":"FILE","description":"WARC record header names"}, + {"prefix":"warc:http:","provenance":"FILE","description":"WARC HTTP response header names"}, + {"prefix":"xmp-raw:","provenance":"FILE","description":"unmapped XMP keys"} +] diff --git a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java new file mode 100644 index 0000000000..4980d770d1 --- /dev/null +++ b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tika.metadata.schema; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +/** + * Completeness backstop for the standard-scoped registry. {@link MetadataSchemaTest} proves the + * registry matches the declarations it <em>scans</em>; this proves nothing <em>escapes</em> the + * scan. It greps the source tree for every module declaring a {@code Property} or + * {@code PassthroughPrefix} and fails if one is neither on the registry classpath nor an explicit + * {@link #OUT_OF_SCOPE} family — so a new key-bearing parser cannot slip in silently. + * + * <p>Reads source (always present) for the declaring set and the runtime classpath for the scanned + * set, so it is correct in a targeted {@code -pl tika-metadata-schema} run — no full build needed. + */ +public class MetadataCoverageTest { + + /** Parser families intentionally excluded from the standard-scoped registry (heavy/optional + * deps). Adding a module here is a conscious scope decision; see README. */ + private static final Set<String> OUT_OF_SCOPE = Set.of( + "tika-parser-scientific-module", // envi. grib: netcdf: + "tika-parser-sqlite3-module", // sqlite3: + "tika-parser-nlp-module", // ctakes: grobid:header_ NER_ + "tika-vlm"); // vlm: + + // Source markers for an actual key declaration (a factory call), not a mere type reference. + private static final String[] DECL_MARKERS = { + "Property.internal", "Property.external", "Property.composite", + "PassthroughPrefix.file(", "PassthroughPrefix.tool("}; + + private static final String SRC_MAIN = sep("src", "main") + File.separator + "java"; + private static final String SRC_TEST = sep("src", "test"); + + @Test + public void everyKeyDeclaringModuleIsScannedOrExcluded() throws Exception { + Path repoRoot = repoRoot(); + assumeTrue(repoRoot != null, "repo root (tika-core + tika-parsers) not found"); + + Set<String> declaring = new TreeSet<>(); + for (String sub : new String[]{"tika-core", "tika-parsers"}) { + Path base = repoRoot.resolve(sub); + if (!Files.isDirectory(base)) { + continue; + } + try (Stream<Path> walk = Files.walk(base)) { + for (Path p : (Iterable<Path>) walk::iterator) { + String module = keyDeclaringModule(p); + if (module != null) { + declaring.add(module); + } + } + } + } + assumeTrue(!declaring.isEmpty(), "no key-declaring source found (unexpected checkout layout)"); + + Set<String> scanned = classpathModules(); + Set<String> escaped = new TreeSet<>(declaring); + escaped.removeAll(scanned); + escaped.removeAll(OUT_OF_SCOPE); + + assertTrue(escaped.isEmpty(), "Module(s) declare metadata keys but are neither scanned by " + + "the registry nor listed OUT_OF_SCOPE: " + escaped + ". Either add the module to " + + "the registry's scan classpath, or add it to OUT_OF_SCOPE (and document it)."); + } + + /** Module name if {@code p} is a main-source file declaring a key, else null. */ + private static String keyDeclaringModule(Path p) throws Exception { + String s = p.toString(); + int i = s.indexOf(SRC_MAIN); + if (!s.endsWith(".java") || i < 0 || s.contains(SRC_TEST)) { + return null; + } + String src = Files.readString(p, StandardCharsets.UTF_8); + boolean declares = false; + for (String marker : DECL_MARKERS) { + if (src.contains(marker)) { + declares = true; + break; + } + } + if (!declares) { + return null; + } + // s.substring(0, i) is the module dir itself (the part before ".../src/main/java/...") + Path moduleDir = Paths.get(s.substring(0, i)); + return moduleDir.getFileName() == null ? null : moduleDir.getFileName().toString(); + } + + /** Artifacts the registry scan sees: reactor {@code target/classes} dirs plus dependency jars + * (Tika dir name == artifactId, so both keys line up with {@link #keyDeclaringModule}). */ + private static Set<String> classpathModules() { + Set<String> mods = new TreeSet<>(); + String classes = sep("target", "classes"); + for (String e : System.getProperty("java.class.path").split(File.pathSeparator)) { + Path p = Paths.get(e); + Path mod = null; + if (e.endsWith(classes)) { + mod = p.getParent().getParent(); // <mod>/target/classes -> <mod> + } else if (e.endsWith(".jar") && p.getParent() != null) { + mod = p.getParent().getParent(); // <artifactId>/<version>/x.jar -> <artifactId> + } + if (mod != null && mod.getFileName() != null) { + mods.add(mod.getFileName().toString()); + } + } + return mods; + } + + private static Path repoRoot() { + Path p = Paths.get("").toAbsolutePath(); + while (p != null) { + if (Files.isDirectory(p.resolve("tika-core")) && Files.isDirectory(p.resolve("tika-parsers"))) { + return p; + } + p = p.getParent(); + } + return null; + } + + private static String sep(String a, String b) { + return File.separator + a + File.separator + b; + } +} diff --git a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java index eff7626df5..25f26c5fbd 100644 --- a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java +++ b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java @@ -25,24 +25,35 @@ import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; /** - * The CI gate: regenerate the registry from the live {@link org.apache.tika.metadata.Property} - * declarations and assert it matches the committed {@code metadata-keys.json}. If a Property is - * added/changed/renamed without regenerating, this fails — the registry cannot silently drift. + * The CI gate: regenerate both registries from the live {@link org.apache.tika.metadata.Property} + * and {@link org.apache.tika.metadata.PassthroughPrefix} declarations and assert each matches its + * committed file. If a declaration is added/changed/renamed without regenerating, this fails — the + * registries cannot silently drift. */ public class MetadataSchemaTest { - private static final String RESOURCE = "/org/apache/tika/metadata/metadata-keys.json"; + private static final String KEYS = "/org/apache/tika/metadata/metadata-keys.json"; + private static final String OPEN = "/org/apache/tika/metadata/metadata-open-namespaces.json"; @Test - public void committedRegistryMatchesDeclarations() throws Exception { - String committed; - try (InputStream in = MetadataSchemaTest.class.getResourceAsStream(RESOURCE)) { - assertNotNull(in, "committed " + RESOURCE + " is missing"); - committed = new String(in.readAllBytes(), StandardCharsets.UTF_8); + public void committedKeysMatchDeclarations() throws Exception { + assertEquals(committed(KEYS), SchemaGenerator.generate(), + "metadata-keys.json is stale. Run SchemaGenerator.main and commit the result."); + } + + @Test + public void committedOpenNamespacesMatchDeclarations() throws Exception { + // generate() first, so the classpath scan force-loads the PassthroughPrefix declarations. + SchemaGenerator.generate(); + assertEquals(committed(OPEN), SchemaGenerator.passthroughJson(), + "metadata-open-namespaces.json is stale. Run SchemaGenerator.main and commit the " + + "result."); + } + + private static String committed(String resource) throws Exception { + try (InputStream in = MetadataSchemaTest.class.getResourceAsStream(resource)) { + assertNotNull(in, "committed " + resource + " is missing"); + return new String(in.readAllBytes(), StandardCharsets.UTF_8); } - String regenerated = SchemaGenerator.generate(); - assertEquals(committed, regenerated, - "metadata-keys.json is stale. Run `mvn -pl tika-metadata-schema exec:java` " - + "(or SchemaGenerator.main) and commit the regenerated file."); } } diff --git a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java index 22565e5fd0..a4d902c882 100644 --- a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java +++ b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java @@ -34,6 +34,7 @@ import org.apache.tika.detect.EncodingDetector; import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.AbstractEncodingDetectorParser; import org.apache.tika.parser.ParseContext; @@ -43,6 +44,8 @@ import org.apache.tika.sax.XHTMLContentHandler; public class EnviHeaderParser extends AbstractEncodingDetectorParser { public static final String ENVI_MIME_TYPE = "application/envi.hdr"; + public static final PassthroughPrefix ENVI = + PassthroughPrefix.file("envi.", "ENVI header field names"); private static final long serialVersionUID = -1479368523072408091L; private static final Logger LOG = LoggerFactory.getLogger(EnviHeaderParser.class); private static final Set<MediaType> SUPPORTED_TYPES = @@ -118,7 +121,7 @@ public class EnviHeaderParser extends AbstractEncodingDetectorParser { if (keyValue[0].trim().equals("map info")) { String[] mapInfoValues = parseMapInfoContents(keyValue[1]); if (mapInfoValues[0].equals("UTM")) { - metadata.set("envi." + keyValue[0].trim().replace(" ", "."), + metadata.set(ENVI.key(keyValue[0].trim().replace(" ", ".")), keyValue[1].trim()); String[] latLonStringArray = convertMapInfoValuesToLatLngAndSetMetadata(mapInfoValues, metadata); @@ -128,11 +131,11 @@ public class EnviHeaderParser extends AbstractEncodingDetectorParser { xhtml.characters(xhtmlLatLongLine); xhtml.endElement("p"); } else { - metadata.set("envi." + keyValue[0].trim().replace(" ", "."), + metadata.set(ENVI.key(keyValue[0].trim().replace(" ", ".")), keyValue[1].trim()); } } else { - metadata.set("envi." + keyValue[0].trim().replace(" ", "."), + metadata.set(ENVI.key(keyValue[0].trim().replace(" ", ".")), keyValue[1].trim()); } } @@ -211,7 +214,7 @@ public class EnviHeaderParser extends AbstractEncodingDetectorParser { double zoneCM = (zone > 0) ? 6 * zone - 183.0 : 3.0; double latitude = 180.0 * (phi1 - fact1 * (fact2 + fact3 + fact4)) / Math.PI; double longitude = zoneCM - _a3; - metadata.set("envi.lat/lon", latitude + ", " + longitude); + metadata.set(ENVI.key("lat/lon"), latitude + ", " + longitude); return new String[]{Double.toString(latitude), Double.toString(longitude)}; } diff --git a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java index 1f98243e06..073eb7bf25 100644 --- a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java +++ b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java @@ -37,6 +37,7 @@ import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.ClimateForcast; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; @@ -47,6 +48,8 @@ import org.apache.tika.sax.XHTMLContentHandler; public class GribParser implements Parser { public static final String GRIB_MIME_TYPE = "application/x-grib2"; + public static final PassthroughPrefix GRIB = + PassthroughPrefix.file("grib:", "GRIB global attribute names"); private static final long serialVersionUID = 7855458954474247655L; private final Set<MediaType> SUPPORTED_TYPES = Collections.singleton(MediaType.application("x-grib2")); @@ -134,7 +137,7 @@ public class GribParser implements Parser { } else if (CF_GLOBAL_ATTRIBUTES.contains(name)) { metadata.add(name, value); } else { - metadata.add("grib:" + name, value); + metadata.add(GRIB.key(name), value); } } diff --git a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java index f40f31215c..fe2300a976 100644 --- a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java +++ b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java @@ -35,6 +35,7 @@ import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.ClimateForcast; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; @@ -56,6 +57,9 @@ public class NetCDFParser implements Parser { */ private static final long serialVersionUID = -5940938274907708665L; + public static final PassthroughPrefix NETCDF = + PassthroughPrefix.file("netcdf:", "NetCDF global attribute names"); + private final Set<MediaType> SUPPORTED_TYPES = Collections.singleton(MediaType.application("x-netcdf")); @@ -81,7 +85,7 @@ public class NetCDFParser implements Parser { ParseContext context) throws IOException, SAXException, TikaException { try (NetcdfFile ncFile = NetcdfFile.open(tis.getFile().getAbsolutePath())) { - metadata.set("netcdf:File-Type-Description", ncFile.getFileTypeDescription()); + metadata.set(NETCDF.key("File-Type-Description"), ncFile.getFileTypeDescription()); // first parse out the set of global attributes for (Attribute attr : ncFile.getGlobalAttributes()) { if (attr.getDataType().isString()) { @@ -142,7 +146,7 @@ public class NetCDFParser implements Parser { } else if (CF_GLOBAL_ATTRIBUTES.contains(name)) { metadata.add(name, value); } else { - metadata.add("netcdf:" + name, value); + metadata.add(NETCDF.key(name), value); } } } diff --git a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java index 1ee5476060..bb6ae8cdcf 100644 --- a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java +++ b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java @@ -27,6 +27,7 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.sax.ContentHandlerDecorator; /** @@ -41,6 +42,8 @@ import org.apache.tika.sax.ContentHandlerDecorator; public class CTAKESContentHandler extends ContentHandlerDecorator { // Prefix used for metadata including cTAKES annotations public static String CTAKES_META_PREFIX = "ctakes:"; + public static final PassthroughPrefix CTAKES = + PassthroughPrefix.tool(CTAKES_META_PREFIX, "cTAKES annotation type names"); // Configuration object for CTAKESContentHandler private CTAKESConfig config = null; @@ -132,7 +135,7 @@ public class CTAKESContentHandler extends ContentHandlerDecorator { ae.process(jcas); // add annotations to metadata - metadata.add(CTAKES_META_PREFIX + "schema", config.getAnnotationPropsAsString()); + metadata.add(CTAKES.key("schema"), config.getAnnotationPropsAsString()); CTAKESAnnotationProperty[] annotationPros = config.getAnnotationProps(); Collection<IdentifiedAnnotation> collection = JCasUtil.select(jcas, IdentifiedAnnotation.class); @@ -146,7 +149,7 @@ public class CTAKESContentHandler extends ContentHandlerDecorator { .append(CTAKESUtils.getAnnotationProperty(annotation, property)); } } - metadata.add(CTAKES_META_PREFIX + annotation.getType().getShortName(), + metadata.add(CTAKES.key(annotation.getType().getShortName()), annotationBuilder.toString()); } diff --git a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java index f6c761ac40..3eab5ee4de 100644 --- a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java +++ b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java @@ -35,6 +35,7 @@ import org.xml.sax.ContentHandler; import org.apache.tika.annotation.TikaComponent; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.parser.ParseContext; /** @@ -46,6 +47,9 @@ public class GrobidRESTParser { private static final Logger LOG = LoggerFactory.getLogger(GrobidRESTParser.class); + public static final PassthroughPrefix GROBID_HEADER = + PassthroughPrefix.tool("grobid:header_", "GROBID-inferred header fields"); + private static final String GROBID_REST_HOST = "http://localhost:8070"; private static final String GROBID_ISALIVE_PATH = "/api/isalive"; private static final String GROBID_PROCESSHEADER_PATH = "/api/processHeaderDocument"; @@ -110,7 +114,7 @@ public class GrobidRESTParser { String resp = response.readEntity(String.class); Metadata teiMet = new TEIDOMParser().parse(resp, context); for (String key : teiMet.names()) { - metadata.add("grobid:header_" + key, teiMet.get(key)); + metadata.add(GROBID_HEADER.key(key), teiMet.get(key)); } } catch (Exception e) { LOG.warn("Couldn't read response", e); diff --git a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java index 31ca528f5e..52df871ded 100644 --- a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java +++ b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java @@ -38,6 +38,7 @@ import org.apache.tika.config.loader.TikaLoader; import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; @@ -61,6 +62,8 @@ public class NamedEntityParser implements Parser { public static final Logger LOG = LoggerFactory.getLogger(NamedEntityParser.class); public static final Set<MediaType> MEDIA_TYPES = new HashSet<>(); public static final String MD_KEY_PREFIX = "NER_"; + public static final PassthroughPrefix NER = + PassthroughPrefix.tool(MD_KEY_PREFIX, "named-entity types"); public static final String DEFAULT_NER_IMPL = OpenNLPNERecogniser.class.getName() + "," + RegexNERecogniser.class.getName(); public static final String SYS_PROP_NER_IMPL = "ner.impl.class"; @@ -137,7 +140,7 @@ public class NamedEntityParser implements Parser { if (names != null) { for (Map.Entry<String, Set<String>> entry : names.entrySet()) { if (entry.getValue() != null) { - String mdKey = MD_KEY_PREFIX + entry.getKey(); + String mdKey = NER.key(entry.getKey()); for (String name : entry.getValue()) { metadata.add(mdKey, name); } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java index bb5ffa52a0..87237ffca9 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java @@ -25,11 +25,15 @@ import org.xml.sax.helpers.DefaultHandler; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Office; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.sax.XHTMLContentHandler; class NumbersContentHandler extends DefaultHandler { + private static final PassthroughPrefix NUMBERS_METADATA = + PassthroughPrefix.file("numbers:", "iWork Numbers document metadata element names"); + private final XHTMLContentHandler xhtml; private final Metadata metadata; @@ -226,7 +230,7 @@ class NumbersContentHandler extends DefaultHandler { } else if ("comment".equals(localName)) { metadata.add(TikaCoreProperties.COMMENTS, value); } else { - metadata.add("numbers:" + localName, value); + metadata.add(NUMBERS_METADATA.key(localName), value); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java index 4be10adc95..1ffb5fa5ec 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java @@ -45,6 +45,7 @@ import org.apache.tika.extractor.EmbeddedDocumentUtil; import org.apache.tika.io.TemporaryResources; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; @@ -66,6 +67,8 @@ public class IWork13PackageParser implements Parser { Property.externalText(IWORKS_PREFIX + "document-id"); public static final Property IWORKS_BUILD_VERSION_HISTORY = Property.externalTextBag(IWORKS_PREFIX + "build-version-history"); + public static final PassthroughPrefix IWORKS_PROPERTIES = + PassthroughPrefix.file(IWORKS_PREFIX, "iWork '13 plist document properties"); private final static Set<MediaType> supportedTypes = Collections.unmodifiableSet( @@ -236,7 +239,7 @@ public class IWork13PackageParser implements Parser { NSDictionary dict = (NSDictionary)rootObj; for (String k : dict.keySet()) { String v = dict.get(k).toString(); - metadata.set(IWORKS_PREFIX + k, v); + metadata.set(IWORKS_PROPERTIES.key(k), v); } } } catch (SecurityException e) { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java index a10238c933..c590e43400 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java @@ -34,6 +34,7 @@ import org.xml.sax.SAXException; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Audio; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.metadata.XMP; import org.apache.tika.metadata.XMPDM; @@ -48,6 +49,9 @@ import org.apache.tika.sax.XHTMLContentHandler; public abstract class OggAudioParser extends AbstractParser { private static final long serialVersionUID = 5168743829615945633L; + private static final PassthroughPrefix VORBIS = + PassthroughPrefix.file("vorbis:", "Vorbis comment field names"); + /** * Returns the first positive integer found under the given comment keys, @@ -118,7 +122,7 @@ public abstract class OggAudioParser extends AbstractParser { for (String key : comments.getAllComments().keySet()) { if (!done.contains(key)) { for (String value : comments.getAllComments().get(key)) { - metadata.add("vorbis:" + key, value); + metadata.add(VORBIS.key(key), value); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java index a3c36ebeea..2feb07cad1 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java @@ -36,6 +36,7 @@ import org.apache.tika.annotation.TikaComponent; import org.apache.tika.exception.TikaException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; @@ -76,6 +77,8 @@ public class FLVParser implements Parser { private static int TYPE_METADATA = 0x12; private static byte MASK_AUDIO = 1; private static byte MASK_VIDEO = 4; + private static final PassthroughPrefix FLV = + PassthroughPrefix.file("flv:", "FLV onMetaData tag keys"); public Set<MediaType> getSupportedTypes(ParseContext context) { return SUPPORTED_TYPES; @@ -245,7 +248,7 @@ public class FLVParser implements Parser { if (entry.getValue() == null) { continue; } - metadata.set("flv:" + entry.getKey(), entry.getValue().toString()); + metadata.set(FLV.key(entry.getKey()), entry.getValue().toString()); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java index b5db58cd32..a03578d0eb 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java @@ -32,6 +32,7 @@ import org.apache.tika.exception.TikaException; import org.apache.tika.io.EndianUtils; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; @@ -60,6 +61,8 @@ public class DWGParser extends AbstractDWGParser { } public static String DWG_CUSTOM_META_PREFIX = "dwg-custom:"; + public static final PassthroughPrefix DWG_CUSTOM = + PassthroughPrefix.file(DWG_CUSTOM_META_PREFIX, "DWG custom document properties"); /** * Serial version UID */ @@ -169,7 +172,7 @@ public class DWGParser extends AbstractDWGParser { String propName = read2004String(tis); String propValue = read2004String(tis); if (propName.length() > 0 && propValue.length() > 0) { - metadata.add(DWG_CUSTOM_META_PREFIX + propName, propValue); + metadata.add(DWG_CUSTOM.key(propName), propValue); } } } @@ -204,7 +207,7 @@ public class DWGParser extends AbstractDWGParser { String propName = read2007and2010String(tis); String propValue = read2007and2010String(tis); if (propName.length() > 0 && propValue.length() > 0) { - metadata.add(DWG_CUSTOM_META_PREFIX + propName, propValue); + metadata.add(DWG_CUSTOM.key(propName), propValue); } } } @@ -255,7 +258,7 @@ public class DWGParser extends AbstractDWGParser { if (splitAt > -1) { String propName = val.substring(0, splitAt); String propVal = val.substring(splitAt + 1); - metadata.add(DWGParser.DWG_CUSTOM_META_PREFIX + propName, propVal); + metadata.add(DWG_CUSTOM.key(propName), propVal); } } } else { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java index f774723486..3ef16fe2cf 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java @@ -294,7 +294,7 @@ public class DWGReadParser extends AbstractDWGParser { if (nextToken.isStructStart()) { jsonParser.skipChildren(); } else if (nextToken.isScalarValue()) { - metadata.set("dwg:" + nextFieldName, jsonParser.getText()); + metadata.set(DWG.RAW_FIELD.key(nextFieldName), jsonParser.getText()); } } } @@ -347,7 +347,7 @@ public class DWGReadParser extends AbstractDWGParser { } else if ("HYPERLINKBASE".equals(nextFieldName)) { metadata.set(TikaCoreProperties.RELATION, textVal); } else if (!Strings.CI.startsWith(nextFieldName, "unknown")) { - metadata.set("dwg:" + nextFieldName, textVal); + metadata.set(DWG.RAW_FIELD.key(nextFieldName), textVal); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java index 12527c4734..4008c39910 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java @@ -73,6 +73,7 @@ import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Geographic; import org.apache.tika.metadata.IPTC; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TIFF; import org.apache.tika.metadata.TikaCoreProperties; @@ -92,6 +93,10 @@ public class ImageMetadataExtractor { public static final String UNKNOWN_IMG_NS = "img" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; public static final String ICC_NS = "ICC" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + public static final PassthroughPrefix UNKNOWN_IMG = + PassthroughPrefix.file(UNKNOWN_IMG_NS, "unrecognized image tag names"); + public static final PassthroughPrefix ICC = + PassthroughPrefix.file(ICC_NS, "unrecognized ICC profile tag names"); private final Metadata metadata; private DirectoryHandler[] handlers; @@ -292,11 +297,11 @@ public class ImageMetadataExtractor { value = Boolean.FALSE.toString(); } if (directory instanceof ExifDirectoryBase) { - metadata.set(UNKNOWN_IMG_NS + directory.getName() + ":" + name, value); + metadata.set(UNKNOWN_IMG.key(directory.getName() + ":" + name), value); } else if (directory instanceof IccDirectory) { - metadata.set(ICC_NS + name, value); + metadata.set(ICC.key(name), value); } else { - metadata.set(UNKNOWN_IMG_NS + name, value); + metadata.set(UNKNOWN_IMG.key(name), value); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mail/MailContentHandler.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mail/MailContentHandler.java index 2f32aa5ec2..2dbf170060 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mail/MailContentHandler.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mail/MailContentHandler.java @@ -370,7 +370,7 @@ class MailContentHandler implements ContentHandler { metadata.set(Message.MULTIPART_BOUNDARY, contentType.getParameters().get("boundary")); } else { - metadata.add(Metadata.MESSAGE_RAW_HEADER_PREFIX + parsedField.getName(), + metadata.add(Message.RAW_HEADER.key(parsedField.getName()), field.getBody()); } } else if (fieldname.equalsIgnoreCase("Date")) { @@ -385,7 +385,7 @@ class MailContentHandler implements ContentHandler { //swallow } } else { - metadata.add(Metadata.MESSAGE_RAW_HEADER_PREFIX + parsedField.getName(), + metadata.add(Message.RAW_HEADER.key(parsedField.getName()), field.getBody()); } } catch (RuntimeException me) { diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java index 7e97ac764e..c5f898da29 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java @@ -43,6 +43,7 @@ import org.apache.tika.extractor.EmbeddedDocumentUtil; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Message; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; @@ -71,6 +72,8 @@ public class MboxParser implements Parser { private static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile("<(.*@.*)>"); private static final String EMAIL_HEADER_METADATA_PREFIX = "MboxParser-"; + private static final PassthroughPrefix EMAIL_HEADER = + PassthroughPrefix.file(EMAIL_HEADER_METADATA_PREFIX, "mbox email header names"); private static final String EMAIL_FROMLINE_METADATA = EMAIL_HEADER_METADATA_PREFIX + "from"; private final Map<Integer, Metadata> trackingMetadata = new HashMap<>(); private boolean tracking = false; @@ -224,7 +227,7 @@ public class MboxParser implements Parser { metadata.add(Metadata.CONTENT_TYPE, headerContent); metadata.set(TikaCoreProperties.FORMAT, headerContent); } else { - metadata.add(EMAIL_HEADER_METADATA_PREFIX + headerTag, headerContent); + metadata.add(EMAIL_HEADER.key(headerTag), headerContent); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessExtractor.java index c302185b56..0a339a21c3 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessExtractor.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessExtractor.java @@ -102,13 +102,13 @@ class JackcessExtractor extends AbstractPOIFSExtractor { db.setDateTimeType(DateTimeType.DATE); PropertyMap dbp = db.getDatabaseProperties(); for (PropertyMap.Property p : dbp) { - parentMetadata.add(JackcessParser.MDB_PROPERTY_PREFIX + p.getName(), + parentMetadata.add(JackcessParser.MDB_PROPERTY.key(p.getName()), toString(p.getValue(), p.getType())); } PropertyMap up = db.getUserDefinedProperties(); for (PropertyMap.Property p : up) { - parentMetadata.add(JackcessParser.USER_DEFINED_PROPERTY_PREFIX + p.getName(), + parentMetadata.add(JackcessParser.MDB_USER_PROPERTY.key(p.getName()), toString(p.getValue(), p.getType())); } @@ -137,7 +137,7 @@ class JackcessExtractor extends AbstractPOIFSExtractor { for (PropertyMap.Property p : db.getSummaryProperties()) { if (!found.contains(p.getName())) { - parentMetadata.add(JackcessParser.SUMMARY_PROPERTY_PREFIX + p.getName(), + parentMetadata.add(JackcessParser.MDB_SUMMARY_PROPERTY.key(p.getName()), toString(p.getValue(), p.getType())); } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java index 3110640549..99a8e4e911 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java @@ -35,6 +35,7 @@ import org.apache.tika.exception.TikaException; import org.apache.tika.exception.UnsupportedFormatException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; @@ -55,6 +56,8 @@ public class JackcessParser implements Parser { public static final String SUMMARY_PROPERTY_PREFIX = "MDB_SUMMARY_PROP" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + public static final PassthroughPrefix MDB_SUMMARY_PROPERTY = + PassthroughPrefix.file(SUMMARY_PROPERTY_PREFIX, "MS Access summary properties"); private final static LinkResolver IGNORE_LINK_RESOLVER = new IgnoreLinkResolver(); private static final long serialVersionUID = -752276948656079347L; private static final MediaType MEDIA_TYPE = MediaType.application("x-msaccess"); @@ -64,8 +67,12 @@ public class JackcessParser implements Parser { // public static Property LINKED_DATABASES = Property.externalTextBag("LinkedDatabases"); public static String MDB_PROPERTY_PREFIX = "MDB_PROP" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + public static final PassthroughPrefix MDB_PROPERTY = + PassthroughPrefix.file(MDB_PROPERTY_PREFIX, "MS Access database properties"); public static String USER_DEFINED_PROPERTY_PREFIX = "MDB_USER_PROP" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER; + public static final PassthroughPrefix MDB_USER_PROPERTY = + PassthroughPrefix.file(USER_DEFINED_PROPERTY_PREFIX, "MS Access user-defined properties"); public static Property MDB_PW = Property.externalText("Password"); private Locale locale = Locale.ROOT; diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java index 89f1913fad..20c5676051 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java @@ -192,8 +192,7 @@ public class SummaryExtractor { private void parse(CustomProperties customProperties) { if (customProperties != null) { for (String name : customProperties.nameSet()) { - // Apply the custom prefix - String key = Office.USER_DEFINED_METADATA_NAME_PREFIX + name; + String key = Office.USER_DEFINED.key(name); // Get, convert and save property value Object value = customProperties.get(name); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/msg/ExtendedMetadataExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/msg/ExtendedMetadataExtractor.java index a9f35695b4..1e39990fd9 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/msg/ExtendedMetadataExtractor.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/msg/ExtendedMetadataExtractor.java @@ -148,7 +148,7 @@ public class ExtendedMetadataExtractor { if (!includeType(propertyValue)) { return; } - String key = MAPI.PREFIX_MAPI_PROPERTY + pair.tikaMapiProperty.name; + String key = MAPI.PROPERTY.key(pair.tikaMapiProperty.name); Types.MAPIType type = propertyValue.getActualType(); if (type == Types.TIME || type == Types.MV_TIME || type == Types.APP_TIME || type == Types.MV_APP_TIME) { Calendar calendar = (Calendar) propertyValue.getValue(); diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/odf/OpenDocumentMetaParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/odf/OpenDocumentMetaParser.java index 765a58a3ac..a40b06e09d 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/odf/OpenDocumentMetaParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/odf/OpenDocumentMetaParser.java @@ -84,7 +84,7 @@ public class OpenDocumentMetaParser extends XMLParser { // custom:Info1=Text1 ContentHandler branch = new MatchingContentHandler( new AttributeDependantMetadataHandler(md, "meta:name", - Office.USER_DEFINED_METADATA_NAME_PREFIX), matcher); + Office.USER_DEFINED.prefix()), matcher); return new TeeContentHandler(ch, branch); } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java index a2ccb629e6..04916b5567 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDFParser.java @@ -665,7 +665,7 @@ public class PDFParser implements Parser, RenderingParser { for (COSName key : info.getCOSObject().keySet()) { String name = key.getName(); if (!handledMetadata.contains(name)) { - PDMetadataExtractor.addMetadata(metadata, PDF.PDF_DOC_INFO_CUSTOM_PREFIX + name, + PDMetadataExtractor.addMetadata(metadata, PDF.DOC_INFO_CUSTOM.key(name), info.getCOSObject().getDictionaryObject(key)); } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java index 40ac9a1beb..7206d6c7ca 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java @@ -40,6 +40,7 @@ import org.apache.tika.extractor.EmbeddedDocumentExtractor; import org.apache.tika.extractor.EmbeddedDocumentUtil; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.metadata.WARC; @@ -64,6 +65,11 @@ public class WARCParser implements Parser { public static String WARC_PREFIX = "warc:"; public static String WARC_HTTP_PREFIX = WARC_PREFIX + "http:"; + public static final PassthroughPrefix WARC_HEADER = + PassthroughPrefix.file(WARC_PREFIX, "WARC record header names"); + public static final PassthroughPrefix WARC_HTTP_HEADER = + PassthroughPrefix.file(WARC_HTTP_PREFIX, "WARC HTTP response header names"); + public static String WARC_HTTP_STATUS = WARC_HTTP_PREFIX + "status"; public static String WARC_HTTP_STATUS_REASON = WARC_HTTP_PREFIX + "status:reason"; @@ -159,7 +165,7 @@ public class WARCParser implements Parser { private void processWarcMetadata(WarcResponse warcResponse, Metadata metadata) { for (Map.Entry<String, List<String>> e : warcResponse.headers().map().entrySet()) { for (String val : e.getValue()) { - metadata.add(WARC_PREFIX + e.getKey(), val); + metadata.add(WARC_HEADER.key(e.getKey()), val); } } } @@ -171,7 +177,7 @@ public class WARCParser implements Parser { } for (Map.Entry<String, List<String>> e : http.headers().map().entrySet()) { for (String val : e.getValue()) { - metadata.add(WARC_HTTP_PREFIX + e.getKey(), val); + metadata.add(WARC_HTTP_HEADER.key(e.getKey()), val); } } } diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java index 9ae8b4b325..b0db5d99cf 100644 --- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java +++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java @@ -39,6 +39,7 @@ import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Office; import org.apache.tika.metadata.PDF; import org.apache.tika.metadata.PagedText; +import org.apache.tika.metadata.PassthroughPrefix; import org.apache.tika.metadata.Photoshop; import org.apache.tika.metadata.Property; import org.apache.tika.metadata.TIFF; @@ -76,6 +77,7 @@ public class XmpExtractor { // X-TIKA: field. Best-effort discovery surface: keys use the document's prefix (not the URI) // and are non-contractual -- promote a field into TABLE when it needs a stable key. static final String RAW_PREFIX = "xmp-raw:"; + static final PassthroughPrefix RAW = PassthroughPrefix.file(RAW_PREFIX, "unmapped XMP keys"); // strip a trailing array index so a raw bag/seq is one multi-valued key, not foo:Bag[1], foo:Bag[2] private static final Pattern TRAILING_INDEX = Pattern.compile("\\[\\d+\\]$"); @@ -425,7 +427,7 @@ public class XmpExtractor { return; } // unmapped: namespaced raw passthrough; a trailing array index collapses to a multi-valued key - metadata.add(RAW_PREFIX + TRAILING_INDEX.matcher(p.path).replaceFirst(""), value); + metadata.add(RAW.key(TRAILING_INDEX.matcher(p.path).replaceFirst("")), value); } private void emit(Metadata metadata, Property prop, String value) {
