Paramétrage TIKA

De EjnTricks

L'extraction des informations des documents à l'aide de TIKA nécessite l'implémentation de la classe TikaConfig. Cette classe possède un constructeur public sans aucun argument et satisfera la grande majorité des utilisations. Cependant, il peut être parfois nécessaire de modifier cette configuration.

Mais comme souvent, il peut être parfois nécessaire de mettre en place sa propre configuration, par exemple pour ajouter un Parser. Cet article se propose de décrire le fonctionnement de cette configuration et d'expliquer comment la personnaliser.

Le code explicité est extrait de la version 1.5, mais l'analyse a toujours été la même depuis la version 0.9.


Books-icon.png Configuration standard

Start-icon.png Constructeur par défaut

Un configuration standard est donc chargée lors de l'utilisation du constructeur par défaut. Son code doit être finement analysé pour comprendre comment sont chargés les différents fichiers.

    public TikaConfig() throws TikaException, IOException {
        ServiceLoader loader = new ServiceLoader();

        String config = System.getProperty("tika.config");
        if (config == null) {
            config = System.getenv("TIKA_CONFIG");
        }

        if (config == null) {
            this.mimeTypes = getDefaultMimeTypes(ServiceLoader.getContextClassLoader());
            this.parser = getDefaultParser(mimeTypes, loader);
            this.detector = getDefaultDetector(mimeTypes, loader);
        } else {
            // Locate the given configuration file
            InputStream stream = null;
            File file = new File(config);
            if (file.isFile()) {
                stream = new FileInputStream(file);
            }
            if (stream == null) {
                try {
                    stream = new URL(config).openStream();
                } catch (IOException ignore) {
                }
            }
            if (stream == null) {
                stream = loader.getResourceAsStream(config);
            }
            if (stream == null) {
                throw new TikaException(
                        "Specified Tika configuration not found: " + config);
            }

            try {
                Element element =
                        getBuilder().parse(stream).getDocumentElement();
                this.mimeTypes = typesFromDomElement(element);
                this.parser =
                        parserFromDomElement(element, mimeTypes, loader);
                this.detector =
                        detectorFromDomElement(element, mimeTypes, loader);
            } catch (SAXException e) {
                throw new TikaException(
                        "Specified Tika configuration has syntax errors: "
                                + config, e);
            } finally {
                stream.close();
            }
        }
    }

Dans le cas où les variables tika.config ou TIKA_CONFIG ne sont pas définis, les méthodes statiques getDefaultXXX sont appelées.

File-find-icon.png Chargement configuration

La méthode getDefaultParser va être analysées, le fonctionnement pour les deux autres étant identiques.

    private static CompositeParser getDefaultParser(
            MimeTypes types, ServiceLoader loader) {
        return new DefaultParser(types.getMediaTypeRegistry(), loader);
    }

S'en suit un enchaînement d'appel de fonction en cascade. Une instance de DefaultParser est construit, nous passerons sous silence les arguments. Le constructeur mis en jeu est le suivant :

    public DefaultParser(MediaTypeRegistry registry, ServiceLoader loader) {
        super(registry, getDefaultParsers(loader));
        this.loader = loader;
    }

A noter l'appel à la fonction getDefaultParsers pour construire un argument.

    private static List<Parser> getDefaultParsers(ServiceLoader loader) {
        List<Parser> parsers =
                loader.loadStaticServiceProviders(Parser.class);
        Collections.sort(parsers, new Comparator<Parser>() {
            public int compare(Parser p1, Parser p2) {
                String n1 = p1.getClass().getName();
                String n2 = p2.getClass().getName();
                boolean t1 = n1.startsWith("org.apache.tika.");
                boolean t2 = n2.startsWith("org.apache.tika.");
                if (t1 == t2) {
                    return n1.compareTo(n2);
                } else if (t1) {
                    return -1;
                } else {
                    return 1;
                }
            }
        });
        return parsers;
    }

Il faut encore remonter sur la fonction loadStaticServiceProviders de l'instance de ServiceLoader.

    public <T> List<T> loadStaticServiceProviders(Class<T> iface) {
        List<T> providers = new ArrayList<T>();

        if (loader != null) {
            List<String> names = new ArrayList<String>();

            String serviceName = iface.getName();
            Enumeration<URL> resources =
                    findServiceResources("META-INF/services/" + serviceName);
            for (URL resource : Collections.list(resources)) {
                try {
                    collectServiceClassNames(resource, names);
                } catch (IOException e) {
                    handler.handleLoadError(serviceName, e);
                }
            }

            for (String name : names) {
                try {
                    Class<?> klass = loader.loadClass(name);
                    if (iface.isAssignableFrom(klass)) {
                        providers.add((T) klass.newInstance());
                    }
                } catch (Throwable t) {
                    handler.handleLoadError(name, t);
                }
            }
        }

        return providers;
    }

