Refactor the tag parsing system

Much cleaner, I like this a whole lot more.
This commit is contained in:
Bradlee Speice 2014-12-01 11:51:13 -05:00
parent 7f221ed863
commit caf2227555
37 changed files with 117 additions and 2604 deletions

View File

@ -2,7 +2,6 @@ package org.bspeice.minimalbible.activity.viewer;
import android.util.Log; import android.util.Log;
import org.bspeice.minimalbible.service.lookup.VerseLookup;
import org.bspeice.minimalbible.service.manager.BookManager; import org.bspeice.minimalbible.service.manager.BookManager;
import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.Book;
@ -97,18 +96,11 @@ public class BibleViewerModules {
return new BookManager(); return new BookManager();
} }
@Provides
@Singleton
VerseLookup verseLookup(@Named("MainBook") Book b) {
return new VerseLookup(b);
}
@Provides @Provides
@Named("MainAdapter") @Named("MainAdapter")
@Singleton @Singleton
BookAdapter bookAdapter(@Named("MainBook") Book b, VerseLookup v, BookAdapter bookAdapter(@Named("MainBook") Book b, BibleViewerPreferences prefs) {
BibleViewerPreferences prefs) { return new BookAdapter(b, prefs);
return new BookAdapter(b, v, prefs);
} }
@Provides @Provides

View File

@ -1,28 +0,0 @@
package org.bspeice.minimalbible.service.format;
/**
* see http://www.crosswire.org/wiki/Frontends:URI_Standard
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class Constants {
// Strings for URL protocols/URI schemes
public static final String SWORD_PROTOCOL = "sword"; //$NON-NLS-1$
public static final String BIBLE_PROTOCOL = "bible"; //$NON-NLS-1$
public static final String DICTIONARY_PROTOCOL = "dict"; //$NON-NLS-1$
public static final String GREEK_DEF_PROTOCOL = "gdef"; //$NON-NLS-1$
public static final String HEBREW_DEF_PROTOCOL = "hdef"; //$NON-NLS-1$
public static final String ALL_GREEK_OCCURRENCES_PROTOCOL = "allgoccur"; //$NON-NLS-1$
public static final String ALL_HEBREW_OCCURRENCES_PROTOCOL = "allhoccur"; //$NON-NLS-1$
public static final String ROBINSON_GREEK_MORPH_PROTOCOL = "robinson"; //$NON-NLS-1$
public static final String HEBREW_MORPH_PROTOCOL = "hmorph"; //$NON-NLS-1$
public static final String COMMENTARY_PROTOCOL = "comment"; //$NON-NLS-1$
public static class HTML {
public static final String NBSP = "&#160;";
public static final String SPACE = " ";
public static final String BR = "<br />";
}
}

View File

@ -1,31 +0,0 @@
package org.bspeice.minimalbible.service.format;
/**
* Info on a note or cross reference
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class Note {
public static final String SUMMARY = "summary";
;
public static final String DETAIL = "detail";
private static final String TAG = "Note";
private String noteRef;
private String noteText;
public Note(int verseNo, String noteRef, String noteText, NoteType noteType, String osisRef) {
super();
this.noteRef = noteRef;
this.noteText = noteText;
}
@Override
public String toString() {
return noteRef + ":" + noteText;
}
public enum NoteType {TYPE_GENERAL, TYPE_REFERENCE}
}

View File

@ -1,91 +0,0 @@
package org.bspeice.minimalbible.service.format;
import org.bspeice.minimalbible.service.format.osistohtml.HtmlTextWriter;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
/**
* Convert OSIS input into Canonical text (used when creating search index)
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class OsisSaxHandler extends DefaultHandler {
// debugging
private boolean isDebugMode = false;
private HtmlTextWriter writer;
public OsisSaxHandler() {
writer = new HtmlTextWriter();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
/* @Override */
public String toString() {
return writer.getHtml();
}
protected String getName(String eName, String qName) {
if (eName != null && eName.length() > 0) {
return eName;
} else {
return qName; // not namespace-aware
}
}
protected void write(String s) {
writer.write(s);
}
/**
* check the value of the specified attribute and return true if same as checkvalue
*
* @param attrs
* @param attrName
* @param checkValue
* @return
*/
protected boolean isAttrValue(Attributes attrs, String attrName, String checkValue) {
if (attrs == null) {
return false;
}
String value = attrs.getValue(attrName);
return checkValue.equals(value);
}
protected void debug(String name, Attributes attrs, boolean isStartTag) {
if (isDebugMode) {
write("*" + name);
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
String aName = attrs.getLocalName(i); // Attr name
if ("".equals(aName)) aName = attrs.getQName(i);
write(" ");
write(aName + "=\"" + attrs.getValue(i) + "\"");
}
}
write("*\n");
}
}
public void setDebugMode(boolean isDebugMode) {
this.isDebugMode = isDebugMode;
}
protected void reset() {
writer.reset();
}
public HtmlTextWriter getWriter() {
return writer;
}
}

View File

@ -1,131 +0,0 @@
package org.bspeice.minimalbible.service.format;
import org.bspeice.minimalbible.service.format.osistohtml.TagHandlerHelper;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import java.util.Stack;
/**
* Convert OSIS input into Canonical text (used when creating search index)
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class OsisToCanonicalTextSaxHandler extends OsisSaxHandler {
@SuppressWarnings("unused")
private int currentVerseNo;
private Stack<CONTENT_STATE> writeContentStack = new Stack<CONTENT_STATE>();
public OsisToCanonicalTextSaxHandler() {
super();
}
@Override
public void startDocument() {
reset();
// default mode is to write
writeContentStack.push(CONTENT_STATE.WRITE);
}
/*
*Called when the Parser Completes parsing the Current XML File.
*/
@Override
public void endDocument() {
// pop initial value
writeContentStack.pop();
assert (writeContentStack.isEmpty());
}
/*
* Called when the starting of the Element is reached. For Example if we have Tag
* called <Title> ... </Title>, then this method is called when <Title> tag is
* Encountered while parsing the Current XML File. The AttributeList Parameter has
* the list of all Attributes declared for the Current Element in the XML File.
*/
@Override
public void startElement(String namespaceURI,
String sName, // simple name
String qName, // qualified name
Attributes attrs) {
String name = getName(sName, qName); // element name
debug(name, attrs, true);
// if encountering either a verse tag or if the current tag is marked as being canonical then turn on writing
if (isAttrValue(attrs, "canonical", "true")) {
writeContentStack.push(CONTENT_STATE.WRITE);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_VERSE)) {
if (attrs != null) {
currentVerseNo = TagHandlerHelper.osisIdToVerseNum(attrs.getValue("", OSISUtil.OSIS_ATTR_OSISID));
}
writeContentStack.push(CONTENT_STATE.WRITE);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_NOTE)) {
writeContentStack.push(CONTENT_STATE.IGNORE);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_TITLE)) {
writeContentStack.push(CONTENT_STATE.IGNORE);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_REFERENCE)) {
writeContentStack.push(CONTENT_STATE.IGNORE);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_L) ||
name.equals(OSISUtil.OSIS_ELEMENT_LB) ||
name.equals(OSISUtil.OSIS_ELEMENT_P)) {
// these occur in Psalms to separate different paragraphs.
// A space is needed for TTS not to be confused by punctuation with a missing space like 'toward us,and the'
write(" ");
//if writing then continue. Also if ignoring then continue
writeContentStack.push(writeContentStack.peek());
} else {
// unknown tags rely on parent tag to determine if content is canonical e.g. the italic tag in the middle of canonical text
writeContentStack.push(writeContentStack.peek());
}
}
/*
* Called when the Ending of the current Element is reached. For example in the
* above explanation, this method is called when </Title> tag is reached
*/
@Override
public void endElement(String namespaceURI,
String sName, // simple name
String qName // qualified name
) {
String name = getName(sName, qName);
debug(name, null, false);
if (name.equals(OSISUtil.OSIS_ELEMENT_VERSE)) {
// A space is needed to separate one verse from the next, otherwise the 2 verses butt up against each other
// which looks bad and confuses TTS
write(" ");
}
// now this tag has ended pop the write/ignore state for the parent tag
writeContentStack.pop();
}
/*
* Handle characters encountered in tags
*/
@Override
public void characters(char buf[], int offset, int len) {
if (CONTENT_STATE.WRITE.equals(writeContentStack.peek())) {
String s = new String(buf, offset, len);
write(s);
}
}
protected void writeContent(boolean writeContent) {
if (writeContent) {
writeContentStack.push(CONTENT_STATE.WRITE);
} else {
writeContentStack.push(CONTENT_STATE.IGNORE);
}
}
private enum CONTENT_STATE {WRITE, IGNORE}
}

View File

@ -1,66 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlSaxHandler.VerseInfo;
import org.crosswire.jsword.passage.Verse;
import java.util.HashSet;
import java.util.Set;
/**
* Display an img if the current verse has MyNote
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class BookmarkMarker {
private Set<Integer> bookmarkedVerses = new HashSet<Integer>();
private OsisToHtmlParameters parameters;
private VerseInfo verseInfo;
private HtmlTextWriter writer;
private boolean bookmarkOpenTagWritten = false;
public BookmarkMarker(OsisToHtmlParameters parameters, VerseInfo verseInfo, HtmlTextWriter writer) {
this.parameters = parameters;
this.verseInfo = verseInfo;
this.writer = writer;
// create hashset of verses to optimise verse note lookup
bookmarkedVerses.clear();
if (parameters.getVersesWithBookmarks() != null) {
for (Verse verse : parameters.getVersesWithBookmarks()) {
bookmarkedVerses.add(verse.getVerse());
}
}
}
public String getTagName() {
return "";
}
/**
* just after verse start tag
*/
public void start() {
if (bookmarkedVerses != null && parameters.isShowBookmarks()) {
if (bookmarkedVerses.contains(verseInfo.currentVerseNo)) {
writer.write("<img src='file:///android_asset/images/GoldStar16x16.png' class='myNoteImg'/>");
// writer.write("<span class='bookmark'>");
bookmarkOpenTagWritten = true;
}
}
}
public void end() {
if (bookmarkOpenTagWritten) {
// writer.write("</span>");
bookmarkOpenTagWritten = false;
}
}
}

View File

