mirror of
https://github.com/geometer/FBReaderJ.git
synced 2025-10-04 10:19:33 +02:00
misc optimizations, first "working" version of book serialization/deserialization (missing things are marked with TODO comments)
This commit is contained in:
parent
db93063629
commit
ff3ea90821
12 changed files with 319 additions and 32 deletions
|
@ -119,7 +119,7 @@ JNIEXPORT jboolean JNICALL Java_org_geometerplus_fbreader_formats_NativeFormatPl
|
|||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL Java_org_geometerplus_fbreader_formats_NativeFormatPlugin_detectLanguageAndEncoding(JNIEnv* env, jobject thiz, jobject javaBook) {
|
||||
JNIEXPORT void JNICALL Java_org_geometerplus_fbreader_formats_NativeFormatPlugin_detectLanguageAndEncodingNative(JNIEnv* env, jobject thiz, jobject javaBook) {
|
||||
shared_ptr<FormatPlugin> plugin = findCppPlugin(thiz);
|
||||
if (plugin.isNull()) {
|
||||
return;
|
||||
|
|
|
@ -56,7 +56,11 @@ public class NativeFormatPlugin extends FormatPlugin {
|
|||
private native boolean readMetaInfoNative(Book book);
|
||||
|
||||
@Override
|
||||
public native void detectLanguageAndEncoding(Book book);
|
||||
public void detectLanguageAndEncoding(Book book) {
|
||||
detectLanguageAndEncodingNative(book);
|
||||
}
|
||||
|
||||
public native void detectLanguageAndEncodingNative(Book book);
|
||||
|
||||
@Override
|
||||
synchronized public void readModel(BookModel model) throws BookReadingException {
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.geometerplus.zlibrary.core.filesystem.ZLFile;
|
|||
|
||||
import org.geometerplus.fbreader.bookmodel.BookReadingException;
|
||||
import org.geometerplus.fbreader.formats.NativeFormatPlugin;
|
||||
import org.geometerplus.fbreader.library.Book;
|
||||
|
||||
public class FB2NativePlugin extends NativeFormatPlugin {
|
||||
public FB2NativePlugin() {
|
||||
|
@ -45,4 +46,9 @@ public class FB2NativePlugin extends NativeFormatPlugin {
|
|||
public EncodingCollection supportedEncodings() {
|
||||
return new AutoEncodingCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detectLanguageAndEncoding(Book book) {
|
||||
book.setEncoding("auto");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,19 +29,14 @@ import org.geometerplus.zlibrary.core.filesystem.ZLFile;
|
|||
|
||||
import org.geometerplus.fbreader.bookmodel.BookModel;
|
||||
import org.geometerplus.fbreader.bookmodel.BookReadingException;
|
||||
|
||||
import org.geometerplus.fbreader.formats.NativeFormatPlugin;
|
||||
import org.geometerplus.fbreader.library.Book;
|
||||
|
||||
public class OEBNativePlugin extends NativeFormatPlugin {
|
||||
public OEBNativePlugin() {
|
||||
super("ePub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncodingCollection supportedEncodings() {
|
||||
return new AutoEncodingCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readModel(BookModel model) throws BookReadingException {
|
||||
super.readModel(model);
|
||||
|
@ -54,4 +49,14 @@ public class OEBNativePlugin extends NativeFormatPlugin {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncodingCollection supportedEncodings() {
|
||||
return new AutoEncodingCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detectLanguageAndEncoding(Book book) {
|
||||
book.setEncoding("auto");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,7 +321,9 @@ public class Book {
|
|||
public String getEncoding() {
|
||||
if (myEncoding == null) {
|
||||
try {
|
||||
System.err.println("detect encoding for " + getId());
|
||||
getPlugin().detectLanguageAndEncoding(this);
|
||||
System.err.println("detected encoding for " + getId() + " = " + myEncoding);
|
||||
} catch (BookReadingException e) {
|
||||
}
|
||||
if (myEncoding == null) {
|
||||
|
|
|
@ -19,17 +19,28 @@
|
|||
|
||||
package org.geometerplus.fbreader.library;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.geometerplus.zlibrary.core.constants.XMLNamespaces;
|
||||
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
|
||||
import org.geometerplus.zlibrary.core.xml.ZLXMLReaderAdapter;
|
||||
import org.geometerplus.zlibrary.core.xml.ZLStringMap;
|
||||
|
||||
public abstract class BookSerializerUtil {
|
||||
private BookSerializerUtil() {
|
||||
}
|
||||
|
||||
public static String serialize(Book book) {
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("<entry>\n");
|
||||
appendTagWithAttributes(
|
||||
buffer, "entry", false,
|
||||
"xmlns:dc", XMLNamespaces.DublinCore
|
||||
);
|
||||
|
||||
appendTagWithContent(buffer, "id", String.valueOf(book.getId()));
|
||||
appendTagWithContent(buffer, "title", book.getTitle());
|
||||
appendTagWithContent(buffer, "dc:language", book.getLanguage());
|
||||
appendTagWithContent(buffer, "dc:encoding", book.getEncodingNoDetection());
|
||||
|
||||
for (Author author : book.authors()) {
|
||||
buffer.append("<author>\n");
|
||||
|
@ -40,14 +51,18 @@ public abstract class BookSerializerUtil {
|
|||
|
||||
for (Tag tag : book.tags()) {
|
||||
appendTagWithAttributes(
|
||||
buffer, "category",
|
||||
buffer, "category", true,
|
||||
"term", tag.toString("/"),
|
||||
"label", tag.Name
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: serialize series info
|
||||
// TODO: serialize description (?)
|
||||
// TODO: serialize cover (?)
|
||||
|
||||
appendTagWithAttributes(
|
||||
buffer, "link",
|
||||
buffer, "link", true,
|
||||
"href", book.File.getUrl(),
|
||||
// TODO: real book mimetype
|
||||
"type", "application/epub+zip",
|
||||
|
@ -59,22 +74,221 @@ public abstract class BookSerializerUtil {
|
|||
}
|
||||
|
||||
public static Book deserialize(String xml) {
|
||||
// TODO: implement
|
||||
return null;
|
||||
final Deserializer deserializer = new Deserializer();
|
||||
deserializer.readQuietly(xml);
|
||||
return deserializer.getBook();
|
||||
}
|
||||
|
||||
private static void appendTagWithContent(StringBuilder buffer, String tag, String content) {
|
||||
if (content != null) {
|
||||
buffer
|
||||
.append('<').append(tag).append('>')
|
||||
.append(content)
|
||||
.append(escapeForXml(content))
|
||||
.append("</").append(tag).append(">\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendTagWithAttributes(StringBuilder buffer, String tag, String ... attrs) {
|
||||
private static void appendTagWithAttributes(StringBuilder buffer, String tag, boolean close, String ... attrs) {
|
||||
buffer.append('<').append(tag);
|
||||
for (int i = 0; i < attrs.length - 1; i += 2) {
|
||||
buffer.append(' ').append(attrs[i]).append("=\"").append(attrs[i + 1]).append('"');
|
||||
buffer.append(' ')
|
||||
.append(escapeForXml(attrs[i])).append("=\"")
|
||||
.append(escapeForXml(attrs[i + 1])).append('"');
|
||||
}
|
||||
if (close) {
|
||||
buffer.append('/');
|
||||
}
|
||||
buffer.append(">\n");
|
||||
}
|
||||
|
||||
private static String escapeForXml(String data) {
|
||||
if (data.indexOf('&') != -1) {
|
||||
data = data.replaceAll("&", "&");
|
||||
}
|
||||
if (data.indexOf('<') != -1) {
|
||||
data = data.replaceAll("&", "<");
|
||||
}
|
||||
if (data.indexOf('>') != -1) {
|
||||
data = data.replaceAll("&", ">");
|
||||
}
|
||||
if (data.indexOf('\'') != -1) {
|
||||
data = data.replaceAll("&", "'");
|
||||
}
|
||||
if (data.indexOf('"') != -1) {
|
||||
data = data.replaceAll("&", """);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static final class Deserializer extends ZLXMLReaderAdapter {
|
||||
private static enum State {
|
||||
READ_NOTHING,
|
||||
READ_ENTRY,
|
||||
READ_ID,
|
||||
READ_TITLE,
|
||||
READ_LANGUAGE,
|
||||
READ_ENCODING,
|
||||
READ_AUTHOR,
|
||||
READ_AUTHOR_URI,
|
||||
READ_AUTHOR_NAME,
|
||||
}
|
||||
|
||||
private State myState = State.READ_NOTHING;
|
||||
|
||||
private long myId = -1;
|
||||
private String myUrl;
|
||||
private StringBuilder myTitle = new StringBuilder();
|
||||
private StringBuilder myLanguage = new StringBuilder();
|
||||
private StringBuilder myEncoding = new StringBuilder();
|
||||
private ArrayList<Author> myAuthors = new ArrayList<Author>();
|
||||
private StringBuilder myAuthorSortKey = new StringBuilder();
|
||||
private StringBuilder myAuthorName = new StringBuilder();
|
||||
|
||||
private Book myBook;
|
||||
|
||||
public Book getBook() {
|
||||
return myState == State.READ_NOTHING ? myBook : null;
|
||||
}
|
||||
|
||||
private static void clear(StringBuilder buffer) {
|
||||
buffer.delete(0, buffer.length());
|
||||
}
|
||||
|
||||
private static String string(StringBuilder buffer) {
|
||||
return buffer.length() != 0 ? buffer.toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDocumentHandler() {
|
||||
myBook = null;
|
||||
|
||||
myId = -1;
|
||||
myUrl = null;
|
||||
clear(myTitle);
|
||||
clear(myLanguage);
|
||||
clear(myEncoding);
|
||||
myAuthors.clear();
|
||||
|
||||
myState = State.READ_NOTHING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endDocumentHandler() {
|
||||
if (myId == -1) {
|
||||
return;
|
||||
}
|
||||
myBook = new Book(
|
||||
myId,
|
||||
ZLFile.createFileByUrl(myUrl),
|
||||
string(myTitle),
|
||||
string(myEncoding),
|
||||
string(myLanguage)
|
||||
);
|
||||
for (Author author : myAuthors) {
|
||||
myBook.addAuthorWithNoCheck(author);
|
||||
}
|
||||
// TODO: add tags
|
||||
// TODO: add series info
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startElementHandler(String tag, ZLStringMap attributes) {
|
||||
switch (myState) {
|
||||
case READ_NOTHING:
|
||||
if (!"entry".equals(tag)) {
|
||||
return true;
|
||||
}
|
||||
myState = State.READ_ENTRY;
|
||||
break;
|
||||
case READ_ENTRY:
|
||||
if ("id".equals(tag)) {
|
||||
myState = State.READ_ID;
|
||||
} else if ("title".equals(tag)) {
|
||||
myState = State.READ_TITLE;
|
||||
} else if ("dc:language".equals(tag)) {
|
||||
myState = State.READ_LANGUAGE;
|
||||
} else if ("dc:encoding".equals(tag)) {
|
||||
myState = State.READ_ENCODING;
|
||||
} else if ("author".equals(tag)) {
|
||||
myState = State.READ_AUTHOR;
|
||||
clear(myAuthorName);
|
||||
clear(myAuthorSortKey);
|
||||
} else if ("category".equals(tag)) {
|
||||
// TODO: implement
|
||||
} else if ("link".equals(tag)) {
|
||||
// TODO: use "rel" attribute
|
||||
myUrl = attributes.getValue("href");
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case READ_AUTHOR:
|
||||
if ("uri".equals(tag)) {
|
||||
myState = State.READ_AUTHOR_URI;
|
||||
} else if ("name".equals(tag)) {
|
||||
myState = State.READ_AUTHOR_NAME;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endElementHandler(String tag) {
|
||||
switch (myState) {
|
||||
case READ_NOTHING:
|
||||
return true;
|
||||
case READ_ENTRY:
|
||||
if ("entry".equals(tag)) {
|
||||
myState = State.READ_NOTHING;
|
||||
}
|
||||
break;
|
||||
case READ_AUTHOR_URI:
|
||||
case READ_AUTHOR_NAME:
|
||||
myState = State.READ_AUTHOR;
|
||||
break;
|
||||
case READ_AUTHOR:
|
||||
if (myAuthorSortKey.length() > 0 && myAuthorName.length() > 0) {
|
||||
myAuthors.add(
|
||||
new Author(myAuthorName.toString(), myAuthorSortKey.toString())
|
||||
);
|
||||
}
|
||||
myState = State.READ_ENTRY;
|
||||
break;
|
||||
default:
|
||||
myState = State.READ_ENTRY;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characterDataHandler(char[] ch, int start, int length) {
|
||||
switch (myState) {
|
||||
case READ_ID:
|
||||
try {
|
||||
myId = Long.parseLong(new String(ch, start, length));
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
break;
|
||||
case READ_TITLE:
|
||||
myTitle.append(ch, start, length);
|
||||
break;
|
||||
case READ_LANGUAGE:
|
||||
myLanguage.append(ch, start, length);
|
||||
break;
|
||||
case READ_ENCODING:
|
||||
myEncoding.append(ch, start, length);
|
||||
break;
|
||||
case READ_AUTHOR_URI:
|
||||
myAuthorSortKey.append(ch, start, length);
|
||||
break;
|
||||
case READ_AUTHOR_NAME:
|
||||
myAuthorName.append(ch, start, length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer.append("/>\n");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,6 +237,10 @@ public final class Library {
|
|||
if (myBooks.containsKey(book.File)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String xml = BookSerializerUtil.serialize(book);
|
||||
BookSerializerUtil.deserialize(xml);
|
||||
|
||||
myBooks.put(book.File, book);
|
||||
|
||||
List<Author> authors = book.authors();
|
||||
|
|
|
@ -58,7 +58,13 @@ public final class Tag {
|
|||
}
|
||||
|
||||
public String toString(String delimiter) {
|
||||
return Parent == null ? Name : Parent.toString(delimiter) + delimiter + Name;
|
||||
return toStringBuilder(delimiter).toString();
|
||||
}
|
||||
|
||||
protected StringBuilder toStringBuilder(String delimiter) {
|
||||
return Parent == null
|
||||
? new StringBuilder(Name)
|
||||
: Parent.toStringBuilder(delimiter).append(delimiter).append(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -103,6 +103,14 @@ public abstract class ZLFile {
|
|||
return file;
|
||||
}
|
||||
|
||||
public static ZLFile createFileByUrl(String url) {
|
||||
System.err.println("url = " + url);
|
||||
if (url == null || !url.startsWith("file://")) {
|
||||
return null;
|
||||
}
|
||||
return createFileByPath(url.substring("file://".length()));
|
||||
}
|
||||
|
||||
public static ZLFile createFileByPath(String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
|
|
|
@ -67,7 +67,7 @@ final class ZLXMLParser {
|
|||
return s;
|
||||
}
|
||||
|
||||
private final InputStreamReader myStreamReader;
|
||||
private final Reader myStreamReader;
|
||||
private final ZLXMLReader myXMLReader;
|
||||
private final boolean myProcessNamespaces;
|
||||
|
||||
|
@ -119,7 +119,15 @@ final class ZLXMLParser {
|
|||
storeString(myEntityName);
|
||||
}
|
||||
|
||||
public ZLXMLParser(ZLXMLReader xmlReader, InputStream stream, int bufferSize) throws IOException {
|
||||
ZLXMLParser(ZLXMLReader xmlReader, Reader reader, int bufferSize) throws IOException {
|
||||
myXMLReader = xmlReader;
|
||||
myProcessNamespaces = xmlReader.processNamespaces();
|
||||
myBuffer = getBuffer(bufferSize);
|
||||
myBufferDescriptionLength = 0;
|
||||
myStreamReader = reader;
|
||||
}
|
||||
|
||||
ZLXMLParser(ZLXMLReader xmlReader, InputStream stream, int bufferSize) throws IOException {
|
||||
myXMLReader = xmlReader;
|
||||
myProcessNamespaces = xmlReader.processNamespaces();
|
||||
|
||||
|
@ -204,7 +212,7 @@ final class ZLXMLParser {
|
|||
final ZLXMLReader xmlReader = myXMLReader;
|
||||
final HashMap<String,char[]> entityMap = getDTDMap(xmlReader.externalDTDs());
|
||||
xmlReader.collectExternalEntities(entityMap);
|
||||
final InputStreamReader streamReader = myStreamReader;
|
||||
final Reader streamReader = myStreamReader;
|
||||
final boolean processNamespaces = myProcessNamespaces;
|
||||
HashMap<String,String> oldNamespaceMap = processNamespaces ? new HashMap<String,String>() : null;
|
||||
HashMap<String,String> currentNamespaceMap = null;
|
||||
|
|
|
@ -33,13 +33,27 @@ public abstract class ZLXMLProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
public static void read(ZLXMLReader reader, InputStream stream, int bufferSize) throws IOException {
|
||||
public static void read(ZLXMLReader xmlReader, InputStream stream, int bufferSize) throws IOException {
|
||||
ZLXMLParser parser = null;
|
||||
try {
|
||||
parser = new ZLXMLParser(reader, stream, bufferSize);
|
||||
reader.startDocumentHandler();
|
||||
parser = new ZLXMLParser(xmlReader, stream, bufferSize);
|
||||
xmlReader.startDocumentHandler();
|
||||
parser.doIt();
|
||||
reader.endDocumentHandler();
|
||||
xmlReader.endDocumentHandler();
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
parser.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void read(ZLXMLReader xmlReader, Reader reader, int bufferSize) throws IOException {
|
||||
ZLXMLParser parser = null;
|
||||
try {
|
||||
parser = new ZLXMLParser(xmlReader, reader, bufferSize);
|
||||
xmlReader.startDocumentHandler();
|
||||
parser.doIt();
|
||||
xmlReader.endDocumentHandler();
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
parser.finish();
|
||||
|
|
|
@ -19,9 +19,8 @@
|
|||
|
||||
package org.geometerplus.zlibrary.core.xml;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
|
||||
|
||||
|
@ -30,7 +29,16 @@ public abstract class ZLXMLReaderAdapter implements ZLXMLReader {
|
|||
|
||||
public boolean readQuietly(ZLFile file) {
|
||||
try {
|
||||
ZLXMLProcessor.read(this, file);
|
||||
read(file);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean readQuietly(String string) {
|
||||
try {
|
||||
read(string);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
|
@ -45,6 +53,14 @@ public abstract class ZLXMLReaderAdapter implements ZLXMLReader {
|
|||
ZLXMLProcessor.read(this, stream, 65536);
|
||||
}
|
||||
|
||||
public void read(String string) throws IOException {
|
||||
ZLXMLProcessor.read(this, new StringReader(string), 65536);
|
||||
}
|
||||
|
||||
public void read(Reader reader) throws IOException {
|
||||
ZLXMLProcessor.read(this, reader, 65536);
|
||||
}
|
||||
|
||||
public boolean dontCacheAttributeValues() {
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue