[ 
https://issues.apache.org/jira/browse/TIKA-4831?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18108806#comment-18108806
 ] 

ASF GitHub Bot commented on TIKA-4831:
--------------------------------------

dschmidt commented on code in PR #3044:
URL: https://github.com/apache/tika/pull/3044#discussion_r3871462379


##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.geogebra;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.apache.tika.utils.StringUtils;
+
+/**
+ * SAX handler for {@code geogebra.xml} and {@code geogebra_macro.xml}.
+ * <p>
+ * Extracts the document metadata from the {@code <geogebra>} root and its
+ * {@code <construction>} child (when asked to), and emits the user-visible
+ * text as XHTML paragraphs: the string literals of text object
+ * {@code <expression>}s, the text runs of {@code <content>} elements (inline
+ * text, tables, mind maps), element {@code <caption>}s and macro names and
+ * help texts.
+ */
+class GeoGebraXMLHandler extends DefaultHandler {
+
+    /**
+     * A GeoGebra string literal. GeoGebra writes strings between plain
+     * double quotes without any escaping, so a literal never contains one.
+     */
+    private static final Pattern STRING_LITERAL = 
Pattern.compile("\"([^\"]*)\"");
+
+    private final XHTMLContentHandler xhtml;
+    private final Metadata metadata;
+    private final boolean documentMetadata;
+    private int depth = 0;
+
+    /**
+     * @param xhtml            the handler paragraphs are written to
+     * @param metadata         the metadata tool names are added to
+     * @param documentMetadata whether to also fill the document metadata from
+     *                         the root and construction elements
+     */
+    GeoGebraXMLHandler(XHTMLContentHandler xhtml, Metadata metadata, boolean 
documentMetadata) {
+        this.xhtml = xhtml;
+        this.metadata = metadata;
+        this.documentMetadata = documentMetadata;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, 
Attributes attributes)
+            throws SAXException {
+        if (depth == 0 && "geogebra".equals(localName)) {
+            if (documentMetadata) {
+                setIfNotBlank(GeoGebraParser.APP_NAME, 
attributes.getValue("app"));
+                setIfNotBlank(GeoGebraParser.APP_VERSION, 
attributes.getValue("version"));
+                setIfNotBlank(GeoGebraParser.FORMAT_VERSION, 
attributes.getValue("format"));
+                setIfNotBlank(GeoGebraParser.ID, attributes.getValue("id"));
+            }
+        } else if (depth == 1 && "construction".equals(localName)) {

Review Comment:
   Tika's SAX parsers come from XMLReaderUtils, which sets namespace awareness 
on, so localName is always filled; OPFParser and EpubParser in this module 
compare localName the same way.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.geogebra;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.apache.tika.utils.StringUtils;
+
+/**
+ * SAX handler for {@code geogebra.xml} and {@code geogebra_macro.xml}.
+ * <p>
+ * Extracts the document metadata from the {@code <geogebra>} root and its
+ * {@code <construction>} child (when asked to), and emits the user-visible
+ * text as XHTML paragraphs: the string literals of text object
+ * {@code <expression>}s, the text runs of {@code <content>} elements (inline
+ * text, tables, mind maps), element {@code <caption>}s and macro names and
+ * help texts.
+ */
+class GeoGebraXMLHandler extends DefaultHandler {
+
+    /**
+     * A GeoGebra string literal. GeoGebra writes strings between plain
+     * double quotes without any escaping, so a literal never contains one.
+     */
+    private static final Pattern STRING_LITERAL = 
Pattern.compile("\"([^\"]*)\"");
+
+    private final XHTMLContentHandler xhtml;
+    private final Metadata metadata;
+    private final boolean documentMetadata;
+    private int depth = 0;
+
+    /**
+     * @param xhtml            the handler paragraphs are written to
+     * @param metadata         the metadata tool names are added to
+     * @param documentMetadata whether to also fill the document metadata from
+     *                         the root and construction elements
+     */
+    GeoGebraXMLHandler(XHTMLContentHandler xhtml, Metadata metadata, boolean 
documentMetadata) {
+        this.xhtml = xhtml;
+        this.metadata = metadata;
+        this.documentMetadata = documentMetadata;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, 
Attributes attributes)
+            throws SAXException {
+        if (depth == 0 && "geogebra".equals(localName)) {
+            if (documentMetadata) {
+                setIfNotBlank(GeoGebraParser.APP_NAME, 
attributes.getValue("app"));
+                setIfNotBlank(GeoGebraParser.APP_VERSION, 
attributes.getValue("version"));
+                setIfNotBlank(GeoGebraParser.FORMAT_VERSION, 
attributes.getValue("format"));
+                setIfNotBlank(GeoGebraParser.ID, attributes.getValue("id"));
+            }
+        } else if (depth == 1 && "construction".equals(localName)) {
+            //only the document's own construction; a macro's construction is
+            //nested one level deeper inside its <macro> element
+            if (documentMetadata) {
+                setIfNotBlank(TikaCoreProperties.TITLE, 
attributes.getValue("title"));
+                setIfNotBlank(TikaCoreProperties.CREATOR, 
attributes.getValue("author"));
+                setIfNotBlank(GeoGebraParser.DATE, 
attributes.getValue("date"));
+            }
+        } else if ("expression".equals(localName)) {
+            handleExpression(attributes.getValue("exp"));
+        } else if ("content".equals(localName)) {
+            handleContent(attributes.getValue("val"));
+        } else if ("caption".equals(localName)) {
+            paragraph(attributes.getValue("val"));
+        } else if ("macro".equals(localName)) {

Review Comment:
   Tika's SAX parsers come from XMLReaderUtils, which sets namespace awareness 
on, so localName is always filled; OPFParser and EpubParser in this module 
compare localName the same way.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/geogebra/GeoGebraXMLHandler.java:
##########
@@ -0,0 +1,171 @@
+/*
+ * 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.geogebra;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.apache.tika.utils.StringUtils;
+
+/**
+ * SAX handler for {@code geogebra.xml} and {@code geogebra_macro.xml}.
+ * <p>
+ * Extracts the document metadata from the {@code <geogebra>} root and its
+ * {@code <construction>} child (when asked to), and emits the user-visible
+ * text as XHTML paragraphs: the string literals of text object
+ * {@code <expression>}s, the text runs of {@code <content>} elements (inline
+ * text, tables, mind maps), element {@code <caption>}s and macro names and
+ * help texts.
+ */
+class GeoGebraXMLHandler extends DefaultHandler {
+
+    /**
+     * A GeoGebra string literal. GeoGebra writes strings between plain
+     * double quotes without any escaping, so a literal never contains one.
+     */
+    private static final Pattern STRING_LITERAL = 
Pattern.compile("\"([^\"]*)\"");
+
+    private final XHTMLContentHandler xhtml;
+    private final Metadata metadata;
+    private final boolean documentMetadata;
+    private int depth = 0;
+
+    /**
+     * @param xhtml            the handler paragraphs are written to
+     * @param metadata         the metadata tool names are added to
+     * @param documentMetadata whether to also fill the document metadata from
+     *                         the root and construction elements
+     */
+    GeoGebraXMLHandler(XHTMLContentHandler xhtml, Metadata metadata, boolean 
documentMetadata) {
+        this.xhtml = xhtml;
+        this.metadata = metadata;
+        this.documentMetadata = documentMetadata;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, 
Attributes attributes)
+            throws SAXException {
+        if (depth == 0 && "geogebra".equals(localName)) {
+            if (documentMetadata) {
+                setIfNotBlank(GeoGebraParser.APP_NAME, 
attributes.getValue("app"));
+                setIfNotBlank(GeoGebraParser.APP_VERSION, 
attributes.getValue("version"));
+                setIfNotBlank(GeoGebraParser.FORMAT_VERSION, 
attributes.getValue("format"));
+                setIfNotBlank(GeoGebraParser.ID, attributes.getValue("id"));
+            }
+        } else if (depth == 1 && "construction".equals(localName)) {
+            //only the document's own construction; a macro's construction is
+            //nested one level deeper inside its <macro> element
+            if (documentMetadata) {
+                setIfNotBlank(TikaCoreProperties.TITLE, 
attributes.getValue("title"));
+                setIfNotBlank(TikaCoreProperties.CREATOR, 
attributes.getValue("author"));
+                setIfNotBlank(GeoGebraParser.DATE, 
attributes.getValue("date"));
+            }
+        } else if ("expression".equals(localName)) {
+            handleExpression(attributes.getValue("exp"));
+        } else if ("content".equals(localName)) {
+            handleContent(attributes.getValue("val"));
+        } else if ("caption".equals(localName)) {
+            paragraph(attributes.getValue("val"));
+        } else if ("macro".equals(localName)) {
+            String toolName = attributes.getValue("toolName");
+            if (StringUtils.isBlank(toolName)) {
+                toolName = attributes.getValue("cmdName");
+            }
+            if (!StringUtils.isBlank(toolName)) {
+                metadata.add(GeoGebraParser.TOOL_NAME, toolName.trim());
+            }
+            paragraph(toolName);
+            paragraph(attributes.getValue("toolHelp"));
+        }
+        depth++;
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) {
+        depth--;
+    }
+
+    /**
+     * Emits the string literals of an expression. A text object's expression
+     * is either a single literal like {@code "some text"} or, for a dynamic
+     * text, literals combined with values like {@code "Area = " + a}; the
+     * literals are the user's text, everything else is geometry and skipped.
+     */
+    private void handleExpression(String exp) throws SAXException {
+        if (exp == null || exp.indexOf('"') < 0) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder();
+        Matcher m = STRING_LITERAL.matcher(exp);
+        while (m.find()) {
+            sb.append(m.group(1));
+        }
+        paragraph(sb.toString());
+    }
+
+    /**
+     * Emits the text runs of a rich-text {@code content} value, a JSON array
+     * of text runs like {@code [{"text":"Hello\n"}]}. All {@code text} fields
+     * are collected recursively (tables and mind maps nest them), joined, and
+     * emitted one paragraph per line.
+     */
+    private void handleContent(String val) throws SAXException {
+        if (val == null) {
+            return;
+        }
+        String trimmed = val.trim();
+        if (trimmed.isEmpty() || (trimmed.charAt(0) != '[' && 
trimmed.charAt(0) != '{')) {
+            //not a JSON document; a plain string carries no text runs
+            return;
+        }
+        JsonNode root;
+        try {
+            root = GeoGebraParser.OBJECT_MAPPER.readTree(trimmed);
+        } catch (IOException e) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (String text : root.findValuesAsText("text")) {
+            sb.append(text);
+        }
+        for (String line : sb.toString().split("\r\n|[\r\n]")) {
+            paragraph(line);
+        }
+    }

Review Comment:
   Fixed: content JSON longer than 1 MB is skipped before parsing (a real 
inline text, table or mind map is a few kilobytes).





> Add content-based detection and a parser for GeoGebra files (ggb, ggs, ggt)
> ---------------------------------------------------------------------------
>
>                 Key: TIKA-4831
>                 URL: https://issues.apache.org/jira/browse/TIKA-4831
>             Project: Tika
>          Issue Type: New Feature
>            Reporter: Dominik Schmidt
>            Priority: Major
>
> GeoGebra files are currently only recognized by file extension. The mime
> registry has glob-only entries for {{application/vnd.geogebra.file}} 
> ({{*.ggb}})
> and {{application/vnd.geogebra.tool}} ({{*.ggt}}), but both formats are zip
> containers and the entries are not declared as sub-classes of
> {{application/zip}}. As a result, as soon as content is available, magic
> detection returns {{application/zip}} and the filename hint is discarded in
> {{MimeTypes.applyHint()}} - even when the resource name is known. Content-only
> detection (no filename) has no way to identify the formats at all, and the
> newer GeoGebra formats {{*.ggs}} (Notes/Slides) and {{*.ggp}} (Pinboard) are
> missing from the registry entirely.
> There is also no parser for any of the GeoGebra formats: files fall through to
> the generic {{PackageParser}}, which extracts the zip entries but produces no
> document metadata and no usable text (the {{geogebra.xml}} construction is
> emitted as raw XML through the XML parser).
> Proposed improvement:
> * mime registry: declare {{application/vnd.geogebra.file}} and
>   {{application/vnd.geogebra.tool}} as {{sub-class-of application/zip}}; add
>   {{application/vnd.geogebra.slides}} ({{*.ggs}}, zip-based) and
>   {{application/vnd.geogebra.pinboard}} ({{*.ggp}}, JSON-based)
> * a {{ZipContainerDetector}} that identifies the formats without a filename by
>   their well-known entries: {{geogebra.xml}} (worksheet), {{structure.json}}
>   plus {{_slideN/geogebra.xml}} (Notes/Slides), {{geogebra_macro.xml}} (tool);
>   a worksheet with macros contains both {{geogebra.xml}} and
>   {{geogebra_macro.xml}}, so the decision must be made after all entry names
>   have been seen
> * a {{GeoGebraParser}} for ggb/ggs/ggt that extracts the construction metadata
>   (title, author, date) and application name/version, emits the user-visible
>   text (text objects, rich-text notes, captions, macro names/help) as XHTML,
>   and emits the embedded {{geogebra_thumbnail.png}} (root, or the first 
> slide's
>   for Notes/Slides) as an embedded document marked
>   {{embeddedResourceType=THUMBNAIL}}, following the existing convention in the
>   OOXML, ODF and iWork parsers, so downstream consumers of {{/unpack/all}}
>   sidecars can identify the preview image
> Use case: file sync/share servers (e.g. OpenCloud) use Tika for content
> extraction and for serving embedded preview images; with the THUMBNAIL marker
> they can select the representative preview of a GeoGebra file the same way as
> for Office documents.
> Pull request to follow.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to