@ -1,39 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.apache.commons.lang3.StringUtils;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
/**
* Handle <figure src="imagefile.jpg" /> to display pictures
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class FigureHandler {
private HtmlTextWriter writer;
private OsisToHtmlParameters parameters;
public FigureHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
this.parameters = parameters;
this.writer = writer;
}
public String getTagName() {
return "figure";
}
public void start(Attributes attrs) {
// Refer to Gen 3:14 in ESV for example use of type=x-indent
String src = attrs.getValue(OSISUtil.ATTRIBUTE_FIGURE_SRC);
if (StringUtils.isNotEmpty(src)) {
writer.write("<img src='" + parameters.getModuleBasePath() + "/" + src + "'/>");
}
}
public void end() {
}
}

View File

@ -1,66 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import java.util.Arrays;
import java.util.List;
import static org.crosswire.jsword.book.OSISUtil.HI_ACROSTIC;
import static org.crosswire.jsword.book.OSISUtil.HI_BOLD;
import static org.crosswire.jsword.book.OSISUtil.HI_EMPHASIS;
import static org.crosswire.jsword.book.OSISUtil.HI_ILLUMINATED;
import static org.crosswire.jsword.book.OSISUtil.HI_ITALIC;
import static org.crosswire.jsword.book.OSISUtil.HI_LINETHROUGH;
import static org.crosswire.jsword.book.OSISUtil.HI_NORMAL;
import static org.crosswire.jsword.book.OSISUtil.HI_SMALL_CAPS;
import static org.crosswire.jsword.book.OSISUtil.HI_SUB;
import static org.crosswire.jsword.book.OSISUtil.HI_SUPER;
import static org.crosswire.jsword.book.OSISUtil.HI_UNDERLINE;
/**
* Handle hi element e.g. <hi type="italic">the child with his mother Mary</hi>
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class HiHandler {
// possible values of type attribute
private static final List<String> HI_TYPE_LIST = Arrays.asList(new String[]{HI_ACROSTIC, HI_BOLD, HI_EMPHASIS, HI_ILLUMINATED, HI_ITALIC, HI_LINETHROUGH, HI_NORMAL, HI_SMALL_CAPS, HI_SUB, HI_SUPER, HI_UNDERLINE});
private final static String DEFAULT = "bold";
private HtmlTextWriter writer;
public HiHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
this.writer = writer;
}
public String getTagName() {
return "hi";
}
public void start(Attributes attrs) {
String type = attrs.getValue(OSISUtil.OSIS_ATTR_TYPE);
start(type, DEFAULT);
}
public void start(String style, String defaultStyle) {
if (style == null || !HI_TYPE_LIST.contains(style)) {
style = defaultStyle;
}
// add any styles that are relevant - the tag name and the style attribute
String cssClasses = getTagName() + " hi_" + style;
// start span with CSS class of 'hi_*' e.g. hi_bold
writer.write("<span class=\'" + cssClasses + "\'>");
}
public void end() {
writer.write("</span>");
}
}

View File

@ -1,91 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
/**
* Write characters out to a StringBuilder - used while creating html for display
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's authors.
*/
public class HtmlTextWriter {
private StringBuilder writer;
private int dontWriteRequestCount = 0;
private int writeTempStoreRequestCount = 0;
private StringBuilder tempStore = new StringBuilder();
// allow insert at a certain position
private String overwrittenString = "";
public HtmlTextWriter() {
writer = new StringBuilder();
}
public void write(String htmlText) {
if (dontWriteRequestCount > 0) {
// ignore all text
} else if (writeTempStoreRequestCount == 0) {
writer.append(htmlText);
} else {
tempStore.append(htmlText);
}
}
/**
* allow pre-verse headings
*/
public void beginInsertAt(int insertOffset) {
overwrittenString = writer.substring(insertOffset);
writer.delete(insertOffset, writer.length());
}
/**
* finish inserting and restore overwritten tail of string
*/
public void finishInserting() {
writer.append(overwrittenString);
overwrittenString = "";
}
public int getPosition() {
return writer.length();
}
public void removeAfter(int position) {
writer.delete(position, writer.length());
}
public void reset() {
writer.setLength(0);
}
public void writeToTempStore() {
writeTempStoreRequestCount++;
}
public void finishWritingToTempStore() {
writeTempStoreRequestCount--;
}
public void clearTempStore() {
tempStore.delete(0, tempStore.length());
}
public String getTempStoreString() {
return tempStore.toString();
}
public String getHtml() {
return writer.toString();
}
public void setDontWrite(boolean dontWrite) {
if (dontWrite) {
dontWriteRequestCount++;
} else {
dontWriteRequestCount--;
}
}
}

View File

@ -1,56 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.xml.sax.Attributes;
import java.util.Stack;
/**
* The lg or "line group" element is used to contain any group of poetic lines. Poetic lines are handled at the line level by And Bible, not line group
* so this class does nothing.
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
@SuppressWarnings("unused")
public class LGHandler {
private HtmlTextWriter writer;
private OsisToHtmlParameters parameters;
private Stack<LGType> stack = new Stack<LGType>();
public LGHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
this.parameters = parameters;
this.writer = writer;
}
public String getTagName() {
return "lg";
}
public void start(Attributes attrs) {
// ignore this for now because it is untested
// LGType lgtype = LGType.IGNORE;
// if (TagHandlerHelper.isAttr(OSISUtil.OSIS_ATTR_SID, attrs) ||
// TagHandlerHelper.isAttr(OSISUtil.OSIS_ATTR_EID, attrs)) {
// lgtype = LGType.IGNORE;
// } else {
// // allow spacing around groups of poetry
// writer.write("<div class='lg'>");
// lgtype = LGType.DIV;
// }
// stack.push(lgtype);
}
public void end() {
// LGType lgtype = stack.pop();
// if (LGType.DIV.equals(lgtype)) {
// writer.write("</div>");
// }
}
enum LGType {DIV, IGNORE}
}

View File

@ -1,84 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import java.util.Stack;
import static org.bspeice.minimalbible.service.format.Constants.HTML;
/**
* This can either signify a quote or Red Letter
* Example from ESV Prov 19:1
* <l sID="x9938"/>...<l eID="x9938" type="x-br"/><l sID="x9939" type="x-indent"/>..<l eID="x9939" type="x-br"/>
* <p/>
* Apparently quotation marks are not supposed to appear in the KJV (https://sites.google.com/site/kjvtoday/home/Features-of-the-KJV/quotation-marks)
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class LHandler {
private static String indent_html = HTML.NBSP + HTML.NBSP;
private HtmlTextWriter writer;
private Stack<LType> stack = new Stack<LType>();
public LHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
this.writer = writer;
int indentCharCount = 4; // TODO: Set a standard value for this
indent_html = StringUtils.repeat(HTML.NBSP, indentCharCount);
}
public String getTagName() {
return "l";
}
public void startL(Attributes attrs) {
// Refer to Gen 3:14 in ESV for example use of type=x-indent
String type = attrs.getValue(OSISUtil.OSIS_ATTR_TYPE);
int level = TagHandlerHelper.getAttribute(OSISUtil.OSIS_ATTR_LEVEL, attrs, 1);
// make numIndents default to zero
int numIndents = Math.max(0, level - 1);
LType ltype = LType.IGNORE;
if (StringUtils.isNotEmpty(type)) {
if (type.contains("indent")) {
// this tag is specifically for indenting so ensure there is an indent
numIndents = numIndents + 1;
writer.write(StringUtils.repeat(indent_html, numIndents));
ltype = LType.INDENT;
} else if (type.contains("br")) {
writer.write(HTML.BR);
ltype = LType.BR;
} else {
ltype = LType.IGNORE;
Log.d("LHandler", "Unknown <l> tag type:" + type);
}
} else if (TagHandlerHelper.isAttr(OSISUtil.OSIS_ATTR_SID, attrs)) {
writer.write(StringUtils.repeat(indent_html, numIndents));
ltype = LType.IGNORE;
} else if (TagHandlerHelper.isAttr(OSISUtil.OSIS_ATTR_EID, attrs)) {
// e.g. Isaiah 40:12
writer.write(HTML.BR);
ltype = LType.BR;
} else {
//simple <l>
writer.write(StringUtils.repeat(indent_html, numIndents));
ltype = LType.END_BR;
}
stack.push(ltype);
}
public void endL() {
LType type = stack.pop();
if (LType.END_BR.equals(type)) {
writer.write(HTML.BR);
}
}
enum LType {INDENT, BR, END_BR, IGNORE}
}

View File

@ -1,61 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlSaxHandler.VerseInfo;
import org.crosswire.jsword.passage.Key;
import org.crosswire.jsword.passage.KeyUtil;
import org.crosswire.jsword.passage.Verse;
import java.util.HashSet;
import java.util.Set;
/**
* Display an img if the current verse has MyNote
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class MyNoteMarker {
private Set<Integer> myNoteVerses = new HashSet<Integer>();
private OsisToHtmlParameters parameters;
private VerseInfo verseInfo;
private HtmlTextWriter writer;
public MyNoteMarker(OsisToHtmlParameters parameters, VerseInfo verseInfo, HtmlTextWriter writer) {
this.parameters = parameters;
this.verseInfo = verseInfo;
this.writer = writer;
// create hashmap of verses to optimise verse note lookup
myNoteVerses.clear();
if (parameters.getVersesWithNotes() != null) {
for (Key key : parameters.getVersesWithNotes()) {
Verse verse = KeyUtil.getVerse(key);
myNoteVerses.add(verse.getVerse());
}
}
}
public String getTagName() {
return "";
}
/**
* just after verse start tag
*/
public void start() {
if (myNoteVerses != null && parameters.isShowMyNotes()) {
if (myNoteVerses.contains(verseInfo.currentVerseNo)) {
writer.write("<img src='file:///android_asset/images/pencil16x16.png' class='myNoteImg'/>");
}
}
}
public void end() {
}
}

View File

