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<>();

Reply via email to