diff --git a/ChangeLog b/ChangeLog
index 8bd8d7015..f1475fb08 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,11 +6,13 @@
* (planned) Fixed authors list/tags list editing
* (planned) CSS selectors priority
-===== 2.3 (Feb ??, 2015) =====
+===== 2.3 (Feb 14, 2015) =====
* File format filter option (can be changed from the bookshelf plugin)
* Samsung multi-window support
* Bookshelf view item in the library
* Updated Persian localisation by biomanw (https://github.com/biomanw)
+* Better RTF encoding detection
+* Don't fail on RTF files with .doc extension
===== 2.2.7 (Jan 30, 2015) =====
* Fixed behaviour for pages with multiple images (fb2.zip only)
diff --git a/assets/resources/application/neutral.xml b/assets/resources/application/neutral.xml
index 8b0712bd0..1f0f5c5f4 100644
--- a/assets/resources/application/neutral.xml
+++ b/assets/resources/application/neutral.xml
@@ -7,7 +7,7 @@
-
+
diff --git a/jni/NativeFormats/fbreader/src/formats/doc/DocPlugin.cpp b/jni/NativeFormats/fbreader/src/formats/doc/DocPlugin.cpp
index e00b5816e..056da2eb3 100644
--- a/jni/NativeFormats/fbreader/src/formats/doc/DocPlugin.cpp
+++ b/jni/NativeFormats/fbreader/src/formats/doc/DocPlugin.cpp
@@ -27,6 +27,7 @@
#include "DocMetaInfoReader.h"
#include "DocBookReader.h"
#include "DocStreams.h"
+#include "../rtf/RtfPlugin.h"
#include "../../bookmodel/BookModel.h"
#include "../../library/Book.h"
@@ -36,6 +37,17 @@ DocPlugin::DocPlugin() {
DocPlugin::~DocPlugin() {
}
+static bool isRtf(const Book &book) {
+ shared_ptr stream = book.file().inputStream();
+ if (stream.isNull() || !stream->open()) {
+ return false;
+ }
+ char buffer[6] = { 0 };
+ stream->read(buffer, 5);
+ static const std::string RTF_BYTES = "{\\rtf";
+ return RTF_BYTES == buffer;
+}
+
bool DocPlugin::providesMetainfo() const {
return true;
}
@@ -49,6 +61,10 @@ bool DocPlugin::acceptsFile(const ZLFile &file) const {
}
bool DocPlugin::readMetainfo(Book &book) const {
+ if (::isRtf(book)) {
+ return RtfPlugin().readMetainfo(book);
+ }
+
if (!DocMetaInfoReader(book).readMetainfo()) {
return false;
}
@@ -71,5 +87,9 @@ bool DocPlugin::readLanguageAndEncoding(Book &/*book*/) const {
}
bool DocPlugin::readModel(BookModel &model) const {
+ if (::isRtf(*model.book())) {
+ return RtfPlugin().readModel(model);
+ }
+
return DocBookReader(model, model.book()->encoding()).readBook();
}
diff --git a/jni/NativeFormats/fbreader/src/formats/rtf/RtfPlugin.cpp b/jni/NativeFormats/fbreader/src/formats/rtf/RtfPlugin.cpp
index 6b65b44c9..9b3a633e4 100644
--- a/jni/NativeFormats/fbreader/src/formats/rtf/RtfPlugin.cpp
+++ b/jni/NativeFormats/fbreader/src/formats/rtf/RtfPlugin.cpp
@@ -34,23 +34,16 @@ bool RtfPlugin::providesMetainfo() const {
}
const std::string RtfPlugin::supportedFileType() const {
- return "rtf";
+ return "RTF";
}
bool RtfPlugin::readMetainfo(Book &book) const {
+ readLanguageAndEncoding(book);
+
if (!RtfDescriptionReader(book).readDocument(book.file())) {
return false;
}
- if (book.encoding().empty()) {
- book.setEncoding(ZLEncodingConverter::UTF8);
- } else if (book.language().empty()) {
- shared_ptr stream = new RtfReaderStream(book.file(), 50000);
- if (!stream.isNull()) {
- detectLanguage(book, *stream, book.encoding());
- }
- }
-
return true;
}
@@ -64,5 +57,19 @@ bool RtfPlugin::readModel(BookModel &model) const {
}
bool RtfPlugin::readLanguageAndEncoding(Book &book) const {
+ if (book.encoding().empty()) {
+ shared_ptr stream = new RtfReaderStream(book.file(), 50000);
+ if (!stream.isNull()) {
+ detectEncodingAndLanguage(book, *stream);
+ }
+ if (book.encoding().empty()) {
+ book.setEncoding(ZLEncodingConverter::UTF8);
+ }
+ } else if (book.language().empty()) {
+ shared_ptr stream = new RtfReaderStream(book.file(), 50000);
+ if (!stream.isNull()) {
+ detectLanguage(book, *stream, book.encoding());
+ }
+ }
return true;
}
diff --git a/src/org/geometerplus/fbreader/book/XMLSerializer.java b/src/org/geometerplus/fbreader/book/XMLSerializer.java
index 241878cf3..d2b93c032 100644
--- a/src/org/geometerplus/fbreader/book/XMLSerializer.java
+++ b/src/org/geometerplus/fbreader/book/XMLSerializer.java
@@ -35,9 +35,13 @@ import org.geometerplus.zlibrary.core.util.ZLColor;
import org.geometerplus.zlibrary.text.view.ZLTextPosition;
class XMLSerializer extends AbstractSerializer {
+ private StringBuilder builder() {
+ return new StringBuilder("");
+ }
+
@Override
public String serialize(BookQuery query) {
- final StringBuilder buffer = new StringBuilder();
+ final StringBuilder buffer = builder();
appendTag(buffer, "query", false,
"limit", String.valueOf(query.Limit),
"page", String.valueOf(query.Page)
@@ -136,7 +140,7 @@ class XMLSerializer extends AbstractSerializer {
@Override
public String serialize(BookmarkQuery query) {
- final StringBuilder buffer = new StringBuilder();
+ final StringBuilder buffer = builder();
appendTag(buffer, "query", false,
"visible", String.valueOf(query.Visible),
"limit", String.valueOf(query.Limit),
@@ -164,7 +168,7 @@ class XMLSerializer extends AbstractSerializer {
@Override
public String serialize(Book book) {
- final StringBuilder buffer = new StringBuilder();
+ final StringBuilder buffer = builder();
serialize(buffer, book);
return buffer.toString();
}
@@ -262,7 +266,7 @@ class XMLSerializer extends AbstractSerializer {
@Override
public String serialize(Bookmark bookmark) {
- final StringBuilder buffer = new StringBuilder();
+ final StringBuilder buffer = builder();
appendTag(
buffer, "bookmark", false,
"id", String.valueOf(bookmark.getId()),
@@ -325,7 +329,7 @@ class XMLSerializer extends AbstractSerializer {
@Override
public String serialize(HighlightingStyle style) {
- final StringBuilder buffer = new StringBuilder();
+ final StringBuilder buffer = builder();
final ZLColor bgColor = style.getBackgroundColor();
final ZLColor fgColor = style.getForegroundColor();
appendTag(buffer, "style", true,
@@ -441,23 +445,41 @@ class XMLSerializer extends AbstractSerializer {
}
}
- private static String escapeForXml(String data) {
- if (data.indexOf('&') != -1) {
- data = data.replaceAll("&", "&");
+ private static CharSequence escapeForXml(String data) {
+ final StringBuilder buffer = new StringBuilder();
+
+ final int len = data.length();
+ for (int i = 0; i < len; ++i) {
+ final char ch = data.charAt(i);
+ switch (ch) {
+ case '\u0009':
+ buffer.append(ch);
+ break;
+ default:
+ if ((ch >= '\u0020' && ch <= '\uD7FF') ||
+ (ch >= '\u0E00' && ch <= '\uFFFD')) {
+ buffer.append(ch);
+ }
+ break;
+ case '&':
+ buffer.append("&");
+ break;
+ case '<':
+ buffer.append("<");
+ break;
+ case '>':
+ buffer.append(">");
+ break;
+ case '"':
+ buffer.append(""");
+ break;
+ case '\'':
+ buffer.append("'");
+ break;
+ }
}
- 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;
+
+ return buffer;
}
private static void clear(StringBuilder buffer) {