@ -1,141 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.apache.commons.lang3.StringUtils;
import org.bspeice.minimalbible.service.format.Note;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlSaxHandler.VerseInfo;
import org.xml.sax.Attributes;
import java.util.ArrayList;
import java.util.List;
import static org.bspeice.minimalbible.service.format.Note.NoteType;
/**
* Convert OSIS tags into html tags
* <p/>
* Example OSIS tags from KJV Ps 119 v1 showing title, w, note
* <title canonical="true" subType="x-preverse" type="section">
* <foreign n="?">ALEPH.</foreign>
* </title>
* <w lemma="strong:H0835">Blessed</w> <transChange type="added">are</transChange> <w lemma="strong:H08549">the undefiled</w>
* ... <w lemma="strong:H01980" morph="strongMorph:TH8802">who walk</w>
* ... <w lemma="strong:H03068">of the <seg><divineName>Lord</divineName></seg></w>.
* <note type="study">undefiled: or, perfect, or, sincere</note>
* <p/>
* Example of notes cross references from ESV
* In the <note n="a" osisID="Gen.1.1!crossReference.a" osisRef="Gen.1.1" type="crossReference"><reference osisRef="Job.38.4-Job.38.7">Job 38:4-7</reference>; <reference osisRef="Ps.33.6">Ps. 33:6</reference>; <reference osisRef="Ps.136.5">136:5</reference>; <reference osisRef="Isa.42.5">Isa. 42:5</reference>; <reference osisRef="Isa.45.18">45:18</reference>; <reference osisRef="John.1.1-John.1.3">John 1:1-3</reference>; <reference osisRef="Acts.14.15">Acts 14:15</reference>; <reference osisRef="Acts.17.24">17:24</reference>; <reference osisRef="Col.1.16-Col.1.17">Col. 1:16, 17</reference>; <reference osisRef="Heb.1.10">Heb. 1:10</reference>; <reference osisRef="Heb.11.3">11:3</reference>; <reference osisRef="Rev.4.11">Rev. 4:11</reference></note>beginning
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class NoteHandler {
private OsisToHtmlParameters parameters;
private VerseInfo verseInfo;
private int noteCount = 0;
//todo temporarily use a string but later switch to Map<int,String> of verse->note
private List<Note> notesList = new ArrayList<Note>();
private boolean isInNote = false;
private String currentNoteRef;
private HtmlTextWriter writer;
public NoteHandler(OsisToHtmlParameters osisToHtmlParameters, VerseInfo verseInfo, HtmlTextWriter theWriter) {
this.parameters = osisToHtmlParameters;
this.verseInfo = verseInfo;
this.writer = theWriter;
}
public void startNote(Attributes attrs) {
isInNote = true;
currentNoteRef = getNoteRef(attrs);
writeNoteRef(currentNoteRef);
// prepare to fetch the actual note into the notes repo
writer.writeToTempStore();
}
/*
* Called when the Ending of the current Element is reached. For example in the
* above explanation, this method is called when </Title> tag is reached
*/
public void endNote() {
String noteText = writer.getTempStoreString();
if (noteText.length() > 0) {
if (!StringUtils.containsOnly(noteText, "[];().,")) {
Note note = new Note(verseInfo.currentVerseNo, currentNoteRef, noteText, NoteType.TYPE_GENERAL, null);
notesList.add(note);
}
// and clear the buffer
writer.clearTempStore();
}
isInNote = false;
writer.finishWritingToTempStore();
}
/**
* a reference is finished and now the note must be added
*/
public void addNoteForReference(String refText, String osisRef) {
// add teh html to show a note character in the (bible) text
// a few modules like HunUj have refs in the text but not surrounded by a Note tag (like esv) so need to add Note here
// special code to cope with HunUj problem
if (parameters.isAutoWrapUnwrappedRefsInNote() && !isInNote()) {
currentNoteRef = createNoteRef();
writeNoteRef(currentNoteRef);
}
// record the note information to show if user requests to see notes for this verse
if (isInNote || parameters.isAutoWrapUnwrappedRefsInNote()) {
Note note = new Note(verseInfo.currentVerseNo, currentNoteRef, refText, NoteType.TYPE_REFERENCE, osisRef);
notesList.add(note);
}
}
/**
* either use the 'n' attribute for the note ref or just get the next character in a list a-z
*
* @return a single char to use as a note ref
*/
private String getNoteRef(Attributes attrs) {
// if the ref is specified as an attribute then use that
String noteRef = attrs.getValue("n");
if (StringUtils.isEmpty(noteRef)) {
noteRef = createNoteRef();
}
return noteRef;
}
/**
* either use the character passed in or get the next character in a list a-z
*
* @return a single char to use as a note ref
*/
private String createNoteRef() {
// else just get the next char
int inta = (int) 'a';
char nextNoteChar = (char) (inta + (noteCount++ % 26));
return String.valueOf(nextNoteChar);
}
/**
* write noteref html to outputstream
*/
private void writeNoteRef(String noteRef) {
if (parameters.isShowNotes()) {
writer.write("<span class='noteRef'>" + noteRef + "</span> ");
}
}
public boolean isInNote() {
return isInNote;
}
public List<Note> getNotesList() {
return notesList;
}
}

View File