Cette fonction est appelée avec la classe org.apache.tika.parser.Parser comme argument. La variable serviceName aura donc pour valeur org.apache.tika.parser.Parser. Par conséquent, les ressources sont recherchées à l'emplacement META-INF/services/org.apache.tika.parser.Parser.

Les classes analysées se trouvent dans le jar tika-core-1.5.jar, mais aucun fichier n'est disponible à l'emplacement cible. Or le jar tika-parsers-1.5.jar doit être pris en compte, car il contient les parsers standards. Le fichier de configuration se trouve dans celui-ci, avec le contenu suivant :

#  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.

org.apache.tika.parser.asm.ClassParser
org.apache.tika.parser.audio.AudioParser
org.apache.tika.parser.audio.MidiParser
org.apache.tika.parser.crypto.Pkcs7Parser
org.apache.tika.parser.dwg.DWGParser
org.apache.tika.parser.epub.EpubParser
org.apache.tika.parser.executable.ExecutableParser
org.apache.tika.parser.feed.FeedParser
org.apache.tika.parser.font.AdobeFontMetricParser
org.apache.tika.parser.font.TrueTypeParser
org.apache.tika.parser.html.HtmlParser
org.apache.tika.parser.image.ImageParser
org.apache.tika.parser.image.PSDParser
org.apache.tika.parser.image.TiffParser
org.apache.tika.parser.iptc.IptcAnpaParser
org.apache.tika.parser.iwork.IWorkPackageParser
org.apache.tika.parser.jpeg.JpegParser
org.apache.tika.parser.mail.RFC822Parser
org.apache.tika.parser.mbox.MboxParser
org.apache.tika.parser.microsoft.OfficeParser
org.apache.tika.parser.microsoft.TNEFParser
org.apache.tika.parser.microsoft.ooxml.OOXMLParser
org.apache.tika.parser.mp3.Mp3Parser
org.apache.tika.parser.mp4.MP4Parser
org.apache.tika.parser.hdf.HDFParser
org.apache.tika.parser.netcdf.NetCDFParser
org.apache.tika.parser.odf.OpenDocumentParser
org.apache.tika.parser.pdf.PDFParser
org.apache.tika.parser.pkg.CompressorParser
org.apache.tika.parser.pkg.PackageParser
org.apache.tika.parser.rtf.RTFParser
org.apache.tika.parser.txt.TXTParser
org.apache.tika.parser.video.FLVParser
org.apache.tika.parser.xml.DcXMLParser
org.apache.tika.parser.xml.FictionBookParser
org.apache.tika.parser.chm.ChmParser
org.apache.tika.parser.code.SourceCodeParser


Configuration-icon.png Configuration pesonnalisée

L'objectif initiale était donc d'étendre la configuration. Pour cela, il existe des constructeurs de TikaConfig acceptant un emplacement, un fichier, une URL ou des élément DOM :

    public TikaConfig(String file)
            throws TikaException, IOException, SAXException {
        this(new File(file));
    }

    public TikaConfig(File file)
            throws TikaException, IOException, SAXException {
        this(getBuilder().parse(file));
    }

    public TikaConfig(URL url)
            throws TikaException, IOException, SAXException {
        this(url, ServiceLoader.getContextClassLoader());
    }

    public TikaConfig(URL url, ClassLoader loader)
            throws TikaException, IOException, SAXException {
        this(getBuilder().parse(url.toString()).getDocumentElement(), loader);
    }

    public TikaConfig(InputStream stream)
            throws TikaException, IOException, SAXException {
        this(getBuilder().parse(stream));
    }

    public TikaConfig(Document document) throws TikaException, IOException {
        this(document.getDocumentElement());
    }

    public TikaConfig(Element element) throws TikaException, IOException {
        this(element, new ServiceLoader());
    }

    public TikaConfig(Element element, ClassLoader loader)
            throws TikaException, IOException {
        this(element, new ServiceLoader(loader));
    }

    private TikaConfig(Element element, ServiceLoader loader)
            throws TikaException, IOException {
        this.mimeTypes = typesFromDomElement(element);
        this.detector = detectorFromDomElement(element, mimeTypes, loader);
        this.parser = parserFromDomElement(element, mimeTypes, loader);
    }

Tous les constructeurs finissent par appeler le constructor basé sur une élément DOM, parsé pour peupler les variables mimeTypes, detector et parser. La première conclusion est que le fichier de paramétrage doit être au format XML. Il suffit ensuite d'analyser les trois fonctions pour en déduire la structure.

Configuration mimetype

