1
0
Fork 0
mirror of https://github.com/geometer/FBReaderJ.git synced 2025-10-04 18:29:23 +02:00

misc optimizations, first "working" version of book serialization/deserialization (missing things are marked with TODO comments)

This commit is contained in:
Nikolay Pultsin 2013-01-07 22:43:39 +00:00
parent db93063629
commit ff3ea90821
12 changed files with 319 additions and 32 deletions

View file

@ -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;

View file

@ -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 {

View file

@ -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");
}
}

View file

@ -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");
}
}

View file

@ -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) {

View file

@ -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) {
buffer
.append('<').append(tag).append('>')
.append(content)
.append("</").append(tag).append(">\n");
if (content != null) {
buffer
.append('<').append(tag).append('>')
.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("&", "&amp;");
}
if (data.indexOf('<') != -1) {
data = data.replaceAll("&", "&lt;");
}
if (data.indexOf('>') != -1) {
data = data.replaceAll("&", "&gt;");
}
if (data.indexOf('\'') != -1) {
data = data.replaceAll("&", "&apos;");
}
if (data.indexOf('"') != -1) {
data = data.replaceAll("&", "&quot;");
}
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");
}
}

View file

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

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

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

View file

@ -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;
@ -44,7 +52,15 @@ public abstract class ZLXMLReaderAdapter implements ZLXMLReader {
public void read(InputStream stream) throws IOException {
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;
}