@ -1,225 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.crosswire.jsword.passage.Key;
import org.crosswire.jsword.passage.KeyUtil;
import org.crosswire.jsword.passage.Verse;
import org.crosswire.jsword.versification.Versification;
import org.crosswire.jsword.versification.system.Versifications;
import java.net.URI;
import java.util.List;
/**
* Parameters passed into the Osis to HTML converter
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class OsisToHtmlParameters {
private String languageCode = "en";
private boolean isLeftToRight = true;
private boolean isShowTitles = true;
private boolean isShowVerseNumbers = false;
private boolean isVersePerline = false;
private boolean isShowMyNotes = false;
private boolean isShowBookmarks = false;
private boolean isShowNotes = false;
private boolean isAutoWrapUnwrappedRefsInNote = false;
// used as a basis if a reference has only chapter and no book
private Verse basisRef;
private Versification documentVersification;
private String font;
private String cssClassForCustomFont;
private boolean isShowStrongs = false;
private boolean isShowMorphology = false;
private boolean isRedLetter = false;
private String extraStylesheet;
private String extraFooter;
private boolean convertStrongsRefsToLinks;
private List<Verse> versesWithNotes;
private List<Verse> versesWithBookmarks;
private URI moduleBasePath;
public String getLanguageCode() {
return languageCode;
}
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
public boolean isLeftToRight() {
return isLeftToRight;
}
public void setLeftToRight(boolean isLeftToRight) {
this.isLeftToRight = isLeftToRight;
}
public boolean isShowTitles() {
return isShowTitles;
}
public void setShowTitles(boolean isShowTitles) {
this.isShowTitles = isShowTitles;
}
public boolean isShowVerseNumbers() {
return isShowVerseNumbers;
}
public void setShowVerseNumbers(boolean isShowVerseNumbers) {
this.isShowVerseNumbers = isShowVerseNumbers;
}
public boolean isVersePerline() {
return isVersePerline;
}
public void setVersePerline(boolean isVersePerline) {
this.isVersePerline = isVersePerline;
}
public boolean isShowMyNotes() {
return isShowMyNotes;
}
public void setShowMyNotes(boolean isShowMyNotes) {
this.isShowMyNotes = isShowMyNotes;
}
public boolean isShowBookmarks() {
return isShowBookmarks;
}
public void setShowBookmarks(boolean isShowBookmarks) {
this.isShowBookmarks = isShowBookmarks;
}
public boolean isShowNotes() {
return isShowNotes;
}
public void setShowNotes(boolean isShowNotes) {
this.isShowNotes = isShowNotes;
}
public boolean isAutoWrapUnwrappedRefsInNote() {
return isAutoWrapUnwrappedRefsInNote;
}
public void setAutoWrapUnwrappedRefsInNote(boolean isAutoWrapUnwrappedRefsInNote) {
this.isAutoWrapUnwrappedRefsInNote = isAutoWrapUnwrappedRefsInNote;
}
public boolean isShowStrongs() {
return isShowStrongs;
}
public void setShowStrongs(boolean isShowStrongs) {
this.isShowStrongs = isShowStrongs;
}
public boolean isShowMorphology() {
return isShowMorphology;
}
public void setShowMorphology(boolean isShowMorphology) {
this.isShowMorphology = isShowMorphology;
}
public String getExtraStylesheet() {
return extraStylesheet;
}
public void setExtraStylesheet(String extraStylesheet) {
this.extraStylesheet = extraStylesheet;
}
public String getExtraFooter() {
return extraFooter;
}
public void setExtraFooter(String extraFooter) {
this.extraFooter = extraFooter;
}
public Verse getBasisRef() {
return basisRef;
}
public void setBasisRef(Key basisRef) {
// KeyUtil always returns a Verse even if it is only Gen 1:1
this.basisRef = KeyUtil.getVerse(basisRef);
}
public boolean isRedLetter() {
return isRedLetter;
}
public void setRedLetter(boolean isRedLetter) {
this.isRedLetter = isRedLetter;
}
public String getFont() {
return font;
}
public void setFont(String font) {
this.font = font;
}
public String getCssClassForCustomFont() {
return cssClassForCustomFont;
}
public void setCssClassForCustomFont(String cssClassForCustomFont) {
this.cssClassForCustomFont = cssClassForCustomFont;
}
public boolean isConvertStrongsRefsToLinks() {
return convertStrongsRefsToLinks;
}
public void setConvertStrongsRefsToLinks(boolean convertStrongsRefsToLinks) {
this.convertStrongsRefsToLinks = convertStrongsRefsToLinks;
}
public List<Verse> getVersesWithNotes() {
return versesWithNotes;
}
public void setVersesWithNotes(List<Verse> versesWithNotes) {
this.versesWithNotes = versesWithNotes;
}
public List<Verse> getVersesWithBookmarks() {
return versesWithBookmarks;
}
public void setVersesWithBookmarks(List<Verse> versesWithBookmarks) {
this.versesWithBookmarks = versesWithBookmarks;
}
public URI getModuleBasePath() {
return moduleBasePath;
}
public void setModuleBasePath(URI moduleBasePath) {
this.moduleBasePath = moduleBasePath;
}
public Versification getDocumentVersification() {
if (documentVersification != null) {
return documentVersification;
} else {
return Versifications.instance().getVersification(Versifications.DEFAULT_V11N);
}
}
public void setDocumentVersification(Versification documentVersification) {
this.documentVersification = documentVersification;
}
}

View File

@ -1,325 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import org.bspeice.minimalbible.service.format.OsisSaxHandler;
import org.bspeice.minimalbible.service.format.osistohtml.preprocessor.HebrewCharacterPreprocessor;
import org.bspeice.minimalbible.service.format.osistohtml.preprocessor.TextPreprocessor;
import org.bspeice.minimalbible.service.format.osistohtml.strongs.StrongsHandler;
import org.bspeice.minimalbible.service.format.osistohtml.strongs.StrongsLinkCreator;
import org.bspeice.minimalbible.service.format.osistohtml.tei.OrthHandler;
import org.bspeice.minimalbible.service.format.osistohtml.tei.PronHandler;
import org.bspeice.minimalbible.service.format.osistohtml.tei.RefHandler;
import org.bspeice.minimalbible.service.format.osistohtml.tei.TEIUtil;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import static org.bspeice.minimalbible.service.format.Constants.HTML;
/**
* Convert OSIS tags into html tags
* <p/>
* Example OSIS tags from KJV Ps 119 v1 showing title, w, note <title
* canonical="true" subType="x-preverse" type="section"> <foreign
* n="?">ALEPH.</foreign> </title> <w lemma="strong:H0835">Blessed</w>
* <transChange type="added">are</transChange> <w lemma="strong:H08549">the
* undefiled</w> ... <w lemma="strong:H01980" morph="strongMorph:TH8802">who
* walk</w> ... <w lemma="strong:H03068">of the
* <seg><divineName>Lord</divineName></seg></w>. <note type="study">undefiled:
* or, perfect, or, sincere</note>
* <p/>
* Example of notes cross references from ESV In the <note n="a"
* osisID="Gen.1.1!crossReference.a" osisRef="Gen.1.1"
* type="crossReference"><reference osisRef="Job.38.4-Job.38.7">Job
* 38:4-7</reference>; <reference osisRef="Ps.33.6">Ps. 33:6</reference>;
* <reference osisRef="Ps.136.5">136:5</reference>; <reference
* osisRef="Isa.42.5">Isa. 42:5</reference>; <reference
* osisRef="Isa.45.18">45:18</reference>; <reference
* osisRef="John.1.1-John.1.3">John 1:1-3</reference>; <reference
* osisRef="Acts.14.15">Acts 14:15</reference>; <reference
* osisRef="Acts.17.24">17:24</reference>; <reference
* osisRef="Col.1.16-Col.1.17">Col. 1:16, 17</reference>; <reference
* osisRef="Heb.1.10">Heb. 1:10</reference>; <reference
* osisRef="Heb.11.3">11:3</reference>; <reference osisRef="Rev.4.11">Rev.
* 4:11</reference></note>beginning
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class OsisToHtmlSaxHandler extends OsisSaxHandler {
private static final String HEBREW_LANGUAGE_CODE = "he";
// properties
private OsisToHtmlParameters parameters;
// tag handlers for the different OSIS tags
private VerseHandler verseHandler;
private MyNoteMarker myNoteMarker;
private BookmarkMarker bookmarkMarker;
private NoteHandler noteHandler;
private ReferenceHandler referenceHandler;
private RefHandler refHandler;
private TitleHandler titleHandler;
private QHandler qHandler;
private LGHandler lgHandler;
private LHandler lHandler;
private HiHandler hiHandler;
private OrthHandler orthHandler;
private PronHandler pronHandler;
private StrongsHandler strongsHandler;
private FigureHandler figureHandler;
// processor for the tag content
private TextPreprocessor textPreprocessor;
// internal logic
private VerseInfo verseInfo = new VerseInfo();
private boolean isAnyTextWritten = false;
public OsisToHtmlSaxHandler(OsisToHtmlParameters parameters) {
super();
this.parameters = parameters;
verseHandler = new VerseHandler(parameters, verseInfo, getWriter());
myNoteMarker = new MyNoteMarker(parameters, verseInfo, getWriter());
bookmarkMarker = new BookmarkMarker(parameters, verseInfo, getWriter());
noteHandler = new NoteHandler(parameters, verseInfo, getWriter());
referenceHandler = new ReferenceHandler(parameters, noteHandler, getWriter());
refHandler = new RefHandler(parameters, noteHandler, getWriter());
titleHandler = new TitleHandler(parameters, verseInfo, getWriter());
qHandler = new QHandler(parameters, getWriter());
hiHandler = new HiHandler(parameters, getWriter());
orthHandler = new OrthHandler(parameters, getWriter());
pronHandler = new PronHandler(parameters, getWriter());
lgHandler = new LGHandler(parameters, getWriter());
lHandler = new LHandler(parameters, getWriter());
strongsHandler = new StrongsHandler(parameters, getWriter());
figureHandler = new FigureHandler(parameters, getWriter());
//TODO at the moment we can only have a single TextPreprocesor, need to chain them and maybe make the writer a TextPreprocessor and put it at the end of the chain
if (HEBREW_LANGUAGE_CODE.equals(parameters.getLanguageCode())) {
textPreprocessor = new HebrewCharacterPreprocessor();
} else if (parameters.isConvertStrongsRefsToLinks()) {
textPreprocessor = new StrongsLinkCreator();
}
}
@Override
public void startDocument() {
// force rtl for rtl languages - rtl support on Android is poor but
// forcing it seems to help occasionally
write("<p>");
if (!parameters.isLeftToRight()) {
write("<span dir='rtl'>");
}
}
/*
* Called when the Parser Completes parsing the Current XML File.
*/
@Override
public void endDocument() {
// close last verse
if (parameters.isVersePerline()) {
//close last verse
if (verseInfo.currentVerseNo > 1) {
write("</div>");
}
}
// add optional footer e.g. Strongs show all occurrences link
if (StringUtils.isNotEmpty(parameters.getExtraFooter())) {
write(parameters.getExtraFooter());
}
if (!parameters.isLeftToRight()) {
write("</span>");
}
write("</p>");
}
/*
* Called when the starting of the Element is reached. For Example if we
* have Tag called <Title> ... </Title>, then this method is called when
* <Title> tag is Encountered while parsing the Current XML File. The
* AttributeList Parameter has the list of all Attributes declared for the
* Current Element in the XML File.
*/
@Override
public void startElement(String namespaceURI,
String sName, // simple name
String qName, // qualified name
Attributes attrs) {
String name = getName(sName, qName); // element name
debug(name, attrs, true);
if (name.equals(OSISUtil.OSIS_ELEMENT_VERSE)) {
verseHandler.startAndUpdateVerse(attrs);
bookmarkMarker.start();
myNoteMarker.start();
// record that we are into a new verse
verseInfo.isTextSinceVerse = false;
} else if (name.equals(OSISUtil.OSIS_ELEMENT_TITLE)) {
titleHandler.start(attrs);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_NOTE)) {
noteHandler.startNote(attrs);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_REFERENCE)) {
referenceHandler.start(attrs);
} else if (name.equals(TEIUtil.TEI_ELEMENT_REF)) {
refHandler.start(attrs);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_LB)) {
if (isAnyTextWritten) {
write(HTML.BR);
}
} else if (name.equals(OSISUtil.OSIS_ELEMENT_LG)) {
lgHandler.start(attrs);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_L)) {
lHandler.startL(attrs);
} else if (name.equals("div")) {
String type = attrs.getValue("type");
if ("paragraph".equals(type)) {
// ignore sID start paragraph sID because it often comes after the verse no and causes a gap between verse no verse text
String eID = attrs.getValue("eID");
if (eID != null && isAnyTextWritten) {
write("<p />");
}
}
} else if (name.equals(OSISUtil.OSIS_ELEMENT_P)) {
write("<p>");
} else if (name.equals(OSISUtil.OSIS_ELEMENT_Q)) {
qHandler.start(attrs);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_HI)) {
hiHandler.start(attrs);
} else if (name.equals(TEIUtil.TEI_ELEMENT_ORTH)) {
orthHandler.start(attrs);
} else if (name.equals(TEIUtil.TEI_ELEMENT_PRON)) {
pronHandler.start(attrs);
} else if (name.equals("milestone")) {
String type = attrs.getValue(OSISUtil.OSIS_ATTR_TYPE);
if (StringUtils.isNotEmpty(type)) {
if (type.equals("line") || type.equals("x-p")) {
if (isAnyTextWritten) {
//e.g. NETtext Mt 4:14; KJV Gen 1:6
writeOptionallyBeforeVerse(HTML.BR);
}
}
}
} else if (name.equals("transChange")) {
write("<span class='transChange'>");
} else if (name.equals(OSISUtil.OSIS_ELEMENT_W)) {
strongsHandler.start(attrs);
} else if (name.equals(OSISUtil.OSIS_ELEMENT_FIGURE)) {
figureHandler.start(attrs);
} else {
// TODO: Cleanup
Log.i("OsisToHtmlSaxHandler", "Verse " + verseInfo.currentVerseNo + " unsupported OSIS tag:" + name);
}
}
/*
* Called when the Ending of the current Element is reached. For example in
* the above explanation, this method is called when </Title> tag is reached
*/
@Override
public void endElement(String namespaceURI, String sName, // simple name
String qName // qualified name
) {
String name = getName(sName, qName);
debug(name, null, false);
if (name.equals(OSISUtil.OSIS_ELEMENT_TITLE)) {
titleHandler.end();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_VERSE)) {
myNoteMarker.end();
bookmarkMarker.end();
verseHandler.end();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_NOTE)) {
noteHandler.endNote();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_REFERENCE)) {
referenceHandler.end();
} else if (name.equals(TEIUtil.TEI_ELEMENT_REF)) {
refHandler.end();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_LG)) {
lgHandler.end();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_L)) {
lHandler.endL();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_P)) {
write("</p>");
} else if (name.equals(OSISUtil.OSIS_ELEMENT_Q)) {
// end quotation, but <q /> tag is a marker and contains no content
// so <q /> will appear at beginning and end of speech
qHandler.end();
} else if (name.equals(OSISUtil.OSIS_ELEMENT_HI)) {
hiHandler.end();
} else if (name.equals(TEIUtil.TEI_ELEMENT_ORTH)) {
orthHandler.end();
} else if (name.equals(TEIUtil.TEI_ELEMENT_PRON)) {
pronHandler.end();
} else if (name.equals("transChange")) {
write("</span>");
} else if (name.equals(OSISUtil.OSIS_ELEMENT_W)) {
strongsHandler.end();
}
}
/*
* While Parsing the XML file, if extra characters like space or enter
* Character are encountered then this method is called. If you don't want
* to do anything special with these characters, then you can normally leave
* this method blank.
*/
@Override
public void characters(char buf[], int offset, int len) {
String s = new String(buf, offset, len);
// record that we are now beyond the verse, but do it quickly so as not to slow down parsing
verseInfo.isTextSinceVerse = verseInfo.isTextSinceVerse ||
len > 2 ||
StringUtils.isNotBlank(s);
isAnyTextWritten = isAnyTextWritten || verseInfo.isTextSinceVerse;
if (textPreprocessor != null) {
s = textPreprocessor.process(s);
}
write(s);
}
/**
* allow line breaks and titles to be moved before verse number
*/
protected void writeOptionallyBeforeVerse(String s) {
boolean writeBeforeVerse = !verseInfo.isTextSinceVerse;
if (writeBeforeVerse) {
getWriter().beginInsertAt(verseInfo.positionToInsertBeforeVerse);
}
getWriter().write(s);
if (writeBeforeVerse) {
getWriter().finishInserting();
}
}
/*
* In the XML File if the parser encounters a Processing Instruction which
* is declared like this <?ProgramName:BooksLib
* QUERY="author, isbn, price"?> Then this method is called where Target
* parameter will have "ProgramName:BooksLib" and data parameter will have
* QUERY="author, isbn, price". You can invoke a External Program from this
* Method if required.
*/
public void processingInstruction(String target, String data) {
// noop
}
public String getDirection() {
return parameters.isLeftToRight() ? "ltr" : "rtl";
}
class VerseInfo {
int currentVerseNo;
int positionToInsertBeforeVerse;
boolean isTextSinceVerse = false;
}
}

View File