Tout se passe dans la fonction typesFromDomElement :

    private static MimeTypes typesFromDomElement(Element element)
            throws TikaException, IOException {
        Element mtr = getChild(element, "mimeTypeRepository");
        if (mtr != null && mtr.hasAttribute("resource")) {
            return MimeTypesFactory.create(mtr.getAttribute("resource"));
        } else {
            return getDefaultMimeTypes(null);
        }
    }

Rien de plus simple pour déduire qu'il faut un noeud fils mimeTypeRepository avec un attribut resource si l'on souhaite spécifier un fichier de paramétrage de la détection des types mimes. Très franchement, cela est la partie la plus compliquée et très certainement celle qui n'est absolument pas nécessaire de modifier. Il suffit soit de mettre en place un noued fils sans attribut, soit de ne pas du tout le spécifier.

Configuration detector

Tout se passe dans la fonction detectorFromDomElement :

    private static Detector detectorFromDomElement(
          Element element, MimeTypes mimeTypes, ServiceLoader loader)
          throws TikaException, IOException {
       List<Detector> detectors = new ArrayList<Detector>();
       NodeList nodes = element.getElementsByTagName("detector");
       for (int i = 0; i < nodes.getLength(); i++) {
           Element node = (Element) nodes.item(i);
           String name = node.getAttribute("class");

           try {
               Class<? extends Detector> detectorClass =
                       loader.getServiceClass(Detector.class, name);
               detectors.add(detectorClass.newInstance());
           } catch (ClassNotFoundException e) {
               throw new TikaException(
                       "Unable to find a detector class: " + name, e);
           } catch (IllegalAccessException e) {
               throw new TikaException(
                       "Unable to access a detector class: " + name, e);
           } catch (InstantiationException e) {
               throw new TikaException(
                       "Unable to instantiate a detector class: " + name, e);
           }
       }
       if (detectors.isEmpty()) {
           return getDefaultDetector(mimeTypes, loader);
       } else {
           MediaTypeRegistry registry = mimeTypes.getMediaTypeRegistry();
           return new CompositeDetector(registry, detectors);
       }
    }

Encore une fois, c'est assez simple de constater que les noeuds fils detector sont recherchés pour extraire depuis l'attribut class la classe à charger. A noter qu'il n'y a aucune organisation spécifique, les noeuds sont recherchés en "vrac". Il suffit de reprendre les classes depuis le fichier META-INF/services/org.apache.tika.detect.Detector pour peupler les noeuds detector.

Configuration parser

Tout se passe dans la fonction parserFromDomElement :

    private static CompositeParser parserFromDomElement(
            Element element, MimeTypes mimeTypes, ServiceLoader loader)
            throws TikaException, IOException {
        List<Parser> parsers = new ArrayList<Parser>();
        NodeList nodes = element.getElementsByTagName("parser");
        for (int i = 0; i < nodes.getLength(); i++) {
            Element node = (Element) nodes.item(i);
            String name = node.getAttribute("class");

            try {
                Class<? extends Parser> parserClass =
                        loader.getServiceClass(Parser.class, name);
                // https://issues.apache.org/jira/browse/TIKA-866
                if (AutoDetectParser.class.isAssignableFrom(parserClass)) {
                    throw new TikaException(
                            "AutoDetectParser not supported in a <parser>"
                            + " configuration element: " + name);
                }
                Parser parser = parserClass.newInstance();

                NodeList mimes = node.getElementsByTagName("mime");
                if (mimes.getLength() > 0) {
                    Set<MediaType> types = new HashSet<MediaType>();
                    for (int j = 0; j < mimes.getLength(); j++) {
                        String mime = getText(mimes.item(j));
                        MediaType type = MediaType.parse(mime);
                        if (type != null) {
                            types.add(type);
                        } else {
                            throw new TikaException(
                                    "Invalid media type name: " + mime);
                        }
                    }
                    parser = ParserDecorator.withTypes(parser, types);
                }

                parsers.add(parser);
            } catch (ClassNotFoundException e) {
                throw new TikaException(
                        "Unable to find a parser class: " + name, e);
            } catch (IllegalAccessException e) {
                throw new TikaException(
                        "Unable to access a parser class: " + name, e);
            } catch (InstantiationException e) {
                throw new TikaException(
                        "Unable to instantiate a parser class: " + name, e);
            }
        }
        if (parsers.isEmpty()) {
            return getDefaultParser(mimeTypes, loader);
        } else {
            MediaTypeRegistry registry = mimeTypes.getMediaTypeRegistry();
            return new CompositeParser(registry, parsers);
        }
    }

Comme pour les deux précédents configurations, le fonctionnement est assez simple. Les noeuds fils parser sont recherchés pour extraire depuis l'attribut class la classe à charger. A noter qu'il n'y a aucune organisation spécifique, les noeuds sont recherchés en "vrac". Il suffit de reprendre les classes depuis le fichier META-INF/services/org.apache.tika.parser.Parser pour peupler les noeuds parser.

