/*
* Copyright (C) 2007-2011 Geometer Plus <contact@geometerplus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
package org.geometerplus.fbreader.formats.fb2;
import java.util.*;
import org.geometerplus.zlibrary.core.library.ZLibrary;
import org.geometerplus.zlibrary.core.constants.XMLNamespaces;
import org.geometerplus.zlibrary.core.xml.*;
import org.geometerplus.zlibrary.core.util.*;
import org.geometerplus.zlibrary.text.model.ZLTextParagraph;
import org.geometerplus.fbreader.bookmodel.*;
public final class FB2Reader extends ZLXMLReaderAdapter {
private final BookReader myBookReader;
private boolean myInsidePoem = false;
private boolean myInsideTitle = false;
private int myBodyCounter = 0;
private boolean myReadMainText = false;
private int mySectionDepth = 0;
private boolean mySectionStarted = false;
private byte myHyperlinkType;
private Base64EncodedImage myCurrentImage;
private boolean myInsideCoverpage = false;
private String myCoverImageReference;
private int myParagraphsBeforeBodyNumber = Integer.MAX_VALUE;
private final char[] SPACE = { ' ' };
private byte[] myTagStack = new byte[10];
private int myTagStackSize = 0;
public FB2Reader(BookModel model) {
myBookReader = new BookReader(model);
}
boolean readBook() {
Base64EncodedImage.resetCounter();
return ZLXMLProcessor.read(this, myBookReader.Model.Book.File);
}
public void startDocumentHandler() {
}
public void endDocumentHandler() {
}
public boolean dontCacheAttributeValues() {
return true;
}
public void characterDataHandler(char[] ch, int start, int length) {
if (length == 0) {
return;
}
final Base64EncodedImage image = myCurrentImage;
if (image != null) {
image.addData(ch, start, length);
} else {
myBookReader.addData(ch, start, length, false);
}
}
public void characterDataHandlerFinal(char[] ch, int start, int length) {
if (length == 0) {
return;
}
final Base64EncodedImage image = myCurrentImage;
if (image != null) {
image.addData(ch, start, length);
} else {
myBookReader.addData(ch, start, length, true);
}
}
public boolean endElementHandler(String tagName) {
final byte tag = myTagStack[--myTagStackSize];
switch (tag) {
case FB2Tag.P:
myBookReader.endParagraph();
break;
case FB2Tag.SUB:
myBookReader.addControl(FBTextKind.SUB, false);
break;
case FB2Tag.SUP:
myBookReader.addControl(FBTextKind.SUP, false);
break;
case FB2Tag.CODE:
myBookReader.addControl(FBTextKind.CODE, false);
break;
case FB2Tag.EMPHASIS:
myBookReader.addControl(FBTextKind.EMPHASIS, false);
break;
case FB2Tag.STRONG:
myBookReader.addControl(FBTextKind.STRONG, false);
break;
case FB2Tag.STRIKETHROUGH:
myBookReader.addControl(FBTextKind.STRIKETHROUGH, false);
break;
case FB2Tag.V:
case FB2Tag.SUBTITLE:
case FB2Tag.TEXT_AUTHOR:
case FB2Tag.DATE:
myBookReader.popKind();
myBookReader.endParagraph();
break;
case FB2Tag.CITE:
case FB2Tag.EPIGRAPH:
myBookReader.popKind();
break;
case FB2Tag.POEM:
myInsidePoem = false;
break;
case FB2Tag.STANZA:
myBookReader.beginParagraph(ZLTextParagraph.Kind.AFTER_SKIP_PARAGRAPH);
myBookReader.endParagraph();
myBookReader.beginParagraph(ZLTextParagraph.Kind.EMPTY_LINE_PARAGRAPH);
myBookReader.endParagraph();
myBookReader.popKind();
break;
case FB2Tag.SECTION:
if (myReadMainText) {
myBookReader.endContentsParagraph();
--mySectionDepth;
mySectionStarted = false;
} else {
myBookReader.unsetCurrentTextModel();
}
break;
case FB2Tag.ANNOTATION:
myBookReader.popKind();
if (myBodyCounter == 0) {
myBookReader.insertEndOfSectionParagraph();
myBookReader.unsetCurrentTextModel();
}
break;
case FB2Tag.TITLE:
myBookReader.popKind();
myBookReader.exitTitle();
myInsideTitle = false;
break;
case FB2Tag.BODY:
myBookReader.popKind();
myReadMainText = false;
if (myReadMainText) {
myBookReader.insertEndOfSectionParagraph();
}
if (mySectionDepth > 0) {
myBookReader.endContentsParagraph();
mySectionDepth = 0;
}
myBookReader.unsetCurrentTextModel();
break;
case FB2Tag.A:
myBookReader.addControl(myHyperlinkType, false);
break;
case FB2Tag.COVERPAGE:
if (myBodyCounter == 0) {
myInsideCoverpage = false;
myBookReader.insertEndOfSectionParagraph();
myBookReader.unsetCurrentTextModel();
}
break;
case FB2Tag.BINARY:
if (myCurrentImage != null) {
myCurrentImage.close();
myCurrentImage = null;
}
break;
default:
break;
}
return false;
}
public boolean startElementHandler(String tagName, ZLStringMap attributes) {
String id = attributes.getValue("id");
if (id != null) {
if (!myReadMainText) {
myBookReader.setFootnoteTextModel(id);
}
myBookReader.addHyperlinkLabel(id);
}
final byte tag = FB2Tag.getTagByName(tagName);
byte[] tagStack = myTagStack;
if (tagStack.length == myTagStackSize) {
tagStack = ZLArrayUtils.createCopy(tagStack, myTagStackSize, myTagStackSize * 2);
myTagStack = tagStack;
}
tagStack[myTagStackSize++] = tag;
switch (tag) {
case FB2Tag.P:
if (mySectionStarted) {
mySectionStarted = false;
} else if (myInsideTitle) {
myBookReader.addContentsData(SPACE);
}
myBookReader.beginParagraph(ZLTextParagraph.Kind.TEXT_PARAGRAPH);
break;
case FB2Tag.SUB:
myBookReader.addControl(FBTextKind.SUB, true);
break;
case FB2Tag.SUP:
myBookReader.addControl(FBTextKind.SUP, true);
break;
case FB2Tag.CODE:
myBookReader.addControl(FBTextKind.CODE, true);
break;
case FB2Tag.EMPHASIS:
myBookReader.addControl(FBTextKind.EMPHASIS, true);
break;
case FB2Tag.STRONG:
myBookReader.addControl(FBTextKind.STRONG, true);
break;
case FB2Tag.STRIKETHROUGH:
myBookReader.addControl(FBTextKind.STRIKETHROUGH, true);
break;
case FB2Tag.V:
myBookReader.pushKind(FBTextKind.VERSE);
myBookReader.beginParagraph(ZLTextParagraph.Kind.TEXT_PARAGRAPH);
break;
case FB2Tag.TEXT_AUTHOR:
myBookReader.pushKind(FBTextKind.AUTHOR);
myBookReader.beginParagraph(ZLTextParagraph.Kind.TEXT_PARAGRAPH);
break;
case FB2Tag.SUBTITLE:
myBookReader.pushKind(FBTextKind.SUBTITLE);
myBookReader.beginParagraph(ZLTextParagraph.Kind.TEXT_PARAGRAPH);
break;
case FB2Tag.DATE:
myBookReader.pushKind(FBTextKind.DATE);
myBookReader.beginParagraph(ZLTextParagraph.Kind.TEXT_PARAGRAPH);
break;
case FB2Tag.EMPTY_LINE:
myBookReader.beginParagraph(ZLTextParagraph.Kind.EMPTY_LINE_PARAGRAPH);
myBookReader.endParagraph();
break;
case FB2Tag.CITE:
myBookReader.pushKind(FBTextKind.CITE);
break;
case FB2Tag.EPIGRAPH:
myBookReader.pushKind(FBTextKind.EPIGRAPH);
break;
case FB2Tag.POEM:
myInsidePoem = true;
break;
case FB2Tag.STANZA:
myBookReader.pushKind(FBTextKind.STANZA);
myBookReader.beginParagraph(ZLTextParagraph.Kind.BEFORE_SKIP_PARAGRAPH);
myBookReader.endParagraph();
break;
case FB2Tag.SECTION:
if (myReadMainText) {
myBookReader.insertEndOfSectionParagraph();
++mySectionDepth;
myBookReader.beginContentsParagraph();
mySectionStarted = true;
}
break;
case FB2Tag.ANNOTATION:
if (myBodyCounter == 0) {
myBookReader.setMainTextModel();
}
myBookReader.pushKind(FBTextKind.ANNOTATION);
break;
case FB2Tag.TITLE:
if (myInsidePoem) {
myBookReader.pushKind(FBTextKind.POEM_TITLE);
} else if (mySectionDepth == 0) {
myBookReader.insertEndOfSectionParagraph();
myBookReader.pushKind(FBTextKind.TITLE);
} else {
myBookReader.pushKind(FBTextKind.SECTION_TITLE);
if (!myBookReader.hasContentsData()) {
myInsideTitle = true;
myBookReader.enterTitle();
}
}
break;
case FB2Tag.BODY:
++myBodyCounter;
myParagraphsBeforeBodyNumber = myBookReader.Model.BookTextModel.getParagraphsNumber();
final String name = attributes.getValue("name");
if (myBodyCounter == 1 || !"notes".equals(name)) {
myBookReader.setMainTextModel();
if (name != null) {
myBookReader.beginContentsParagraph();
myBookReader.addContentsData(name.toCharArray());
++mySectionDepth;
}
myReadMainText = true;
}
myBookReader.pushKind(FBTextKind.REGULAR);
break;
case FB2Tag.A:
{
String ref = getAttributeValue(attributes, XMLNamespaces.XLink, "href");
if ((ref != null) && (ref.length() != 0)) {
final String type = attributes.getValue("type");
if (ref.charAt(0) == '#') {
myHyperlinkType = "note".equals(type) ? FBTextKind.FOOTNOTE : FBTextKind.INTERNAL_HYPERLINK;
ref = ref.substring(1);
} else {
myHyperlinkType = FBTextKind.EXTERNAL_HYPERLINK;
}
myBookReader.addHyperlinkControl(myHyperlinkType, ref);
} else {
myHyperlinkType = FBTextKind.FOOTNOTE;
myBookReader.addControl(myHyperlinkType, true);
}
break;
}
case FB2Tag.COVERPAGE:
if (myBodyCounter == 0) {
myInsideCoverpage = true;
myBookReader.setMainTextModel();
}
break;
case FB2Tag.IMAGE:
{
String imgRef = getAttributeValue(attributes, XMLNamespaces.XLink, "href");
if ((imgRef != null) && (imgRef.length() != 0) && (imgRef.charAt(0) == '#')) {
String vOffset = attributes.getValue("voffset");
short offset = 0;
try {
offset = Short.parseShort(vOffset);
} catch (NumberFormatException e) {
}
imgRef = imgRef.substring(1);
if (!imgRef.equals(myCoverImageReference) ||
myParagraphsBeforeBodyNumber != myBookReader.Model.BookTextModel.getParagraphsNumber()) {
myBookReader.addImageReference(imgRef, offset);
}
if (myInsideCoverpage) {
myCoverImageReference = imgRef;
}
}
break;
}
case FB2Tag.BINARY:
final String contentType = attributes.getValue("content-type");
final String imgId = attributes.getValue("id");
if ((contentType != null) && (id != null)) {
myCurrentImage = new Base64EncodedImage(contentType);
myBookReader.addImage(imgId, myCurrentImage);
}
break;
default:
break;
}
return false;
}
public boolean processNamespaces() {
return true;
}
public void addExternalEntities(HashMap<String,char[]> entityMap) {
entityMap.put("FBReaderVersion", ZLibrary.Instance().getVersionName().toCharArray());
}
public List<String> externalDTDs() {
return Collections.emptyList();
}
}