@ -1,86 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import java.util.Stack;
/**
* This can either signify a quote or Red Letter
* Example from ESV
* But he answered them, <q marker="" who="Jesus"><q level="1" marker="<EFBFBD>" sID="40024002.1"/>You see all these
* Example from KJV
* said ... unto them, <q who="Jesus">...See ye
* <p/>
* Apparently quotation marks are not supposed to appear in the KJV (https://sites.google.com/site/kjvtoday/home/Features-of-the-KJV/quotation-marks)
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class QHandler {
private static final String MARKER = "marker";
private static final String HTML_QUOTE_ENTITY = "&quot;";
private HtmlTextWriter writer;
private OsisToHtmlParameters parameters;
;
// quotes can be embedded so maintain a stack of info about each quote to be used when closing quote
private Stack<QuoteInfo> stack = new Stack<QuoteInfo>();
public QHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
this.parameters = parameters;
this.writer = writer;
}
public String getTagName() {
return "q";
}
public void start(Attributes attrs) {
QuoteInfo quoteInfo = new QuoteInfo();
String who = attrs.getValue(OSISUtil.ATTRIBUTE_Q_WHO);
boolean isWho = who != null;
quoteInfo.isMilestone = TagHandlerHelper.isAttr(OSISUtil.OSIS_ATTR_SID, attrs) || TagHandlerHelper.isAttr(OSISUtil.OSIS_ATTR_EID, attrs);
// Jesus -> no default quote
quoteInfo.marker = TagHandlerHelper.getAttribute(MARKER, attrs, isWho ? "" : HTML_QUOTE_ENTITY);
quoteInfo.isRedLetter = parameters.isRedLetter() && "Jesus".equals(who);
// apply the above logic
writer.write(quoteInfo.marker);
if (quoteInfo.isRedLetter) {
writer.write("<span class='redLetter'>");
}
// and save the info for the closing tag
stack.push(quoteInfo);
}
public void end() {
QuoteInfo quoteInfo = stack.pop();
// Jesus words
if (quoteInfo.isRedLetter) {
writer.write("</span>");
}
// milestone opening and closing tags are doubled up so ensure not double quotes
if (!quoteInfo.isMilestone) {
writer.write(quoteInfo.marker);
}
}
enum QType {quote, redLetter}
private static class QuoteInfo {
private boolean isMilestone;
private boolean isRedLetter;
private String marker = HTML_QUOTE_ENTITY;
}
}

View File

@ -1,157 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import org.crosswire.jsword.book.OSISUtil;
import org.crosswire.jsword.passage.Key;
import org.crosswire.jsword.passage.Passage;
import org.crosswire.jsword.passage.PassageKeyFactory;
import org.crosswire.jsword.passage.RestrictionType;
import org.crosswire.jsword.passage.VerseRange;
import org.xml.sax.Attributes;
import java.util.Iterator;
import static org.bspeice.minimalbible.service.format.Constants.BIBLE_PROTOCOL;
/**
* Convert OSIS tags into html tags
* <p/>
* Example OSIS tags from KJV Ps 119 v1 showing title, w, note
* <title canonical="true" subType="x-preverse" type="section">
* <foreign n="?">ALEPH.</foreign>
* </title>
* <w lemma="strong:H0835">Blessed</w> <transChange type="added">are</transChange> <w lemma="strong:H08549">the undefiled</w>
* ... <w lemma="strong:H01980" morph="strongMorph:TH8802">who walk</w>
* ... <w lemma="strong:H03068">of the <seg><divineName>Lord</divineName></seg></w>.
* <note type="study">undefiled: or, perfect, or, sincere</note>
* <p/>
* Example of notes cross references from ESV
* In the <note n="a" osisID="Gen.1.1!crossReference.a" osisRef="Gen.1.1" type="crossReference"><reference osisRef="Job.38.4-Job.38.7">Job 38:4-7</reference>; <reference osisRef="Ps.33.6">Ps. 33:6</reference>; <reference osisRef="Ps.136.5">136:5</reference>; <reference osisRef="Isa.42.5">Isa. 42:5</reference>; <reference osisRef="Isa.45.18">45:18</reference>; <reference osisRef="John.1.1-John.1.3">John 1:1-3</reference>; <reference osisRef="Acts.14.15">Acts 14:15</reference>; <reference osisRef="Acts.17.24">17:24</reference>; <reference osisRef="Col.1.16-Col.1.17">Col. 1:16, 17</reference>; <reference osisRef="Heb.1.10">Heb. 1:10</reference>; <reference osisRef="Heb.11.3">11:3</reference>; <reference osisRef="Rev.4.11">Rev. 4:11</reference></note>beginning
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class ReferenceHandler {
private final String TAG = "ReferenceHandler";
private OsisToHtmlParameters parameters;
private String currentRefOsisRef;
private NoteHandler noteHandler;
private HtmlTextWriter writer;
public ReferenceHandler(OsisToHtmlParameters osisToHtmlParameters, NoteHandler noteHandler, HtmlTextWriter theWriter) {
this.parameters = osisToHtmlParameters;
this.noteHandler = noteHandler;
this.writer = theWriter;
}
public void start(Attributes attrs) {
// store the osisRef attribute for use with the note
String target = attrs.getValue(OSISUtil.OSIS_ATTR_REF);
start(target);
}
protected void start(String target) {
// don't need to do anything until closing reference tag except..
// delete separators like ';' that sometimes occur between reference tags
writer.clearTempStore();
writer.writeToTempStore();
// store the osisRef attribute for use with the note
this.currentRefOsisRef = target;
}
public void end() {
writer.finishWritingToTempStore();
if (noteHandler.isInNote() || parameters.isAutoWrapUnwrappedRefsInNote()) {
noteHandler.addNoteForReference(writer.getTempStoreString(), currentRefOsisRef);
} else {
String refText = writer.getTempStoreString();
writer.write(getReferenceTag(currentRefOsisRef, refText));
}
// and clear the buffer
writer.clearTempStore();
currentRefOsisRef = null;
}
/**
* create a link tag from an OSISref and the content of the tag
*/
private String getReferenceTag(String reference, String content) {
Log.d(TAG, "Ref:" + reference + " Content:" + content);
StringBuilder result = new StringBuilder();
try {
//JSword does not know the basis (default book) so prepend it if it looks like JSword failed to work it out
//We only need to worry about the first ref because JSword uses the first ref as the basis for the subsequent refs
// if content starts with a number and is not followed directly by an alpha char e.g. 1Sa
if (reference == null && content != null && content.length() > 0 && StringUtils.isNumeric(content.subSequence(0, 1)) &&
(content.length() < 2 || !StringUtils.isAlphaSpace(content.subSequence(1, 2)))) {
// maybe should use VerseRangeFactory.fromstring(orig, basis)
// this check for a colon to see if the first ref is verse:chap is not perfect but it will do until JSword adds a fix
int firstColonPos = content.indexOf(":");
boolean isVerseAndChapter = firstColonPos > 0 && firstColonPos < 4;
if (isVerseAndChapter) {
reference = parameters.getBasisRef().getBook().getOSIS() + " " + content;
} else {
reference = parameters.getBasisRef().getBook().getOSIS() + " " + parameters.getBasisRef().getChapter() + ":" + content;
}
Log.d(TAG, "Patched reference:" + reference);
} else if (reference == null) {
reference = content;
}
// convert urns of type book:key to sword://book/key to simplify urn parsing (1 fewer case to check for).
// Avoid urls of type 'matt 3:14' by excludng urns with a space
if (reference.contains(":") && !reference.contains(" ") && !reference.startsWith("sword://")) {
reference = "sword://" + reference.replace(":", "/");
}
boolean isFullSwordUrn = reference.contains("/") && reference.contains(":");
if (isFullSwordUrn) {
// e.g. sword://StrongsRealGreek/01909
// don't play with the reference - just assume it is correct
result.append("<a href='").append(reference).append("'>");
result.append(content);
result.append("</a>");
} else {
Passage ref = (Passage) PassageKeyFactory.instance().getKey(parameters.getDocumentVersification(), reference);
boolean isSingleVerse = ref.countVerses() == 1;
boolean isSimpleContent = content.length() < 3 && content.length() > 0;
Iterator<VerseRange> it = ref.rangeIterator(RestrictionType.CHAPTER);
if (isSingleVerse && isSimpleContent) {
// simple verse no e.g. 1 or 2 preceding the actual verse in TSK
result.append("<a href='").append(BIBLE_PROTOCOL).append(":").append(it.next().getOsisRef()).append("'>");
result.append(content);
result.append("</a>");
} else {
// multiple complex references
boolean isFirst = true;
while (it.hasNext()) {
Key key = it.next();
if (!isFirst) {
result.append(" ");
}
result.append("<a href='").append(BIBLE_PROTOCOL).append(":").append(key.iterator().next().getOsisRef()).append("'>");
result.append(key);
result.append("</a>");
isFirst = false;
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error parsing OSIS reference:" + reference);
// just return the content with no html markup
result.append(content);
}
return result.toString();
}
}

View File

@ -1,82 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.Attributes;
/**
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class TagHandlerHelper {
private static final String TAG = "TagHandlerHelper";
/**
* support defaultvalue with attribute fetch
*/
public static String getAttribute(String attributeName, Attributes attrs, String defaultValue) {
String attrValue = attrs.getValue(attributeName);
if (attrValue != null) {
return attrValue;
} else {
return defaultValue;
}
}
/**
* support defaultvalue with attribute fetch
*/
public static int getAttribute(String attributeName, Attributes attrs, int defaultValue) {
int retval = defaultValue;
try {
String attrValue = attrs.getValue(attributeName);
if (attrValue != null) {
retval = Integer.parseInt(attrValue);
}
} catch (Exception e) {
Log.w(TAG, "Non numeric but expected integer for " + attributeName);
}
return retval;
}
/**
* see if an attribute exists and has a value
*
* @param attributeName
* @param attrs
* @return
*/
public static boolean isAttr(String attributeName, Attributes attrs) {
String attrValue = attrs.getValue(attributeName);
return StringUtils.isNotEmpty(attrValue);
}
/**
* return verse from osis id of format book.chap.verse
*
* @param osisID osis Id
* @return verse number
*/
public static int osisIdToVerseNum(String osisID) {
/* You have to use "\\.", the first backslash is interpreted as an escape by the
Java compiler, so you have to use two to get a String that contains one
backslash and a dot, which is what you want the regexp engine to see.*/
if (osisID != null) {
String[] parts = osisID.split("\\.");
if (parts.length > 1) {
String verse = parts[parts.length - 1];
return Integer.valueOf(verse);
}
}
return 0;
}
public static void printAttributes(Attributes attrs) {
for (int i = 0; i < attrs.getLength(); i++) {
Log.d(TAG, attrs.getLocalName(i) + ":" + attrs.getValue(i));
}
}
}

View File

@ -1,79 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.apache.commons.lang3.StringUtils;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlSaxHandler.VerseInfo;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
/**
* This can either signify a quote or Red Letter
* Example
* ESV section heading <title subType="x-preverse" type="section">
* ESV canonical heading<title canonical="true" subType="x-preverse" type="section">To the choirmaster. Of David,
* WEB when formatted with JSword seems to have type="x-gen"
* <p/>
* Apparently quotation marks are not supposed to appear in the KJV (https://sites.google.com/site/kjvtoday/home/Features-of-the-KJV/quotation-marks)
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class TitleHandler {
private static final String PREVERSE = "preverse"; // the full string is 'x-preverse' but we just check for contains for extra tolerance
private HtmlTextWriter writer;
private VerseInfo verseInfo;
private OsisToHtmlParameters parameters;
private boolean isShowTitle;
private boolean isMoveBeforeVerse;
public TitleHandler(OsisToHtmlParameters parameters, VerseInfo verseInfo, HtmlTextWriter writer) {
this.parameters = parameters;
this.verseInfo = verseInfo;
this.writer = writer;
}
public String getTagName() {
return OSISUtil.OSIS_ELEMENT_TITLE;
}
public void start(Attributes attrs) {
//JSword adds the chapter no at the top but hide this because the chapter is in the And Bible header
boolean addedByJSword = attrs.getLength() == 1 && OSISUtil.GENERATED_CONTENT.equals(attrs.getValue(OSISUtil.OSIS_ATTR_TYPE));
// otherwise show if user wants Titles or the title is canonical
isShowTitle = !addedByJSword &&
(parameters.isShowTitles() ||
"true".equalsIgnoreCase(attrs.getValue(OSISUtil.OSIS_ATTR_CANONICAL)));
if (isShowTitle) {
// ESV has subType butNETtext has lower case subtype so concatenate both and search with contains()
String subtype = attrs.getValue(OSISUtil.OSIS_ATTR_SUBTYPE) + attrs.getValue(OSISUtil.OSIS_ATTR_SUBTYPE.toLowerCase());
isMoveBeforeVerse = StringUtils.containsIgnoreCase(subtype, PREVERSE) || (!verseInfo.isTextSinceVerse && verseInfo.currentVerseNo > 0);
if (isMoveBeforeVerse) {
// section Titles normally come before a verse, so overwrite the, already written verse, which is rewritten on writer.finishedInserting
writer.beginInsertAt(verseInfo.positionToInsertBeforeVerse);
}
// get title type from level
String titleClass = "heading" + TagHandlerHelper.getAttribute(OSISUtil.OSIS_ATTR_LEVEL, attrs, "1");
writer.write("<h1 class='" + titleClass + "'>");
} else {
writer.setDontWrite(true);
}
}
public void end() {
if (isShowTitle) {
writer.write("</h1>");
if (isMoveBeforeVerse) {
// move positionToInsertBeforeVerse forward to after this title otherwise any subtitle will be above the title
verseInfo.positionToInsertBeforeVerse = writer.getPosition();
writer.finishInserting();
}
} else {
writer.setDontWrite(false);
}
}
}

View File

@ -1,81 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlSaxHandler.VerseInfo;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import static org.bspeice.minimalbible.service.format.Constants.HTML;
/**
* Write the verse number at the beginning of a verse
* Also handle verse per line
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class VerseHandler {
private OsisToHtmlParameters parameters;
private VerseInfo verseInfo;
private int writerRollbackPosition;
private HtmlTextWriter writer;
public VerseHandler(OsisToHtmlParameters parameters, VerseInfo verseInfo, HtmlTextWriter writer) {
this.parameters = parameters;
this.verseInfo = verseInfo;
this.writer = writer;
}
public String getTagName() {
return OSISUtil.OSIS_ELEMENT_VERSE;
}
public void startAndUpdateVerse(Attributes attrs) {
writerRollbackPosition = writer.getPosition();
if (attrs != null) {
verseInfo.currentVerseNo = TagHandlerHelper.osisIdToVerseNum(attrs.getValue("", OSISUtil.OSIS_ATTR_OSISID));
} else {
verseInfo.currentVerseNo++;
}
if (parameters.isVersePerline()) {
//close preceding verse
if (verseInfo.currentVerseNo > 1) {
writer.write("</div>");
}
// start current verse
writer.write("<div>");
}
writeVerse(verseInfo.currentVerseNo);
}
public void end() {
if (!verseInfo.isTextSinceVerse) {
writer.removeAfter(writerRollbackPosition);
}
}
private void writeVerse(int verseNo) {
verseInfo.positionToInsertBeforeVerse = writer.getPosition();
// The id is used to 'jump to' the verse using javascript so always need the verse tag with an id
// Do not show verse 0
StringBuilder verseHtml = new StringBuilder();
if (parameters.isShowVerseNumbers() && verseNo != 0) {
verseHtml.append(" <span class='verse' id='").append(verseNo).append("'>").append(verseNo).append("</span>").append(HTML.NBSP);
} else {
// we really want an empty span but that is illegal and causes problems such as incorrect verse calculation in Psalms
// so use something that will hopefully interfere as little as possible - a zero-width-space
// also put a space before it to allow a separation from the last word of previous verse or to be ignored if start of line
verseHtml.append(" <span class='verse' id='").append(verseNo).append("'/>&#x200b;</span>");
}
writer.write(verseHtml.toString());
}
}

View File

@ -1,99 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.preprocessor;
import android.os.Build;
import org.apache.commons.lang3.StringUtils;
/**
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class HebrewCharacterPreprocessor implements TextPreprocessor {
// the following characters are not handled well in Android 2.2 & 2.3 and
// need special processing which for all except Sof Pasuq means removal
// puctuation char at the end of hebrew verses that looks like a ':'
private static final String HEBREW_SOF_PASUQ_CHAR = "\u05C3";
// vowels are on the first row and cantillations on the second
private static final char[] HEBREW_VOWELS_AND_CANTILLATIONS = new char[]{
'\u05B0', '\u05B1', '\u05B2', '\u05B3', '\u05B4', '\u05B5',
'\u05B6', '\u05B7', '\u05B8', '\u05B9', '\u05BA', '\u05BB',
'\u05BC', '\u05BD', '\u05BE', '\u05BF', '\u05C1', '\u05C2',
'\u0591', '\u0592', '\u0593', '\u0594', '\u0595', '\u0596',
'\u0597', '\u0598', '\u0599', '\u059A', '\u059B', '\u059C',
'\u059D', '\u059E', '\u05A0', '\u05A1', '\u05A2', '\u05A3',
'\u05A4', '\u05A5', '\u05A6', '\u05A7', '\u05A8', '\u05A9',
'\u05AA', '\u05AB', '\u05AC', '\u05AD', '\u05AE', '\u05AF'};
/**
* StringUtils methods only compare with a single char and hence create lots
* of temporary Strings This method compares with all chars and just creates
* one new string for each original string. This is to minimise memory
* overhead & gc.
*
* @param str
* @param removeChars
* @return
*/
public static String remove(String str, char[] removeChars) {
if (StringUtils.isEmpty(str)
|| !StringUtils.containsAny(str, removeChars)) {
return str;
}
StringBuilder r = new StringBuilder(str.length());
// for all chars in string
for (int i = 0; i < str.length(); i++) {
char strCur = str.charAt(i);
// compare with all chars to be removed
boolean matched = false;
for (int j = 0; j < removeChars.length && !matched; j++) {
if (removeChars[j] == strCur) {
matched = true;
}
}
// if current char does not match any in the list then add it to the
if (!matched) {
r.append(strCur);
}
}
return r.toString();
}
/**
* Some characters are not handled well in Android 2.2 & 2.3 and need
* special processing which for all except Sof Pasuq means removal
*
* @param text
* @return adjusted string
*/
@Override
public String process(String text) {
if (isVowelsBugFixed()) {
return text;
} else {
return doHebrewCharacterAdjustments(text);
}
}
/**
* vowels rtl problem fixed in recent cyanogenmod and 4.0.3
*/
private boolean isVowelsBugFixed() {
return Build.VERSION.SDK_INT >= 15 || //Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
(Build.VERSION.SDK_INT >= 10 && System.getProperty("os.version").contains("cyanogenmod")); // 10 is GINGERBREAD_MR1 (2.3.3)
}
private String doHebrewCharacterAdjustments(String s) {
// remove Hebrew vowels because i) they confuse bidi and ii) they are
// not positioned correctly under/over the appropriate letter
// http://groups.google.com/group/android-contrib/browse_thread/thread/5b6b079f9ec7792a?pli=1
s = remove(s, HEBREW_VOWELS_AND_CANTILLATIONS);
// even without vowel points the : at the end of each verse confuses
// Android's bidi but specifying the char as rtl helps
s = s.replace(HEBREW_SOF_PASUQ_CHAR, "<span dir='rtl'>"
+ HEBREW_SOF_PASUQ_CHAR + "</span> ");
return s;
}
}

