Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Release 4.1.0 - unreleased

* GeoGebraParser emits the icon of a tool (*.ggt, the macro's iconFile)
as its THUMBNAIL embedded document; tool files have no thumbnail of
their own (TIKA-4831).

* Enum values in JSON configuration are matched case-insensitively, so
"no_ocr" works as well as "NO_OCR"; the server docs used the lower-case
form in their examples (TIKA-4859).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
* {@code <div class="slide">}, in the order given by {@code structure.json}.
* <p>
* The representative rendering of the document, {@code geogebra_thumbnail.png}
* at the root of a worksheet or tool, or the first available slide thumbnail
* at the root of a worksheet, the icon of a tool ({@code iconFile} of its
* macro), or the first available slide thumbnail
* of a Notes/Slides file, is emitted as an embedded document marked with
* {@link TikaCoreProperties.EmbeddedResourceType#THUMBNAIL}, so that clients
* (e.g. the unpacker's sidecar metadata) can pick it as the preview image.
Expand Down Expand Up @@ -193,14 +194,16 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata
//document metadata comes from the first XML parsed: a worksheet's
//geogebra.xml, a tool's geogebra_macro.xml, or the first slide
boolean documentMetadataPending = true;
List<String> iconFiles = new ArrayList<>();
if (rootXml != null) {
documentMetadataPending = false;
parseGeoGebraXml(zipFile, rootXml, xhtml, metadata, true, context);
}
if (macroXml != null) {
//a worksheet with macros carries both XMLs; the macro one only
//contributes the tool names then, not the document metadata
parseGeoGebraXml(zipFile, macroXml, xhtml, metadata, documentMetadataPending, context);
iconFiles = parseGeoGebraXml(zipFile, macroXml, xhtml, metadata,
documentMetadataPending, context);
documentMetadataPending = false;
}
Map<String, Integer> pageNumbers = new HashMap<>();
Expand All @@ -220,8 +223,9 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata
}
}
}
handleThumbnail(zipFile, slideIds, xhtml, metadata, context, embeddedDocumentExtractor);
handleOtherEntries(zipFile, pageNumbers, xhtml, metadata, context,
String thumbnail = handleThumbnail(zipFile, slideIds, iconFiles, xhtml, metadata, context,
embeddedDocumentExtractor);
handleOtherEntries(zipFile, pageNumbers, thumbnail, xhtml, metadata, context,
embeddedDocumentExtractor);
xhtml.endDocument();
}
Expand Down Expand Up @@ -301,21 +305,24 @@ private static String stripLeadingZeros(String digits) {
* is set, the document metadata. A part that cannot be read or is not
* well-formed is recorded in the metadata and skipped.
*/
private void parseGeoGebraXml(ZipFile zipFile, ZipArchiveEntry entry,
XHTMLContentHandler xhtml, Metadata metadata,
boolean documentMetadata, ParseContext context)
/**
* @return the icon files of the macros in the XML, in document order
*/
private List<String> parseGeoGebraXml(ZipFile zipFile, ZipArchiveEntry entry,
XHTMLContentHandler xhtml, Metadata metadata,
boolean documentMetadata, ParseContext context)
throws SAXException {
if (entry == null) {
return;
return Collections.emptyList();
}
if (!zipFile.canReadEntryData(entry)) {
EmbeddedDocumentUtil.recordEmbeddedStreamException(
new IOException("Unsupported zip entry: " + entry.getName()), metadata, context);
return;
return Collections.emptyList();
}
GeoGebraXMLHandler xmlHandler = new GeoGebraXMLHandler(xhtml, metadata, documentMetadata);
try (InputStream is = zipFile.getInputStream(entry)) {
XMLReaderUtils.parseSAX(is, new EmbeddedContentHandler(
new GeoGebraXMLHandler(xhtml, metadata, documentMetadata)), context);
XMLReaderUtils.parseSAX(is, new EmbeddedContentHandler(xmlHandler), context);
} catch (SAXException e) {
if (WriteLimitReachedException.isWriteLimitReached(e)) {
throw e;
Expand All @@ -324,25 +331,37 @@ private void parseGeoGebraXml(ZipFile zipFile, ZipArchiveEntry entry,
} catch (IOException | TikaException e) {
EmbeddedDocumentUtil.recordEmbeddedStreamException(e, metadata, context);
}
return xmlHandler.getIconFiles();
}

/**
* Emits the representative thumbnail: the root one of a worksheet or
* tool, or the first slide thumbnail (in slide order) of a Notes/Slides
* file.
* Emits the representative thumbnail: the root one of a worksheet, the
* first slide thumbnail (in slide order) of a Notes/Slides file, or the
* icon of the first tool that has one. A tool file has no rendering of
* its own; its icon (a picture in a directory with a generated name,
* referenced by the macro's {@code iconFile}) is what GeoGebra shows for
* it.
*
* @return the name of the entry emitted, or null if there is none
*/
private void handleThumbnail(ZipFile zipFile, List<String> slideIds, XHTMLContentHandler xhtml,
Metadata metadata, ParseContext context,
EmbeddedDocumentExtractor embeddedDocumentExtractor)
private String handleThumbnail(ZipFile zipFile, List<String> slideIds, List<String> iconFiles,
XHTMLContentHandler xhtml, Metadata metadata,
ParseContext context,
EmbeddedDocumentExtractor embeddedDocumentExtractor)
throws IOException, SAXException {
ZipArchiveEntry entry = zipFile.getEntry(THUMBNAIL_PNG);
for (int i = 0; entry == null && i < slideIds.size(); i++) {
entry = zipFile.getEntry(slideIds.get(i) + "/" + THUMBNAIL_PNG);
}
if (entry != null) {
handleEmbedded(zipFile, entry, TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
null, xhtml, metadata, context, embeddedDocumentExtractor);
for (int i = 0; entry == null && i < iconFiles.size(); i++) {
entry = zipFile.getEntry(iconFiles.get(i));
}
if (entry == null) {
return null;
}
handleEmbedded(zipFile, entry, TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
null, xhtml, metadata, context, embeddedDocumentExtractor);
return entry.getName();
}

/**
Expand All @@ -352,8 +371,8 @@ private void handleThumbnail(ZipFile zipFile, List<String> slideIds, XHTMLConten
* so a file of the same name elsewhere is still emitted.
*/
private void handleOtherEntries(ZipFile zipFile, Map<String, Integer> pageNumbers,
XHTMLContentHandler xhtml, Metadata metadata,
ParseContext context,
String thumbnail, XHTMLContentHandler xhtml,
Metadata metadata, ParseContext context,
EmbeddedDocumentExtractor embeddedDocumentExtractor)
throws IOException, SAXException {
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
Expand All @@ -363,6 +382,10 @@ private void handleOtherEntries(ZipFile zipFile, Map<String, Integer> pageNumber
continue;
}
String name = entry.getName();
if (name.equals(thumbnail)) {
//already emitted as the thumbnail (a tool icon)
continue;
}
String dir = "";
String basename = name;
int slash = name.indexOf('/');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.apache.tika.parser.geogebra;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -59,6 +61,11 @@ class GeoGebraXMLHandler extends DefaultHandler {
private final Metadata metadata;
private final boolean documentMetadata;
private int depth = 0;
/**
* The {@code iconFile} of every macro, in document order: the path of
* the tool's icon inside the zip, if the tool has one.
*/
private final List<String> iconFiles = new ArrayList<>();

/**
* @param xhtml the handler paragraphs are written to
Expand Down Expand Up @@ -106,10 +113,18 @@ public void startElement(String uri, String localName, String qName, Attributes
}
paragraph(toolName);
paragraph(attributes.getValue("toolHelp"));
String iconFile = attributes.getValue("iconFile");
if (!StringUtils.isBlank(iconFile)) {
iconFiles.add(iconFile.trim());
}
}
depth++;
}

List<String> getIconFiles() {
return iconFiles;
}

@Override
public void endElement(String uri, String localName, String qName) {
depth--;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -199,6 +200,57 @@ public void testGGT() throws Exception {
assertNull(metadata.get(TikaCoreProperties.TITLE));
}

/**
* A tool with an icon: GeoGebra stores it in a directory with a generated
* name and points to it with the macro's iconFile. It is the tool's
* thumbnail, and is not emitted a second time as a picture.
*/
@Test
public void testGGTIconIsTheThumbnail() throws Exception {
String icon = "5d41402abc4b2a76b9719d911017c592/Midpoint.png";
Map<String, String> entries = new HashMap<>();
entries.put("geogebra_macro.xml", geogebra("classic", "5.0.815.0", "tool-1",
"<macro cmdName=\"Mid\" toolName=\"Midpoint\" toolHelp=\"Two points\" iconFile=\""
+ icon + "\"><construction/></macro>"));
List<Metadata> metadataList = parse(entries, Collections.singletonMap(icon, PNG), null);
assertEquals("application/vnd.geogebra.tool",
metadataList.get(0).get(HttpHeaders.CONTENT_TYPE));
assertEquals(2, metadataList.size());
Metadata thumbnail = byName(metadataList, icon);
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
thumbnail.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
assertEquals("image/png", thumbnail.get(HttpHeaders.CONTENT_TYPE));

//an iconFile that is not in the zip: no thumbnail, no error
metadataList = parse(entries);
assertEquals(1, metadataList.size());
assertNull(metadataList.get(0).get(TikaCoreProperties.TIKA_META_EXCEPTION_EMBEDDED_STREAM));
}

/**
* A worksheet keeps its own thumbnail; the icon of an embedded tool is
* just a picture then.
*/
@Test
public void testWorksheetThumbnailBeatsToolIcon() throws Exception {
String icon = "5d41402abc4b2a76b9719d911017c592/Midpoint.png";
Map<String, String> entries = new HashMap<>();
entries.put("geogebra.xml", geogebra("classic", "5.0.815.0", "ws-1", "<construction/>"));
entries.put("geogebra_macro.xml", geogebra("classic", "5.0.815.0", "ws-1",
"<macro cmdName=\"Mid\" toolName=\"Midpoint\" iconFile=\"" + icon
+ "\"><construction/></macro>"));
Map<String, byte[]> binary = new HashMap<>();
binary.put("geogebra_thumbnail.png", PNG);
binary.put(icon, PNG);
List<Metadata> metadataList = parse(entries, binary, null);
assertEquals(3, metadataList.size());
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
byName(metadataList, "geogebra_thumbnail.png")
.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
assertEquals(TikaCoreProperties.EmbeddedResourceType.INLINE.toString(),
byName(metadataList, icon).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
}

@Test
public void testMacroDoesNotOverrideWorksheetMetadata() throws Exception {
Map<String, String> entries = new LinkedHashMap<>();
Expand Down