GeoGebraJson.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.geogebra;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
/**
* Streaming readers for the two bits of JSON in a GeoGebra file. Both read
* with jackson-core only; the parsers do not need a tree.
*/
final class GeoGebraJson {
private static final JsonFactory JSON_FACTORY = new JsonFactory();
/**
* The containers an element id sits in: {@code {"chapters":[{"pages":
* [{"elements":[{"id":...}]}]}]}}. A container opened as an array element
* or at the root has no field name.
*/
private static final List<String> ELEMENT_PATH =
Arrays.asList(null, "chapters", null, "pages", null, "elements", null);
private GeoGebraJson() {
}
/**
* Returns the {@code id}s of the elements of a {@code structure.json},
* in document order.
*
* @throws IOException if the stream is not well-formed JSON
*/
static List<String> elementIds(InputStream is) throws IOException {
List<String> ids = new ArrayList<>();
try (JsonParser parser = JSON_FACTORY.createParser(is)) {
Deque<String> path = new ArrayDeque<>();
String fieldName = null;
for (JsonToken t = parser.nextToken(); t != null; t = parser.nextToken()) {
if (t == JsonToken.FIELD_NAME) {
fieldName = parser.currentName();
} else if (t.isStructStart()) {
path.addLast(fieldName == null ? "" : fieldName);
fieldName = null;
} else if (t.isStructEnd()) {
path.removeLast();
} else {
if ("id".equals(fieldName) && t.isScalarValue() && atElement(path)) {
ids.add(parser.getText());
}
fieldName = null;
}
}
}
return ids;
}
private static boolean atElement(Deque<String> path) {
if (path.size() != ELEMENT_PATH.size()) {
return false;
}
int i = 0;
for (String name : path) {
String expected = ELEMENT_PATH.get(i++);
if (!(expected == null ? "" : expected).equals(name)) {
return false;
}
}
return true;
}
/**
* Returns the values of every {@code text} field, at any depth, in
* document order: the text runs of a {@code content} value such as
* {@code [{"text":"Hello\n"}]}. A {@code text} field whose value is null,
* an object or an array contributes nothing and is not descended into.
*
* @throws IOException if the string is not well-formed JSON
*/
static List<String> textValues(String json) throws IOException {
List<String> texts = new ArrayList<>();
try (JsonParser parser = JSON_FACTORY.createParser(json)) {
for (JsonToken t = parser.nextToken(); t != null; t = parser.nextToken()) {
if (t == JsonToken.FIELD_NAME && "text".equals(parser.currentName())) {
t = parser.nextToken();
if (t == null) {
break;
}
if (t == JsonToken.VALUE_NULL) {
continue;
}
if (t.isScalarValue()) {
texts.add(parser.getText());
} else {
parser.skipChildren();
}
}
}
}
return texts;
}
}