View File

@ -1,14 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.preprocessor;
/**
* preprocess text content in the Sword module
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public interface TextPreprocessor {
/* convert module text to that required for display
*/
String process(String text);
}

View File

@ -1,170 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.strongs;
import org.apache.commons.lang3.StringUtils;
import org.bspeice.minimalbible.service.format.osistohtml.HtmlTextWriter;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlParameters;
import org.bspeice.minimalbible.service.format.osistohtml.TagHandlerHelper;
import org.crosswire.jsword.book.OSISUtil;
import org.xml.sax.Attributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.bspeice.minimalbible.service.format.Constants.HTML;
/**
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class StrongsHandler {
List<String> pendingStrongsAndMorphTags;
;
private HtmlTextWriter writer;
private OsisToHtmlParameters parameters;
public StrongsHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
this.parameters = parameters;
this.writer = writer;
}
public String getTagName() {
return "q";
}
public void start(Attributes attrs) {
if ((parameters.isShowStrongs() || parameters.isShowMorphology()) && TagHandlerHelper.isAttr(OSISUtil.ATTRIBUTE_W_LEMMA, attrs)) {
// Strongs & morphology references
// example of strongs refs: <w lemma="strong:H0430">God</w> <w lemma="strong:H0853 strong:H01254" morph="strongMorph:TH8804">created</w>
// better example, because we just use Robinson: <w lemma="strong:G652" morph="robinson:N-NSM" src="2">an apostle</w>
String strongsLemma = attrs.getValue(OSISUtil.ATTRIBUTE_W_LEMMA);
if (strongsLemma.startsWith(OSISUtil.LEMMA_STRONGS)) {
String morphology = attrs.getValue(OSISUtil.ATTRIBUTE_W_MORPH);
pendingStrongsAndMorphTags = getStrongsAndMorphTags(strongsLemma, morphology);
}
}
}
public void end() {
if ((parameters.isShowStrongs() || parameters.isShowMorphology())) {
if (pendingStrongsAndMorphTags != null) {
for (int i = 0; i < pendingStrongsAndMorphTags.size(); i++) {
writer.write(HTML.SPACE); // separator between adjacent tags and words
writer.write(pendingStrongsAndMorphTags.get(i));
}
writer.write(HTML.SPACE); // separator between adjacent tags and words
pendingStrongsAndMorphTags = null;
}
}
}
/**
* Convert a Strongs lemma into a url E.g. lemmas "strong:H0430",
* "strong:H0853 strong:H01254"
*
* @return a single char to use as a note ref
*/
private List<String> getStrongsAndMorphTags(String strongsLemma,
String morphology) {
// there may occasionally be more than on ref so split them into a list
// of single refs
List<String> strongsTags = getStrongsTags(strongsLemma);
List<String> morphTags = getMorphTags(morphology);
List<String> mergedStrongsAndMorphTags = new ArrayList<String>();
// each morph tag should relate to a Strongs tag so they should be same
// length but can't assume that
// merge the tags into the merge list
for (int i = 0; i < Math.max(strongsTags.size(), morphTags.size()); i++) {
StringBuilder merged = new StringBuilder();
if (i < strongsTags.size()) {
merged.append(strongsTags.get(i));
}
if (i < morphTags.size()) {
merged.append(morphTags.get(i));
}
mergedStrongsAndMorphTags.add(merged.toString());
}
// for some reason the generic tags should come last and the order seems
// always reversed in other systems
// the second tag (once reversed) seems to relate to a missing word like
// eth
Collections.reverse(mergedStrongsAndMorphTags);
return mergedStrongsAndMorphTags;
}
private List<String> getStrongsTags(String strongsLemma) {
// there may occasionally be more than on ref so split them into a list
// of single refs
List<String> strongsTags = new ArrayList<String>();
if (parameters.isShowStrongs()) {
String[] refList = strongsLemma.split(" ");
for (String ref : refList) {
// ignore if string doesn't start with "strong;"
if (ref.startsWith(OSISUtil.LEMMA_STRONGS)
&& ref.length() > OSISUtil.LEMMA_STRONGS.length() + 2) {
// reduce ref like "strong:H0430" to "H0430"
ref = ref.substring(OSISUtil.LEMMA_STRONGS.length());
// select Hebrew or Greek protocol
String protocol = StrongsUtil.getStrongsProtocol(ref);
if (protocol != null) {
// remove initial G or H
String strongsNumber = ref.substring(1);
String strTag = StrongsUtil.createStrongsLink(protocol, strongsNumber);
strongsTags.add(strTag);
}
}
}
}
return strongsTags;
}
/**
* example of strongs and morphology, we just use Robinson: <w
* lemma="strong:G652" morph="robinson:N-NSM" src="2">an apostle</w>
*
* @param morphology
* @return
*/
private List<String> getMorphTags(String morphology) {
// there may occasionally be more than on ref so split them into a list
// of single refs
List<String> morphTags = new ArrayList<String>();
if (parameters.isShowMorphology()) {
if (StringUtils.isNotEmpty(morphology)) {
String[] refList = morphology.split(" ");
for (String ref : refList) {
// ignore if string doesn't start with "robinson"
if (ref.startsWith(OSISUtil.MORPH_ROBINSONS)
&& ref.length() > OSISUtil.MORPH_ROBINSONS.length() + 2) {
// reduce ref like "robinson:N-NSM" to "N-NSM" for
// display
String display = ref.substring(OSISUtil.MORPH_ROBINSONS
.length());
StringBuilder tag = new StringBuilder();
tag.append("<a href='").append(ref).append(
"' class='morphology'>").append(display)
.append("</a>");
morphTags.add(tag.toString());
}
}
}
}
return morphTags;
}
enum QType {quote, redLetter}
}

