This is an automated email from the ASF dual-hosted git repository.
THausherr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 19ca28457e TIKA-4831: The icon of a GeoGebra tool is its thumbnail
(#3100)
19ca28457e is described below
commit 19ca28457e045f5b015d038a3d40fc1ae4f01a00
Author: Dominik Schmidt <[email protected]>
AuthorDate: Mon Aug 31 11:34:26 2026 +0200
TIKA-4831: The icon of a GeoGebra tool is its thumbnail (#3100)
* TIKA-4831 - the icon of a GeoGebra tool is its THUMBNAIL
* TIKA-4831 - merge the parseGeoGebraXml javadocs
---
CHANGES.txt | 4 ++
.../tika/parser/geogebra/GeoGebraParser.java | 66 ++++++++++++++--------
.../tika/parser/geogebra/GeoGebraXMLHandler.java | 15 +++++
.../tika/parser/geogebra/GeoGebraParserTest.java | 52 +++++++++++++++++
4 files changed, 115 insertions(+), 22 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 9a61c586a2..ea7a41d7d4 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -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).
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraParser.java
index 7db66ec776..4b725006e1 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraParser.java
@@ -71,7 +71,8 @@ import org.apache.tika.zip.utils.ZipFileHelper;
* {@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.
@@ -193,6 +194,7 @@ public class GeoGebraParser implements Parser {
//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);
@@ -200,7 +202,8 @@ public class GeoGebraParser implements Parser {
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<>();
@@ -220,8 +223,9 @@ public class GeoGebraParser implements Parser {
}
}
}
- 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();
}
@@ -300,22 +304,24 @@ public class GeoGebraParser implements Parser {
* Parses one GeoGebra XML for its text and, if {@code documentMetadata}
* is set, the document metadata. A part that cannot be read or is not
* well-formed is recorded in the metadata and skipped.
+ *
+ * @return the icon files of the macros in the XML, in document order
*/
- private void parseGeoGebraXml(ZipFile zipFile, ZipArchiveEntry entry,
- XHTMLContentHandler xhtml, Metadata metadata,
- boolean documentMetadata, ParseContext
context)
+ 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;
@@ -324,25 +330,37 @@ public class GeoGebraParser implements Parser {
} 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();
}
/**
@@ -352,8 +370,8 @@ public class GeoGebraParser implements Parser {
* 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();
@@ -363,6 +381,10 @@ public class GeoGebraParser implements Parser {
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('/');
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java
index b3d579fa05..e5d65a7bd9 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java
@@ -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;
@@ -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
@@ -106,10 +113,18 @@ class GeoGebraXMLHandler extends DefaultHandler {
}
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--;
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/geogebra/GeoGebraParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/geogebra/GeoGebraParserTest.java
index 76856a7e32..f67852febc 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/geogebra/GeoGebraParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/geogebra/GeoGebraParserTest.java
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
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;
@@ -199,6 +200,57 @@ public class GeoGebraParserTest extends TikaTest {
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<>();