EMFParserTest.java

/*
 * 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.parser.microsoft;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.xml.sax.ContentHandler;

import org.apache.tika.TikaTest;
import org.apache.tika.config.loader.TikaLoader;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.HttpHeaders;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.Rendering;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;

public class EMFParserTest extends TikaTest {


    @Test
    public void testTextExtractionMac() throws Exception {
        List<Metadata> metadataList = getRecursiveMetadata("testEXCEL_embeddedPDF_mac.xls");
        Metadata emfMetadata = metadataList.get(2);
        assertEquals("image/emf", emfMetadata.get(HttpHeaders.CONTENT_TYPE));
        assertContains("is a toolkit for detecting",
                emfMetadata.get(TikaCoreProperties.TIKA_CONTENT));
        //test that a space was inserted before url
        assertContains("Tika http://incubator.apache.org/tika/",
                emfMetadata.get(TikaCoreProperties.TIKA_CONTENT));
    }

    @Test
    public void testIconOnly() throws Exception {
        String fullFileName = "some word doc with a very long name that should be wrapped.docx";
        //test file contributed by Ross Spencer on TIKA-3968
        List<Metadata> metadataList = getRecursiveMetadata("testEMF_iconOnlyLongFilename.emf");
        assertEquals("true", metadataList.get(0).get(EMFParser.EMF_ICON_ONLY));
        assertEquals(fullFileName, metadataList.get(0).get(EMFParser.EMF_ICON_STRING));
        assertContains("some word doc", metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
        assertContains("a very long name that should be wrapped.docx",
                metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT));
    }

    /**
     * Rendering is off by default: an EMF yields no embedded document of
     * its own.
     */
    @Test
    public void testNoRenderingByDefault() throws Exception {
        List<Metadata> metadataList = getRecursiveMetadata("testEMF.emf");
        assertEquals(1, metadataList.size());
    }

    @Test
    public void testRenderingFromConfig() throws Exception {
        Parser parser = TikaLoader
                .load(getConfigPath(EMFParserTest.class, "tika-config-emf-render.json"))
                .loadParsers();
        Metadata metadata = new Metadata();
        metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, "testEMF.emf");
        metadata.set(HttpHeaders.CONTENT_TYPE, "image/emf");
        List<Metadata> metadataList =
                getRecursiveMetadata("testEMF.emf", parser, metadata, new ParseContext(), false);
        assertEquals(2, metadataList.size());
        assertRendering(metadataList.get(1), "testEMF.png",
                TikaCoreProperties.EmbeddedResourceType.RENDERING);
    }

    /**
     * The per-request form: the parser config is supplied through the
     * ParseContext, as tika-server does for a multipart config part.
     */
    @Test
    public void testRenderingFromParseContext() throws Exception {
        ParseContext context = new ParseContext();
        context.setJsonConfig("emf-parser", "{\"renderImage\": true}");
        List<Metadata> metadataList = getRecursiveMetadata("testEMF.emf", context);
        assertEquals(2, metadataList.size());
        assertRendering(metadataList.get(1), "testEMF.png",
                TikaCoreProperties.EmbeddedResourceType.RENDERING);
        assertEquals("1", metadataList.get(1).get(TikaCoreProperties.EMBEDDED_DEPTH));
    }

    /**
     * The docProps thumbnail of a Word document is an EMF; with rendering on
     * its PNG rendering follows it, one level deeper.
     */
    @Test
    public void testDocxThumbnailRendering() throws Exception {
        ParseContext context = new ParseContext();
        context.setJsonConfig("emf-parser", "{\"renderImage\": true, \"renderWidth\": 200}");
        List<Metadata> metadataList = getRecursiveMetadata("testDOCX_Thumbnail.docx", context);
        //the document, its thumbnail, the WMF picture inside the thumbnail's
        //EMF and the thumbnail's rendering
        assertEquals(4, metadataList.size());
        Metadata thumbnail = byName(metadataList, "thumbnail.emf");
        assertEquals("image/emf", thumbnail.get(HttpHeaders.CONTENT_TYPE));
        assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.name(),
                thumbnail.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
        assertEquals("1", thumbnail.get(TikaCoreProperties.EMBEDDED_DEPTH));
        Metadata rendering = byName(metadataList, "thumbnail.png");
        assertRendering(rendering, "thumbnail.png",
                TikaCoreProperties.EmbeddedResourceType.THUMBNAIL);
        assertEquals("2", rendering.get(TikaCoreProperties.EMBEDDED_DEPTH));
        assertEquals("/thumbnail.emf/thumbnail.png",
                rendering.get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH));
    }

    /**
     * Restricted to THUMBNAIL embedded documents, the parser renders the
     * document's thumbnail but not a picture that is merely embedded.
     */
    @Test
    public void testRenderOnlyThumbnails() throws Exception {
        ParseContext context = new ParseContext();
        context.setJsonConfig("emf-parser",
                "{\"renderImage\": true, \"renderOnlyEmbeddedResourceTypes\": [\"THUMBNAIL\"]}");
        List<Metadata> metadataList = getRecursiveMetadata("testDOCX_Thumbnail.docx", context);
        assertRendering(byName(metadataList, "thumbnail.png"), "thumbnail.png",
                TikaCoreProperties.EmbeddedResourceType.THUMBNAIL);

        //a bare EMF is the document itself, not a THUMBNAIL: no rendering
        metadataList = getRecursiveMetadata("testEMF.emf", context);
        assertEquals(1, metadataList.size());
    }

    /**
     * The configured width reaches the renderer the parser was handed, which
     * is the SPI-injected one in a default setup, not the parser's own
     * instance.
     */
    @ParameterizedTest
    @ValueSource(ints = {200, 400})
    public void testRenderWidth(int width) throws Exception {
        ParseContext context = new ParseContext();
        context.setJsonConfig("emf-parser",
                "{\"renderImage\": true, \"renderWidth\": " + width + "}");
        List<byte[]> renderings = new ArrayList<>();
        context.set(EmbeddedDocumentExtractor.class, collector(renderings));
        try (InputStream is = getResourceAsStream("/test-documents/testEMF.emf")) {
            AUTO_DETECT_PARSER.parse(TikaInputStream.get(is), new BodyContentHandler(-1),
                    new Metadata(), context);
        }
        assertEquals(1, renderings.size());
        //the PNG header carries the width at offset 16
        assertEquals(width, ByteBuffer.wrap(renderings.get(0), 16, 4).getInt());
    }

    private static EmbeddedDocumentExtractor collector(List<byte[]> renderings) {
        return new EmbeddedDocumentExtractor() {
            @Override
            public boolean shouldParseEmbedded(Metadata metadata, ParseContext parseContext) {
                return true;
            }

            @Override
            public void parseEmbedded(TikaInputStream stream, ContentHandler handler,
                                      Metadata metadata, ParseContext parseContext,
                                      boolean outputHtml) throws IOException {
                renderings.add(stream.readAllBytes());
            }
        };
    }

    private static Metadata byName(List<Metadata> metadataList, String name) {
        for (Metadata m : metadataList) {
            if (name.equals(m.get(TikaCoreProperties.RESOURCE_NAME_KEY))) {
                return m;
            }
        }
        throw new AssertionError("no embedded document named " + name);
    }

    /**
     * There is no image parser on this module's test classpath, so the PNG
     * is checked by its type, name and size rather than its dimensions.
     * The rendering of a THUMBNAIL is a THUMBNAIL, any other a RENDERING.
     */
    private static void assertRendering(Metadata rendering, String name,
                                        TikaCoreProperties.EmbeddedResourceType type) {
        assertEquals("image/png", rendering.get(HttpHeaders.CONTENT_TYPE));
        assertEquals(type.name(), rendering.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
        assertEquals(name, rendering.get(TikaCoreProperties.RESOURCE_NAME_KEY));
        assertEquals("poi-metafile-renderer", rendering.get(Rendering.RENDERED_BY));
        assertTrue(Long.parseLong(rendering.get(HttpHeaders.CONTENT_LENGTH)) > 100);
        assertNull(rendering.get(TikaCoreProperties.TIKA_META_EXCEPTION_EMBEDDED_STREAM));
    }

    @Test
    public void testMissingCoords() throws Exception {
        //TIKA-4432
        List<Metadata> metadataList = getRecursiveMetadata("testEMF_zero_coords.emf");
        String txt = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
        assertNotContained("title13At", txt);
        assertContains("Presentation title 13", txt);
        assertContains("<p>At Contoso", txt);
        assertContains("next-generation", txt);//this is stored in three records -- test that no spaces are interpolated
    }


}