View File

@ -1,43 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.strongs;
import org.bspeice.minimalbible.service.format.osistohtml.preprocessor.TextPreprocessor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Used with StrongsGreek and StrongsHebrew to find text like 'see HEBREW for 0433' and 'see GREEK for 1223' and converts to links
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class StrongsLinkCreator implements TextPreprocessor {
static Pattern patt = Pattern.compile("see (HEBREW|GREEK) for (\\d{1,5})"); //".*see ([HEBREW|GREEK]) for (\\d{1,5}).*");
public String process(String text) {
StringBuffer result = new StringBuffer();
Matcher m = patt.matcher(text);
while (m.find()) {
String lang = m.group(1);
String refNo = m.group(2);
// select Hebrew or Greek protocol
String protocol = StrongsUtil.getStrongsProtocol(lang);
// append the actual link to the Strongs ref
String refLink = StrongsUtil.createStrongsLink(protocol, refNo, m.group(), "");
m.appendReplacement(result, refLink);
}
// append any trailing space after the last match, or if no match then the whole string
m.appendTail(result);
return result.toString();
}
}

View File

@ -1,60 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.strongs;
import org.apache.commons.lang3.StringUtils;
import static org.bspeice.minimalbible.service.format.Constants.GREEK_DEF_PROTOCOL;
import static org.bspeice.minimalbible.service.format.Constants.HEBREW_DEF_PROTOCOL;
/**
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class StrongsUtil {
private static final String DEFAULT_CSS_CLASS = "strongs";
/**
* create an html link for teh passed in strongs number and protocol
*
* @param protocol = G or H
* @param strongsNumber
* @return
*/
public static String createStrongsLink(String protocol, String strongsNumber) {
return createStrongsLink(protocol, strongsNumber, strongsNumber, DEFAULT_CSS_CLASS);
}
public static String createStrongsLink(String protocol, String strongsNumber, String content, String cssClass) {
// pad with leading zeros to 5 characters
String paddedRef = StringUtils.leftPad(strongsNumber, 5, "0");
StringBuilder tag = new StringBuilder();
// create opening tag for Strong's link
tag.append("<a href='");
// calculate uri e.g. H:01234
tag.append(protocol).append(":").append(paddedRef);
// set css class
tag.append("' class='" + cssClass + "'>");
// descriptive string
tag.append(content);
// link closing tag
tag.append("</a>");
String strTag = tag.toString();
return strTag;
}
public static String getStrongsProtocol(String ref) {
if (ref.startsWith("H")) {
return HEBREW_DEF_PROTOCOL;
} else if (ref.startsWith("G")) {
return GREEK_DEF_PROTOCOL;
}
return null;
}
}

View File

@ -1,34 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.tei;
import org.bspeice.minimalbible.service.format.osistohtml.HiHandler;
import org.bspeice.minimalbible.service.format.osistohtml.HtmlTextWriter;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlParameters;
import org.xml.sax.Attributes;
/**
* Handle orth tag very similarly to hi tag
* <orth>?????????</orth>
* <orth rend="bold" type="trans">aneuthetos</orth>
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class OrthHandler extends HiHandler {
private final static String DEFAULT = "bold";
public OrthHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
super(parameters, writer);
}
public String getTagName() {
return "orth";
}
public void start(Attributes attrs) {
String rend = attrs.getValue(TEIUtil.TEI_ATTR_REND);
start(rend, DEFAULT);
}
}

View File

@ -1,34 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.tei;
import org.bspeice.minimalbible.service.format.osistohtml.HiHandler;
import org.bspeice.minimalbible.service.format.osistohtml.HtmlTextWriter;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlParameters;
import org.xml.sax.Attributes;
/**
* Handle orth tag very similarly to hi tag
* <orth>?????????</orth>
* <orth rend="bold" type="trans">aneuthetos</orth>
*
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class PronHandler extends HiHandler {
private final static String DEFAULT = "italic";
public PronHandler(OsisToHtmlParameters parameters, HtmlTextWriter writer) {
super(parameters, writer);
}
public String getTagName() {
return "pron";
}
public void start(Attributes attrs) {
String rend = attrs.getValue(TEIUtil.TEI_ATTR_REND);
start(rend, DEFAULT);
}
}

View File

@ -1,24 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.tei;
import org.bspeice.minimalbible.service.format.osistohtml.HtmlTextWriter;
import org.bspeice.minimalbible.service.format.osistohtml.NoteHandler;
import org.bspeice.minimalbible.service.format.osistohtml.OsisToHtmlParameters;
import org.bspeice.minimalbible.service.format.osistohtml.ReferenceHandler;
import org.xml.sax.Attributes;
/**
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class RefHandler extends ReferenceHandler {
public RefHandler(OsisToHtmlParameters osisToHtmlParameters, NoteHandler noteHandler, HtmlTextWriter theWriter) {
super(osisToHtmlParameters, noteHandler, theWriter);
}
public void start(Attributes attrs) {
String target = attrs.getValue(TEIUtil.TEI_ATTR_TARGET);
start(target);
}
}

View File

@ -1,18 +0,0 @@
package org.bspeice.minimalbible.service.format.osistohtml.tei;
/**
* @author Martin Denham [mjdenham at gmail dot com]
* @see gnu.lgpl.License for license details.<br>
* The copyright to this program is held by it's author.
*/
public class TEIUtil {
// E.g. <ref target="StrongsHebrew:00411">H411</ref> taken from StrongsHebrew:00428
public static final String TEI_ELEMENT_REF = "ref";
public static final String TEI_ATTR_TARGET = "target";
public static final String TEI_ELEMENT_ORTH = "orth";
public static final String TEI_ELEMENT_PRON = "pron";
// the way tag contents are rendered e.g. 'bold'. 'italic'
public static final String TEI_ATTR_REND = "rend";
}

View File

@ -11,21 +11,17 @@ import org.crosswire.jsword.versification.getBooks
import org.crosswire.jsword.versification.BibleBook import org.crosswire.jsword.versification.BibleBook
import org.bspeice.minimalbible.activity.viewer.BookAdapter.ChapterInfo import org.bspeice.minimalbible.activity.viewer.BookAdapter.ChapterInfo
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import org.bspeice.minimalbible.service.lookup.VerseLookup
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import android.graphics.Typeface
import android.text.style.SuperscriptSpan
import android.text.style.RelativeSizeSpan
import android.util.TypedValue import android.util.TypedValue
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
import android.util.Log
/** /**
* Adapter used for displaying a book * Adapter used for displaying a book
* Displays one chapter at a time, * Displays one chapter at a time,
* as each TextView widget is it's own line break * as each TextView widget is it's own line break
*/ */
class BookAdapter(val b: Book, val lookup: VerseLookup, class BookAdapter(val b: Book, val prefs: BibleViewerPreferences)
val prefs: BibleViewerPreferences)
: RecyclerView.Adapter<PassageView>() { : RecyclerView.Adapter<PassageView>() {
val versification = b.getVersification() val versification = b.getVersification()
@ -81,7 +77,7 @@ class BookAdapter(val b: Book, val lookup: VerseLookup,
// TODO: Prefs object for handling this? // TODO: Prefs object for handling this?
emptyView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) emptyView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
val passage = PassageView(emptyView, b, lookup) val passage = PassageView(emptyView, b)
return passage return passage
} }
@ -114,85 +110,20 @@ class BookAdapter(val b: Book, val lookup: VerseLookup,
} }
} }
class PassageView(val v: TextView, val b: Book, val lookup: VerseLookup) class PassageView(val v: TextView, val b: Book)
: RecyclerView.ViewHolder(v) { : RecyclerView.ViewHolder(v) {
// Span to be applied to an individual verse - doesn't know about the sizes fun buildOrdinal(verse: Int, info: ChapterInfo) =
// of other verses so that's why start and end are relative b.getVersification().decodeOrdinal(verse + info.vOffset)
/**
* A holder object that knows how apply itself to a SpannableStringBuilder fun getAllVerses(verses: Progression<Int>, info: ChapterInfo): SpannableStringBuilder {
* Since we don't know ahead of time where this verse will end up relative to the val builder = SpannableStringBuilder()
* entire TextView (since there is one chapter per TextView) we use a start and end verses.forEach { OsisParser(builder).appendVerse(b, buildOrdinal(it, info)) }
* relative to the verse text itself. That is, rStart of 0 indicates verse text start,
* and rEnd of (text.length - 1) indicates the end of verse text
* @param span The span object we should apply
* @param rStart When the span should begin, relative to the verse
* @param rEnd When the span should end, relative to the verse
*/
data class SpanHolder(val span: Any?, val rStart: Int, val rEnd: Int) {
// TODO: Is there a more case-class like way of doing this?
class object {
val EMPTY = SpanHolder(null, 0, 0)
}
/**
* Apply this span object to the specified builder
* Tries to be as close to immutable as possible. The offset is used to calculate
* the absolute position of when this span should start and end, since
* rStart and rEnd are relative to the verse text, and know nothing about the
* rest of the text in the TextView
*/
fun apply(builder: SpannableStringBuilder, offset: Int): SpannableStringBuilder {
if (span != null)
builder.setSpan(span, rStart + offset, rEnd + offset, 0)
return builder return builder
} }
}
// TODO: getRawVerse shouldn't know how to decode ints
fun getRawVerse(verse: Int): String =
lookup.getText(b.getVersification().decodeOrdinal(verse))
// TODO: This code is nasty, not sure how to refactor, but it needs doing
fun getProcessedVerse(verseOrdinal: Int, info: ChapterInfo): Pair<String, List<SpanHolder>> {
val rawText = getRawVerse(verseOrdinal)
// To be honest, I have no idea why I need to subtract one. But I do.
val relativeVerse = verseOrdinal - info.vOffset - 1
val processedText = when (relativeVerse) {
0 -> ""
1 -> "${info.chapter} $rawText"
else -> "$relativeVerse$rawText"
}
val spans: List<SpanHolder> = listOf(
when (relativeVerse) {
0 -> SpanHolder.EMPTY
1 -> SpanHolder(StyleSpan(Typeface.BOLD), 0, info.chapter.toString().length)
else -> SpanHolder(SuperscriptSpan(), 0, relativeVerse.toString().length)
}
)
val secondSpan =
if (relativeVerse > 1)
// TODO: No magic numbers!
spans plus SpanHolder(RelativeSizeSpan(0.6f), 0, relativeVerse.toString().length)
else
spans
return Pair(processedText, secondSpan)
}
fun getAllVerses(verses: Progression<Int>, info: ChapterInfo) =
verses.map { getProcessedVerse(it + info.vOffset, info) }
// For each verse, get the text
.fold(SpannableStringBuilder(), { initialBuilder, versePair ->
val offset = initialBuilder.length()
val builderWithText = initialBuilder append versePair.first
// And apply all spans
versePair.second.fold(builderWithText, { postBuilder, span ->
span.apply(postBuilder, offset)
})
})
fun bind(info: ChapterInfo) { fun bind(info: ChapterInfo) {
Log.d("PassageView", "Binding chapter ${info.chapter}")
v setText getAllVerses(info.vStart..info.vEnd, info) v setText getAllVerses(info.vStart..info.vEnd, info)
} }
} }