Ce fichier de configuration permet également de spécifier explicitement les mime-types auxquels s'appliquent chacun des parser, en ajoutant des noeuds fils mime au noeud parser. Cependant, cette possibilité n'est pas exploitée avec la configuration par défaut. Donc il semble inutile de mettre en place ce type de configuration.

Examples-icon.png Exemple

Dans le cadre de l'article d'optimisation de l'extraction, un fichier de paramétrage est mis en place. Celui-ci a fait suite a été mis en place avec ce même type d'analyse. Il est donc présenté à titre d'exemple.

A noter que les noeuds recherchés detector et parser sont regroupés dans un noeuds parent, afin d'apporter un semblant de structure au fichier.

Lors de l'analyse, le nom du noeud root n'a jamais évoqués. Et pour cause, celui-ci peut être n'importe lequel. Pour cette exemple, il a été nommé properties.

<?xml version="1.0" encoding="UTF-8"?>
<properties>
	<detectors>
		<detector class="org.gagravarr.tika.OggDetector" />
		<detector class="org.apache.tika.parser.microsoft.POIFSContainerDetector" />
		<detector class="org.apache.tika.parser.pkg.ZipContainerDetector" />
<!-- Do not forget to add the default detector, otherwise almost everything is considered as application/octet. -->
		<detector class="org.apache.tika.detect.DefaultDetector" />
<!--  -->
	</detectors>
	<mimeTypeRepository />
	<parsers>
		<parser class="org.apache.tika.parser.asm.ClassParser" />
		<parser class="org.apache.tika.parser.audio.AudioParser" />
		<parser class="org.apache.tika.parser.audio.MidiParser" />
		<parser class="org.apache.tika.parser.crypto.Pkcs7Parser" />
		<parser class="org.apache.tika.parser.dwg.DWGParser" />
		<parser class="org.apache.tika.parser.epub.EpubParser" />
		<parser class="org.apache.tika.parser.executable.ExecutableParser" />
		<parser class="org.apache.tika.parser.feed.FeedParser" />
		<parser class="org.apache.tika.parser.font.AdobeFontMetricParser" />
		<parser class="org.apache.tika.parser.font.TrueTypeParser" />
		<parser class="org.apache.tika.parser.html.HtmlParser" />
		<parser class="org.apache.tika.parser.image.ImageParser" />
		<parser class="org.apache.tika.parser.image.PSDParser" />
		<parser class="org.apache.tika.parser.image.TiffParser" />
		<parser class="org.apache.tika.parser.iptc.IptcAnpaParser" />
		<parser class="org.apache.tika.parser.iwork.IWorkPackageParser" />
		<parser class="org.apache.tika.parser.jpeg.JpegParser" />
		<parser class="org.apache.tika.parser.mail.RFC822Parser" />
		<parser class="org.apache.tika.parser.mbox.MboxParser" />
<!-- Start parser update -->
<!--
		<parser class="org.apache.tika.parser.microsoft.OfficeParser" />
-->
		<parser class="org.apache.tika.parser.microsoft.ContentFilterOfficeParser" />
<!-- Start parser update -->
<!-- End parser update -->
		<parser class="org.apache.tika.parser.microsoft.TNEFParser" />
<!--
		<parser class="org.apache.tika.parser.microsoft.ooxml.OOXMLParser" />
-->
		<parser class="org.apache.tika.parser.microsoft.ooxml.ContentFilterOOXMLParser" />
<!-- End parser update -->
		<parser class="org.apache.tika.parser.mp3.Mp3Parser" />
		<parser class="org.apache.tika.parser.mp4.MP4Parser" />
		<parser class="org.apache.tika.parser.hdf.HDFParser" />
		<parser class="org.apache.tika.parser.netcdf.NetCDFParser" />
		<parser class="org.apache.tika.parser.odf.OpenDocumentParser" />
<!-- Start parser update -->
<!--
		<parser class="org.apache.tika.parser.pdf.PDFParser" />
-->
		<parser class="org.apache.tika.parser.pdf.ContentFilterPDFParser" />
<!-- End parser update -->
		<parser class="org.apache.tika.parser.pkg.CompressorParser" />
		<parser class="org.apache.tika.parser.pkg.PackageParser" />
		<parser class="org.apache.tika.parser.rtf.RTFParser" />
		<parser class="org.apache.tika.parser.txt.TXTParser" />
		<parser class="org.apache.tika.parser.video.FLVParser" />
		<parser class="org.apache.tika.parser.xml.DcXMLParser" />
		<parser class="org.apache.tika.parser.xml.FictionBookParser" />
		<parser class="org.apache.tika.parser.chm.ChmParser" />
		<parser class="org.apache.tika.parser.code.SourceCodeParser" />
	</parsers>
</properties>