Closed alastortenebris closed 10 months ago
This is planned, but will require quite a bit more code. For simplicity, I'll probably write an entirely new extra OPF parsing function since the existing one isn't really suited for parsing the refines
property.
I'll probably make calibre:series
take precedence, then the first EPUB3-style collection with a group-position similar to what I do with NickelSeries for Kobo eReaders.
Testing a simple Java implementation. Will rewrite in smali later.
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
public class SeriesParser {
public static void main(String[] args) {
try {
final SeriesParser p = new SeriesParser();
System.out.println(p.parseSeries(Files.newInputStream(Paths.get("package.opf"))));
System.out.println(p.mSeries + " #" + p.mSeriesIndex);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public String mSeries;
public String mSeriesIndex;
public String parseSeries(InputStream is) throws XmlPullParserException, IOException {
final XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); // Landroid/util/Xml;->newPullParser()Lorg/xmlpull/v1/XmlPullParser;
xpp.setFeature("http://xmlpull.org/v1/doc/features.html#process-namespaces", true);
xpp.setInput(is, null);
final LinkedHashSet<String> hSeriesSkip = new LinkedHashSet<>();
final LinkedHashMap<String, String> hSeries = new LinkedHashMap<>();
final LinkedHashMap<String, String> hSeriesIndex = new LinkedHashMap<>();
hSeries.put(null, null); // calibre series metadata first
final StringBuilder txt = new StringBuilder();
for (int depth = 0, depthMatch = 0, evt = xpp.getEventType(); evt != XmlPullParser.END_DOCUMENT; ) {
switch (evt) {
case XmlPullParser.END_TAG:
if (depth-- < depthMatch) {
depthMatch--;
}
break;
case XmlPullParser.START_TAG:
if (depth++ == depthMatch) {
if ("http://www.idpf.org/2007/opf".equals(xpp.getNamespace())) {
switch (depth) {
case 1:
if ("package".equals(xpp.getName()))
depthMatch++;
break;
case 2:
if ("metadata".equals(xpp.getName()))
depthMatch++;
break;
case 3:
if ("meta".equals(xpp.getName()))
depthMatch++;
break;
}
}
}
// if we're at a package>metadata>meta
if (depthMatch == 3) {
// get the attributes we want
final String pName = xpp.getAttributeValue(null, "name");
final String pContent = xpp.getAttributeValue(null, "content");
final String pProperty = xpp.getAttributeValue(null, "property");
final String pId = xpp.getAttributeValue(null, "id");
final String pRefines = xpp.getAttributeValue(null, "refines");
// get the text within the element
txt.setLength(0);
for (evt = xpp.next(); !(depth == 3 && evt == XmlPullParser.END_TAG); evt = xpp.next()) {
switch (evt) {
case XmlPullParser.START_TAG:
depth++;
break;
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.TEXT:
final String tmp = xpp.getText();
if (tmp != null) {
txt.append(tmp);
}
break;
}
}
// get an identifier (null for calibre metadata) and key/value meta pair
String vSrc, vKey, vValue;
if (pName != null) {
vSrc = null;
vKey = pName;
vValue = pContent;
} else {
if (pRefines != null && pRefines.startsWith("#")) {
vSrc = pRefines.substring(1);
} else if (pId != null) {
vSrc = pId;
} else {
vSrc = "";
}
vKey = pProperty;
vValue = txt.toString().trim();
if (vValue.isEmpty()) {
vValue = null;
}
}
// if we have a key/value pair, process it
if (vKey != null && vValue != null)
if ("calibre:series".equals(vKey) || "belongs-to-collection".equals(vKey))
hSeries.put(vSrc, vValue);
else if ("calibre:series_index".equals(vKey) || "group-position".equals(vKey))
hSeriesIndex.put(vSrc, vValue);
else if ("collection-type".equals(vKey) && !"series".equals(vValue))
hSeriesSkip.add(vSrc);
continue; // we already consumed the next token (END_TAG) in the txt loop
}
break;
}
evt = xpp.next();
}
// get the first series
for (final String src : hSeries.keySet()) {
final String series = hSeries.get(src);
if (series != null) {
final String seriesIndex = hSeriesIndex.get(src);
if (seriesIndex != null) {
if (!hSeriesSkip.contains(src)) {
mSeries = series;
mSeriesIndex = seriesIndex;
return src != null ? "#" + src : "calibre";
}
}
}
}
return null;
}
}
Currently, these patches only check for series metadata under the
calibre:series
tag. This tag is only used with EPUB2 books. EPUB3 supports series using thebelongs-to-collection
tag listed here. Calibre will not use thecalibre:series
tag when embedding metadata if the ebook is an EPUB3 file.