View File

@ -8,21 +8,26 @@ import org.crosswire.jsword.book.OSISUtil
import org.crosswire.jsword.book.BookData import org.crosswire.jsword.book.BookData
import org.crosswire.jsword.book.Book import org.crosswire.jsword.book.Book
import kotlin.properties.Delegates import kotlin.properties.Delegates
import org.bspeice.minimalbible.service.format.osisparser.handler.TagHandler
import org.bspeice.minimalbible.service.format.osisparser.handler.VerseHandler
import org.bspeice.minimalbible.service.format.osisparser.handler.UnknownHandler
import android.text.SpannableStringBuilder
import org.bspeice.minimalbible.service.format.osisparser.handler.DivineHandler
/** /**
* Parse out the OSIS XML into whatever we want! * Parse out the OSIS XML into whatever we want!
* TODO: Speed up parsing. This is the single most expensive repeated operation * TODO: Speed up parsing. This is the single most expensive repeated operation
*/ */
class OsisParser() : DefaultHandler() { class OsisParser(val builder: SpannableStringBuilder) : DefaultHandler() {
// Don't pass a verse as part of the constructor, but still guarantee // Don't pass a verse as part of the constructor, but still guarantee
// that it will exist // that it will exist
var verseContent: VerseContent by Delegates.notNull() var verseContent: VerseContent by Delegates.notNull()
// TODO: Implement a stack to keep min API 8 // TODO: Implement a stack to keep min API 8
val doWrite = ArrayDeque<Boolean>() val handlerStack = ArrayDeque<TagHandler>()
fun getVerse(b: Book, v: Verse): VerseContent { fun appendVerse(b: Book, v: Verse): VerseContent {
verseContent = VerseContent(v) verseContent = VerseContent(v)
BookData(b, v).getSAXEventProvider() provideSAXEvents this BookData(b, v).getSAXEventProvider() provideSAXEvents this
return verseContent return verseContent
@ -31,18 +36,17 @@ class OsisParser() : DefaultHandler() {
override fun startElement(uri: String, localName: String, override fun startElement(uri: String, localName: String,
qName: String, attributes: Attributes) { qName: String, attributes: Attributes) {
when (localName) { when (localName) {
OSISUtil.OSIS_ELEMENT_VERSE -> doWrite.push(true) OSISUtil.OSIS_ELEMENT_VERSE -> handlerStack push VerseHandler()
"divineName" -> doWrite.push(true) "divineName" -> handlerStack push DivineHandler()
else -> doWrite.push(false) else -> handlerStack push UnknownHandler(localName)
} }
} }
override fun endElement(uri: String, localName: String, qName: String) { override fun endElement(uri: String, localName: String, qName: String) {
doWrite.pop() handlerStack.pop()
} }
override fun characters(ch: CharArray, start: Int, length: Int) { override fun characters(ch: CharArray, start: Int, length: Int) {
if (doWrite.peek()) handlerStack.peek().render(builder, verseContent, String(ch))
verseContent = verseContent appendContent String(ch)
} }
} }

View File

@ -7,7 +7,7 @@ import com.google.gson.Gson
import org.crosswire.jsword.passage.Verse import org.crosswire.jsword.passage.Verse
import java.util.ArrayList import java.util.ArrayList
//TODO: JSON Streaming parsing? http://instagram-engineering.tumblr.com/post/97147584853/json-parsing // TODO: Refactor to a VerseInfo class, not the actual content to support "streaming" parsing
data class VerseContent(val v: Verse, data class VerseContent(val v: Verse,
val id: Int = v.getOrdinal(), val id: Int = v.getOrdinal(),
val bookName: String = v.getName(), val bookName: String = v.getName(),

View File

@ -0,0 +1,19 @@
package org.bspeice.minimalbible.service.format.osisparser.handler
import android.text.SpannableStringBuilder
import org.bspeice.minimalbible.service.format.osisparser.VerseContent
import android.text.style.RelativeSizeSpan
/**
* Created by bspeice on 12/1/14.
*/
class DivineHandler() : TagHandler {
override fun render(builder: SpannableStringBuilder, info: VerseContent, chars: String) {
this buildDivineName chars forEach { it apply builder }
}
fun buildDivineName(chars: String) =
listOf(AppendArgs(chars take 1, null),
AppendArgs(chars drop 1, RelativeSizeSpan(.9f))
)
}

View File

@ -0,0 +1,27 @@
package org.bspeice.minimalbible.service.format.osisparser.handler
import android.text.SpannableStringBuilder
import org.bspeice.minimalbible.service.format.osisparser.VerseContent
import android.text.style.CharacterStyle
/**
* Created by bspeice on 12/1/14.
*/
trait TagHandler {
fun render(builder: SpannableStringBuilder, info: VerseContent, chars: String)
}
data class AppendArgs(val text: String, val span: Any?) {
fun apply(builder: SpannableStringBuilder) {
val offset = builder.length()
builder.append(text)
when (span) {
is List<*> -> span.forEach { builder.setSpan(it, offset, offset + text.length, 0) }
is CharacterStyle -> builder.setSpan(span, offset, offset + text.length, 0)
}
builder.setSpan(span, offset, offset + text.length, 0)
}
}

View File

@ -0,0 +1,14 @@
package org.bspeice.minimalbible.service.format.osisparser.handler
import android.text.SpannableStringBuilder
import android.util.Log
import org.bspeice.minimalbible.service.format.osisparser.VerseContent
/**
* Created by bspeice on 12/1/14.
*/
class UnknownHandler(val tagName: String) : TagHandler {
override fun render(builder: SpannableStringBuilder, info: VerseContent, chars: String) {
Log.d("UnknownHandler", "Unknown tag $tagName received text: $chars")
}
}

View File

@ -0,0 +1,28 @@
package org.bspeice.minimalbible.service.format.osisparser.handler
import android.text.SpannableStringBuilder
import org.bspeice.minimalbible.service.format.osisparser.VerseContent
import android.text.style.StyleSpan
import android.graphics.Typeface
import android.text.style.SuperscriptSpan
import android.text.style.RelativeSizeSpan
/**
* Created by bspeice on 12/1/14.
*/
class VerseHandler() : TagHandler {
var isVerseStart = true
override fun render(builder: SpannableStringBuilder, info: VerseContent, chars: String) {
buildVerseHeader(info.chapter, info.verseNum, isVerseStart) apply builder
builder append chars
isVerseStart = false
}
fun buildVerseHeader(chapter: Int, verseNum: Int, verseStart: Boolean): AppendArgs =
when {
!verseStart -> AppendArgs("", null)
verseNum == 1 -> AppendArgs("$chapter", StyleSpan(Typeface.BOLD))
else -> AppendArgs("${verseNum}", listOf(SuperscriptSpan(), RelativeSizeSpan(.75f)))
}
}

View File

@ -1,86 +0,0 @@
package org.bspeice.minimalbible.service.lookup
import org.crosswire.jsword.book.Book
import android.support.v4.util.LruCache
import rx.functions.Action1
import org.crosswire.jsword.passage.Verse
import rx.subjects.PublishSubject
import rx.schedulers.Schedulers
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
import org.crosswire.jsword.book.getVersification
import org.bspeice.minimalbible.service.format.osisparser.VerseContent
/**
* Do the low-level work of getting a verse's content
* This class is currently impossible to test because I can't mock Verse objects
*/
open class VerseLookup(val b: Book) : Action1<Verse> {
val cache = VerseCache()
/**
* The listener servers to let other objects notify us we should pre-cache verses
*/
val listener: PublishSubject<Verse> = PublishSubject.create();
{
listener.observeOn(Schedulers.io())
.subscribe(this)
}
fun getVerseId(v: Verse) = v.getOrdinal()
fun getText(v: Verse): String =
if (cache contains v)
cache[getVerseId(v)]
else {
val content = doLookup(v).content
notify(v)
content
}
/**
* Perform the ugly work of getting the actual data for a verse
* Note that we build the verse object, JS should be left to determine how
* it is displayed.
*
* @param v The verse to look up
* @return The string content of this verse
*/
fun doLookup(v: Verse): VerseContent = OsisParser().getVerse(b, v)
fun doLookup(ordinal: Int): VerseContent = OsisParser()
.getVerse(b, b.getVersification() decodeOrdinal ordinal)
/**
* Not necessary, but helpful if you let us know ahead of time we should pre-cache a verse.
* For example, if something showed up in search results, it'd be helpful to start
* looking up some of the results.
*
* @param v The verse we should pre-cache
*/
fun notify(v: Verse) = listener onNext v
/**
* Let someone know if the cache contains a verse we want
* Also provides a nice wrapper if the underlying cache isn't working properly.
*
* @param v The verse to check
* @return Whether we can retrieve the verse from our cache
*/
open fun contains(v: Verse) = cache[v.getOrdinal()] != null
// IO Thread operations begin here
/**
* Someone was nice enough to let us know that a verse was recently called,
* we should probably cache its neighbors!
*/
override fun call(t1: Verse?) {
}
}
open class VerseCache : LruCache<Int, String>(1000000) {
fun getId(v: Verse) = v.getOrdinal()
fun contains(v: Verse) = (this get getId(v)) != null
}