[sword-svn] r3068 - in trunk: include src/modules/filters

refdoc at crosswire.org refdoc at crosswire.org
Tue Mar 4 17:05:47 MST 2014


Author: refdoc
Date: 2014-03-04 17:05:47 -0700 (Tue, 04 Mar 2014)
New Revision: 3068

Modified:
   trunk/include/osislatex.h
   trunk/src/modules/filters/osislatex.cpp
Log:
filter for LaTeX now based on xhtml - needs fleshing out


Modified: trunk/include/osislatex.h
===================================================================
--- trunk/include/osislatex.h	2014-03-04 18:32:24 UTC (rev 3067)
+++ trunk/include/osislatex.h	2014-03-05 00:05:47 UTC (rev 3068)
@@ -1,10 +1,10 @@
 /******************************************************************************
  *
- *  osislatex.h -	Implementation of OSISLaTeX
+ *  osislatex.h -	Render filter for LaTeX of an OSIS module
  *
  * $Id$
  *
- * Copyright 2013 CrossWire Bible Society (http://www.crosswire.org)
+ * Copyright 2011-2013 CrossWire Bible Society (http://www.crosswire.org)
  *	CrossWire Bible Society
  *	P. O. Box 2528
  *	Tempe, AZ  85280-2528
@@ -20,23 +20,54 @@
  *
  */
 
-#ifndef OSISLATEX_H
-#define OSISLATEX_H
+#ifndef OSISLaTeX_H
+#define OSISLaTeX_H
 
 #include <swbasicfilter.h>
-#include <utilxml.h>
 
 SWORD_NAMESPACE_START
 
-/** this filter converts OSIS text to LaTeX text
+/** this filter converts OSIS text to classed XHTML
  */
 class SWDLLEXPORT OSISLaTeX : public SWBasicFilter {
-public:
+private:
+	bool morphFirst;
+	bool renderNoteNumbers;
 protected:
+
+	class TagStack;
+	// used by derived classes so we have it in the header
 	virtual BasicFilterUserData *createUserData(const SWModule *module, const SWKey *key);
 	virtual bool handleToken(SWBuf &buf, const char *token, BasicFilterUserData *userData);
+
+
+	class MyUserData : public BasicFilterUserData {
+	public:
+		bool osisQToTick;
+		bool inXRefNote;
+		bool BiblicalText;
+		int suspendLevel;
+		SWBuf wordsOfChristStart;
+		SWBuf wordsOfChristEnd;
+		TagStack *quoteStack;
+		TagStack *hiStack;
+		TagStack *titleStack;
+		TagStack *lineStack;
+		int consecutiveNewlines;
+		SWBuf lastTransChange;
+		SWBuf w;
+		SWBuf fn;
+		SWBuf version;
+
+		MyUserData(const SWModule *module, const SWKey *key);
+		~MyUserData();
+		void outputNewline(SWBuf &buf);
+	};
 public:
 	OSISLaTeX();
+	void setMorphFirst(bool val = true) { morphFirst = val; }
+	void setRenderNoteNumbers(bool val = true) { renderNoteNumbers = val; }
+	virtual const char *getHeader() const;
 };
 
 SWORD_NAMESPACE_END

Modified: trunk/src/modules/filters/osislatex.cpp
===================================================================
--- trunk/src/modules/filters/osislatex.cpp	2014-03-04 18:32:24 UTC (rev 3067)
+++ trunk/src/modules/filters/osislatex.cpp	2014-03-05 00:05:47 UTC (rev 3068)
@@ -1,10 +1,10 @@
 /******************************************************************************
  *
- *  osislatex.cpp -	An SWFilter that provides conversion of OSIS to LaTeX
+ *  osislatex.cpp -	Render filter for LaTeX of an OSIS module
  *
  * $Id$
  *
- * Copyright 2013 CrossWire Bible Society (http://www.crosswire.org)
+ * Copyright 2011-2014 CrossWire Bible Society (http://www.crosswire.org)
  *	CrossWire Bible Society
  *	P. O. Box 2528
  *	Tempe, AZ  85280-2528
@@ -21,28 +21,115 @@
  */
 
 #include <stdlib.h>
+#include <ctype.h>
 #include <osislatex.h>
-#include <ctype.h>
+#include <utilxml.h>
+#include <utilstr.h>
 #include <versekey.h>
+#include <swmodule.h>
+#include <url.h>
 #include <stringmgr.h>
+#include <stack>
 
 SWORD_NAMESPACE_START
 
+const char *OSISLaTeX::getHeader() const {
+	const static char *header = "\
+		.divineName { font-variant: small-caps; }\n\
+		.wordsOfJesus { color: red; }\n\
+		.transChangeSupplied { font-style: italic; }\n\
+		.small, .sub, .sup { font-size: .83em }\n\
+		.sub             { vertical-align: sub }\n\
+		.sup             { vertical-align: super }\n\
+		.indent1         { margin-left: 10px }\n\
+		.indent2         { margin-left: 20px }\n\
+		.indent3         { margin-left: 30px }\n\
+		.indent4         { margin-left: 40px }\n\
+	";
+	return header;
+}
 
+
 namespace {
 
-	class MyUserData : public BasicFilterUserData {
-	public:
-		SWBuf w;
-		XMLTag tag;
-		VerseKey *vk;
-		char testament;
-		SWBuf hiType;
-		MyUserData(const SWModule *module, const SWKey *key) : BasicFilterUserData(module, key) {}
-	};
+// though this might be slightly slower, possibly causing an extra bool check, this is a renderFilter
+// so speed isn't the absolute highest priority, and this is a very minor possible hit
+static inline void outText(const char *t, SWBuf &o, BasicFilterUserData *u) { if (!u->suspendTextPassThru) o += t; else u->lastSuspendSegment += t; }
+static inline void outText(char t, SWBuf &o, BasicFilterUserData *u) { if (!u->suspendTextPassThru) o += t; else u->lastSuspendSegment += t; }
+
+void processLemma(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf) {
+	const char *attrib;
+	const char *val;
+	if ((attrib = tag.getAttribute("lemma"))) {
+		int count = tag.getAttributePartCount("lemma", ' ');
+		int i = (count > 1) ? 0 : -1;		// -1 for whole value cuz it's faster, but does the same thing as 0
+		do {
+			attrib = tag.getAttribute("lemma", i, ' ');
+			if (i < 0) i = 0;	// to handle our -1 condition
+			val = strchr(attrib, ':');
+			val = (val) ? (val + 1) : attrib;
+			SWBuf gh;
+			if(*val == 'G')
+				gh = "Greek";
+			if(*val == 'H')
+				gh = "Hebrew";
+			const char *val2 = val;
+			if ((strchr("GH", *val)) && (isdigit(val[1])))
+				val2++;
+			//if ((!strcmp(val2, "3588")) && (lastText.length() < 1))
+			//	show = false;
+			//else {
+				if (!suspendTextPassThru) {
+					buf.appendFormatted("<small><em class=\"strongs\">&lt;<a href=\"passagestudy.jsp?action=showStrongs&type=%s&value=%s\" class=\"strongs\">%s</a>&gt;</em></small>",
+							(gh.length()) ? gh.c_str() : "", 
+							URL::encode(val2).c_str(),
+							val2);
+				}
+			//}
+			
+		} while (++i < count);
+	}
 }
 
 
+
+void processMorph(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf) {
+	const char * attrib;
+	const char *val;
+	if ((attrib = tag.getAttribute("morph"))) { // && (show)) {
+		SWBuf savelemma = tag.getAttribute("savlm");
+		//if ((strstr(savelemma.c_str(), "3588")) && (lastText.length() < 1))
+		//	show = false;
+		//if (show) {
+			int count = tag.getAttributePartCount("morph", ' ');
+			int i = (count > 1) ? 0 : -1;		// -1 for whole value cuz it's faster, but does the same thing as 0
+			do {
+				attrib = tag.getAttribute("morph", i, ' ');
+				if (i < 0) i = 0;	// to handle our -1 condition
+				val = strchr(attrib, ':');
+				val = (val) ? (val + 1) : attrib;
+				const char *val2 = val;
+				if ((*val == 'T') && (strchr("GH", val[1])) && (isdigit(val[2])))
+					val2+=2;
+				if (!suspendTextPassThru) {
+					buf.appendFormatted("<small><em class=\"morph\">(<a href=\"passagestudy.jsp?action=showMorph&type=%s&value=%s\" class=\"morph\">%s</a>)</em></small>",
+							URL::encode(tag.getAttribute("morph")).c_str(),
+							URL::encode(val).c_str(), 
+							val2);
+				}
+			} while (++i < count);
+		//}
+	}
+}
+
+
+}	// end anonymous namespace
+
+BasicFilterUserData *OSISLaTeX::createUserData(const SWModule *module, const SWKey *key) {
+	return new MyUserData(module, key);
+}
+
+
 OSISLaTeX::OSISLaTeX() {
 	setTokenStart("<");
 	setTokenEnd(">");
@@ -51,209 +138,633 @@
 	setEscapeEnd(";");
 
 	setEscapeStringCaseSensitive(true);
+	setPassThruNumericEscapeString(true);
 
-	addEscapeStringSubstitute("amp", "&");
-	addEscapeStringSubstitute("apos", "'");
-	addEscapeStringSubstitute("lt", "<");
-	addEscapeStringSubstitute("gt", ">");
-	addEscapeStringSubstitute("quot", "\"");
+	addAllowedEscapeString("quot");
+	addAllowedEscapeString("apos");
+	addAllowedEscapeString("amp");
+	addAllowedEscapeString("lt");
+	addAllowedEscapeString("gt");
 
-	   setTokenCaseSensitive(true);
-	   addTokenSubstitute("title", "\n");
-	   addTokenSubstitute("/title", "\n");
-	   addTokenSubstitute("/l", "\n");
-	   addTokenSubstitute("lg", "\n");
-	   addTokenSubstitute("/lg", "\n");
+	setTokenCaseSensitive(true);
+	
+	//	addTokenSubstitute("lg",  "<br />");
+	//	addTokenSubstitute("/lg", "<br />");
+
+	morphFirst = false;
+	renderNoteNumbers = false;
 }
 
-BasicFilterUserData *OSISLaTeX::createUserData(const SWModule *module, const SWKey *key) {
-	MyUserData *u = new MyUserData(module, key);
-	u->vk = SWDYNAMIC_CAST(VerseKey, u->key);
-	u->testament = (u->vk) ? u->vk->getTestament() : 2;	// default to NT
-	return u;
+class OSISLaTeX::TagStack : public std::stack<SWBuf> {
+};
+
+OSISLaTeX::MyUserData::MyUserData(const SWModule *module, const SWKey *key) : BasicFilterUserData(module, key), quoteStack(new TagStack()), hiStack(new TagStack()), titleStack(new TagStack()), lineStack(new TagStack()) {
+	inXRefNote    = false;
+	suspendLevel = 0;
+	wordsOfChristStart = "<span class=\"wordsOfJesus\"> ";
+	wordsOfChristEnd   = "</span> ";
+	if (module) {
+		osisQToTick = ((!module->getConfigEntry("OSISqToTick")) || (strcmp(module->getConfigEntry("OSISqToTick"), "false")));
+		version = module->getName();
+		BiblicalText = (!strcmp(module->getType(), "Biblical Texts"));
+	}
+	else {
+		osisQToTick = true;	// default
+		version = "";
+	}
+	consecutiveNewlines = 0;
 }
 
+OSISLaTeX::MyUserData::~MyUserData() {
+	delete quoteStack;
+	delete hiStack;
+	delete titleStack;
+	delete lineStack;
+}
 
+void OSISLaTeX::MyUserData::outputNewline(SWBuf &buf) {
+	if (++consecutiveNewlines <= 2) {
+		outText("<br />\n", buf, this);
+		supressAdjacentWhitespace = true;
+	}
+}
 bool OSISLaTeX::handleToken(SWBuf &buf, const char *token, BasicFilterUserData *userData) {
-	   // manually process if it wasn't a simple substitution
-	if (!substituteToken(buf, token)) {
-		MyUserData *u = (MyUserData *)userData;
-		if (((*token == 'w') && (token[1] == ' ')) ||
-		    ((*token == '/') && (token[1] == 'w') && (!token[2]))) {
-				 u->tag = token;
-			
-			bool start = false;
-			if (*token == 'w') {
-				if (token[strlen(token)-1] != '/') {
-					u->w = token;
-					return true;
+	MyUserData *u = (MyUserData *)userData;
+	SWBuf scratch;
+	bool sub = (u->suspendTextPassThru) ? substituteToken(scratch, token) : substituteToken(buf, token);
+	if (!sub) {
+  // manually process if it wasn't a simple substitution
+		XMLTag tag(token);
+		
+		// <w> tag
+		if (!strcmp(tag.getName(), "w")) {
+ 
+			// start <w> tag
+			if ((!tag.isEmpty()) && (!tag.isEndTag())) {
+				u->w = token;
+			}
+
+			// end or empty <w> tag
+			else {
+				bool endTag = tag.isEndTag();
+				SWBuf lastText;
+				//bool show = true;	// to handle unplaced article in kjv2003-- temporary till combined
+
+				if (endTag) {
+					tag = u->w.c_str();
+					lastText = u->lastTextNode.c_str();
 				}
-				start = true;
+				else lastText = "stuff";
+
+				const char *attrib;
+				const char *val;
+				if ((attrib = tag.getAttribute("xlit"))) {
+					val = strchr(attrib, ':');
+					val = (val) ? (val + 1) : attrib;
+					outText(" ", buf, u);
+					outText(val, buf, u);
+				}
+				if ((attrib = tag.getAttribute("gloss"))) {
+					// I'm sure this is not the cleanest way to do it, but it gets the job done
+					// for rendering ruby chars properly ^_^
+					buf -= lastText.length();
+					
+					outText("<ruby><rb>", buf, u);
+					outText(lastText, buf, u);
+					val = strchr(attrib, ':');
+					val = (val) ? (val + 1) : attrib;
+					outText("</rb><rp>(</rp><rt>", buf, u);
+					outText(val, buf, u);
+					outText("</rt><rp>)</rp></ruby>", buf, u);
+				}
+				if (!morphFirst) {
+					processLemma(u->suspendTextPassThru, tag, buf);
+					processMorph(u->suspendTextPassThru, tag, buf);
+				}
+				else {
+					processMorph(u->suspendTextPassThru, tag, buf);
+					processLemma(u->suspendTextPassThru, tag, buf);
+				}
+				if ((attrib = tag.getAttribute("POS"))) {
+					val = strchr(attrib, ':');
+					val = (val) ? (val + 1) : attrib;
+					outText(" ", buf, u);
+					outText(val, buf, u);
+				}
+
+				/*if (endTag)
+					buf += "}";*/
 			}
-			u->tag = (start) ? token : u->w.c_str();
-			bool show = true;	// to handle unplaced article in kjv2003-- temporary till combined
+		}
 
-			SWBuf lastText = (start) ? "stuff" : u->lastTextNode.c_str();
+		// <note> tag
+		else if (!strcmp(tag.getName(), "note")) {
+			if (!tag.isEndTag()) {
+				SWBuf type = tag.getAttribute("type");
+				bool strongsMarkup = (type == "x-strongsMarkup" || type == "strongsMarkup");	// the latter is deprecated
+				if (strongsMarkup) {
+					tag.setEmpty(false);	// handle bug in KJV2003 module where some note open tags were <note ... />
+				}
 
-			const char *attrib;
-			const char *val;
-			if ((attrib = u->tag.getAttribute("xlit"))) {
-				val = strchr(attrib, ':');
-				val = (val) ? (val + 1) : attrib;
-				buf.append(" <");
-				buf.append(val);
-				buf.append('>');
+				if (!tag.isEmpty()) {
+
+					if (!strongsMarkup) {	// leave strong's markup notes out, in the future we'll probably have different option filters to turn different note types on or off
+						SWBuf footnoteNumber = tag.getAttribute("swordFootnote");
+						SWBuf noteName = tag.getAttribute("n");
+						VerseKey *vkey = NULL;
+						char ch = ((tag.getAttribute("type") && ((!strcmp(tag.getAttribute("type"), "crossReference")) || (!strcmp(tag.getAttribute("type"), "x-cross-ref")))) ? 'x':'n');
+
+						u->inXRefNote = true; // Why this change? Ben Morgan: Any note can have references in, so we need to set this to true for all notes
+//						u->inXRefNote = (ch == 'x');
+
+						// see if we have a VerseKey * or descendant
+						SWTRY {
+							vkey = SWDYNAMIC_CAST(VerseKey, u->key);
+						}
+						SWCATCH ( ... ) {	}
+						if (vkey) {
+							//printf("URL = %s\n",URL::encode(vkey->getText()).c_str());
+							buf.appendFormatted("<a href=\"passagestudy.jsp?action=showNote&type=%c&value=%s&module=%s&passage=%s\"><small><sup class=\"%c\">*%c%s</sup></small></a>",
+								ch, 
+								URL::encode(footnoteNumber.c_str()).c_str(), 
+								URL::encode(u->version.c_str()).c_str(), 
+								URL::encode(vkey->getText()).c_str(), 
+								ch,
+								ch, 
+								(renderNoteNumbers ? URL::encode(noteName.c_str()).c_str() : ""));
+						}
+						else {
+							buf.appendFormatted("<a href=\"passagestudy.jsp?action=showNote&type=%c&value=%s&module=%s&passage=%s\"><small><sup class=\"%c\">*%c%s</sup></small></a>",
+								ch, 
+								URL::encode(footnoteNumber.c_str()).c_str(), 
+								URL::encode(u->version.c_str()).c_str(), 
+								URL::encode(u->key->getText()).c_str(),  
+								ch,
+								ch, 
+								(renderNoteNumbers ? URL::encode(noteName.c_str()).c_str() : ""));
+						}
+					}
+				}
+				u->suspendTextPassThru = (++u->suspendLevel);
 			}
-			if ((attrib = u->tag.getAttribute("gloss"))) {
-				val = strchr(attrib, ':');
-				val = (val) ? (val + 1) : attrib;
-				buf.append(" <");
-				buf.append(val);
-				buf.append('>');
+			if (tag.isEndTag()) {
+				u->suspendTextPassThru = (--u->suspendLevel);
+				u->inXRefNote = false;
+				u->lastSuspendSegment = ""; // fix/work-around for nasb devineName in note bug
 			}
-			if ((attrib = u->tag.getAttribute("lemma"))) {
-				int count = u->tag.getAttributePartCount("lemma", ' ');
-				int i = (count > 1) ? 0 : -1;		// -1 for whole value cuz it's faster, but does the same thing as 0
-				do {
-					char gh;
-					attrib = u->tag.getAttribute("lemma", i, ' ');
-					if (i < 0) i = 0;	// to handle our -1 condition
-					val = strchr(attrib, ':');
-					val = (val) ? (val + 1) : attrib;
-					if ((strchr("GH", *val)) && (isdigit(val[1]))) {
-						gh = *val;
-						val++;
+		}
+
+		// <p> paragraph and <lg> linegroup tags
+		else if (!strcmp(tag.getName(), "p") || !strcmp(tag.getName(), "lg")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {	// non-empty start tag
+				u->outputNewline(buf);
+			}
+			else if (tag.isEndTag()) {	// end tag
+				u->outputNewline(buf);
+			}
+			else {					// empty paragraph break marker
+				u->outputNewline(buf);
+			}
+		}
+
+		// Milestoned paragraphs, created by osis2mod
+		// <div type="paragraph" sID.../>
+		// <div type="paragraph" eID.../>
+		else if (tag.isEmpty() && !strcmp(tag.getName(), "div") && tag.getAttribute("type") && (!strcmp(tag.getAttribute("type"), "x-p") || !strcmp(tag.getAttribute("type"), "paragraph"))) {
+			// <div type="paragraph"  sID... />
+			if (tag.getAttribute("sID")) {	// non-empty start tag
+				u->outputNewline(buf);
+			}
+			// <div type="paragraph"  eID... />
+			else if (tag.getAttribute("eID")) {
+				u->outputNewline(buf);
+			}
+		}
+
+		// <reference> tag
+		else if (!strcmp(tag.getName(), "reference")) {	
+			if (!u->inXRefNote) {	// only show these if we're not in an xref note				
+				if (!tag.isEndTag()) {
+					SWBuf target;
+					SWBuf work;
+					SWBuf ref;
+					bool is_scripRef = false;
+
+					target = tag.getAttribute("osisRef");
+					const char* the_ref = strchr(target, ':');
+					
+					if(!the_ref) {
+						// No work
+						ref = target;
+						is_scripRef = true;
 					}
 					else {
-						gh = (u->testament>1) ? 'G' : 'H';
+						// Compensate for starting :
+						ref = the_ref + 1;
+
+						int size = target.size() - ref.size() - 1;
+						work.setSize(size);
+						strncpy(work.getRawData(), target, size);
+
+						// For Bible:Gen.3.15 or Bible.vulgate:Gen.3.15
+						if(!strncmp(work, "Bible", 5))
+							is_scripRef = true;
 					}
-					if ((!strcmp(val, "3588")) && (lastText.length() < 1))
-						show = false;
-					else	{
-						buf.append(" <");
-						buf.append(gh);
-						buf.append(val);
-						buf.append(">");
+
+					if(is_scripRef)
+					{
+						buf.appendFormatted("<a href=\"passagestudy.jsp?action=showRef&type=scripRef&value=%s&module=\">",
+							URL::encode(ref.c_str()).c_str()
+//							(work.size()) ? URL::encode(work.c_str()).c_str() : "")
+							);
 					}
-				} while (++i < count);
+					else
+					{
+						// Dictionary link, or something
+						buf.appendFormatted("<a href=\"sword://%s/%s\">",
+							URL::encode(work.c_str()).c_str(),
+							URL::encode(ref.c_str()).c_str()
+							);
+					}
+				}
+				else {
+					outText("</a>", buf, u);
+				}
 			}
-			if ((attrib = u->tag.getAttribute("morph")) && (show)) {
-				int count = u->tag.getAttributePartCount("morph", ' ');
-				int i = (count > 1) ? 0 : -1;		// -1 for whole value cuz it's faster, but does the same thing as 0
-				do {
-					attrib = u->tag.getAttribute("morph", i, ' ');
-					if (i < 0) i = 0;	// to handle our -1 condition
-					val = strchr(attrib, ':');
-					val = (val) ? (val + 1) : attrib;
-					if ((*val == 'T') && (strchr("GH", val[1])) && (isdigit(val[2])))
-						val+=2;
-					buf.append(" (");
-					buf.append(val);
-					buf.append(')');
-				} while (++i < count);
+		}
+
+		// <l> poetry, etc
+		else if (!strcmp(tag.getName(), "l")) {
+			// start line marker
+			if (tag.getAttribute("sID") || (!tag.isEndTag() && !tag.isEmpty())) {
+				// nested lines plus if the line itself has an x-indent type attribute value
+				outText(SWBuf("<span class=\"line indent").appendFormatted("%d\">", u->lineStack->size() + (SWBuf("x-indent") == tag.getAttribute("type")?1:0)).c_str(), buf, u);
+				u->lineStack->push(tag.toString());
 			}
-			if ((attrib = u->tag.getAttribute("POS"))) {
-				val = strchr(attrib, ':');
-				val = (val) ? (val + 1) : attrib;
-				
-				buf.append(" <");
-				buf.append(val);
-				buf.append('>');
+			// end line marker
+			else if (tag.getAttribute("eID") || tag.isEndTag()) {
+				outText("</span>", buf, u);
+				u->outputNewline(buf);
+				if (u->lineStack->size()) u->lineStack->pop();
 			}
+			// <l/> without eID or sID
+			// Note: this is improper osis. This should be <lb/>
+			else if (tag.isEmpty() && !tag.getAttribute("sID")) {
+				u->outputNewline(buf);
+			}
 		}
 
-		// <note> tag
-		else if (!strncmp(token, "note", 4)) {
-				if (!strstr(token, "strongsMarkup")) {	// leave strong's markup notes out, in the future we'll probably have different option filters to turn different note types on or off
-					buf.append(" \\footnote{");
+		// <lb.../>
+		else if (!strcmp(tag.getName(), "lb") && (!tag.getAttribute("type") || strcmp(tag.getAttribute("type"), "x-optional"))) {
+				u->outputNewline(buf);
+		}
+		// <milestone type="line"/>
+		// <milestone type="x-p"/>
+		// <milestone type="cQuote" marker="x"/>
+		else if ((!strcmp(tag.getName(), "milestone")) && (tag.getAttribute("type"))) {
+			if (!strcmp(tag.getAttribute("type"), "line")) {
+				u->outputNewline(buf);
+				if (tag.getAttribute("subType") && !strcmp(tag.getAttribute("subType"), "x-PM")) {
+					u->outputNewline(buf);
 				}
-				else	u->suspendTextPassThru = true;
 			}
-		else if (!strncmp(token, "/note", 5)) {
-			if (!u->suspendTextPassThru)
-				buf.append("} ");
-			else	u->suspendTextPassThru = false;
-		}
+			else if (!strcmp(tag.getAttribute("type"),"x-p"))  {
+				if (tag.getAttribute("marker"))
+					outText(tag.getAttribute("marker"), buf, u);
+				else outText("<!p>", buf, u);
+			}
+			else if (!strcmp(tag.getAttribute("type"), "cQuote")) {
+				const char *tmp = tag.getAttribute("marker");
+				bool hasMark    = tmp;
+				SWBuf mark      = tmp;
+				tmp             = tag.getAttribute("level");
+				int level       = (tmp) ? atoi(tmp) : 1;
 
-		// <p> paragraph tag
-		else if (((*token == 'p') && ((token[1] == ' ') || (!token[1]))) ||
-			((*token == '/') && (token[1] == 'p') && (!token[2]))) {
-				userData->supressAdjacentWhitespace = true;
-				buf.append('\n');
+				// first check to see if we've been given an explicit mark
+				if (hasMark)
+					outText(mark, buf, u);
+				// finally, alternate " and ', if config says we should supply a mark
+				else if (u->osisQToTick)
+					outText((level % 2) ? '\"' : '\'', buf, u);
+			}
 		}
 
-		// Milestoned paragraph, created by osis2mod
-		// <div type="paragraph"  sID... />
-		// <div type="paragraph"  eID... />
-		else if (!strcmp(u->tag.getName(), "div") && u->tag.getAttribute("type") && (!strcmp(u->tag.getAttribute("type"), "x-p") || !strcmp(u->tag.getAttribute("type"), "paragraph")) &&
-			(u->tag.isEmpty() && (u->tag.getAttribute("sID") || u->tag.getAttribute("eID")))) {
-				userData->supressAdjacentWhitespace = true;
-				buf.append('\n');
+		// <title>
+		else if (!strcmp(tag.getName(), "title")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				VerseKey *vkey = SWDYNAMIC_CAST(VerseKey, u->key);
+				if (vkey && !vkey->getVerse()) {
+					if (!vkey->getChapter()) {
+						if (!vkey->getBook()) {
+							if (!vkey->getTestament()) {
+								buf += "<h1 class=\"moduleHeader\">";
+								tag.setAttribute("pushed", "h1");
+							}
+							else {
+								buf += "<h1 class=\"testamentHeader\">";
+								tag.setAttribute("pushed", "h1");
+							}
+						}
+						else {
+							buf += "<h1 class=\"bookHeader\">";
+							tag.setAttribute("pushed", "h1");
+						}
+					}
+					else {
+						buf += "<h2 class=\"chapterHeader\">";
+						tag.setAttribute("pushed", "h2");
+					}
+				}
+				else {
+					buf += "<h3>";
+					tag.setAttribute("pushed", "h3");
+				}
+				u->titleStack->push(tag.toString());
+			}
+			else if (tag.isEndTag()) {
+				if (!u->titleStack->empty()) {
+					XMLTag tag(u->titleStack->top());
+					if (u->titleStack->size()) u->titleStack->pop();
+					SWBuf pushed = tag.getAttribute("pushed");
+					if (pushed.size()) {
+						buf += (SWBuf)"</" + pushed + ">\n\n";
+					}
+					else {
+						buf += "</h3>\n\n";
+					}
+					++u->consecutiveNewlines;
+					u->supressAdjacentWhitespace = true;
+				}
+			}
 		}
+		
+		// <list>
+		else if (!strcmp(tag.getName(), "list")) {
+			if((!tag.isEndTag()) && (!tag.isEmpty())) {
+				outText("<ul>\n", buf, u);
+			}
+			else if (tag.isEndTag()) {
+				outText("</ul>\n", buf, u);
+				++u->consecutiveNewlines;
+				u->supressAdjacentWhitespace = true;
+			}
+		}
 
-                // <lb .../>
-                else if (!strncmp(token, "lb", 2)) {
-			userData->supressAdjacentWhitespace = true;
-			buf.append('\n');
+		// <item>
+		else if (!strcmp(tag.getName(), "item")) {
+			if((!tag.isEndTag()) && (!tag.isEmpty())) {
+				outText("\t<li>", buf, u);
+			}
+			else if (tag.isEndTag()) {
+				outText("</li>\n", buf, u);
+				++u->consecutiveNewlines;
+				u->supressAdjacentWhitespace = true;
+			}
 		}
-		else if (!strncmp(token, "l", 1) && strstr(token, "eID")) {
-			userData->supressAdjacentWhitespace = true;
-			buf.append('\n');
+		// <catchWord> & <rdg> tags (italicize)
+		else if (!strcmp(tag.getName(), "rdg") || !strcmp(tag.getName(), "catchWord")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				outText("<i>", buf, u);
+			}
+			else if (tag.isEndTag()) {
+				outText("</i>", buf, u);
+			}
 		}
-		else if (!strncmp(token, "/divineName", 11)) {
-			// Get the end portion of the string, and upper case it
-			char* end = buf.getRawData();
-			end += buf.size() - u->lastTextNode.size();
-			toupperstr(end);
+
+		// divineName  
+		else if (!strcmp(tag.getName(), "divineName")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				u->suspendTextPassThru = (++u->suspendLevel);
+			}
+			else if (tag.isEndTag()) {
+				SWBuf lastText = u->lastSuspendSegment.c_str();
+				u->suspendTextPassThru = (--u->suspendLevel);
+				if (lastText.size()) {
+					scratch.setFormatted("<span class=\"divineName\">%s</span>", lastText.c_str());
+					outText(scratch.c_str(), buf, u);
+				}               
+			} 
 		}
-		else if (!strncmp(token, "hi", 2)) {
 
-				// handle both OSIS 'type' and TEI 'rend' attributes
+		// <hi> text highlighting
+		else if (!strcmp(tag.getName(), "hi")) {
+			SWBuf type = tag.getAttribute("type");
+
+			// handle tei rend attribute if type doesn't exist
+			if (!type.length()) type = tag.getAttribute("rend");
+
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				if (type == "bold" || type == "b" || type == "x-b") {
+					outText("<b>", buf, u);
+				}
+
 				// there is no officially supported OSIS overline attribute,
 				// thus either TEI overline or OSIS x-overline would be best,
 				// but we have used "ol" in the past, as well.  Once a valid
 				// OSIS overline attribute is made available, these should all
 				// eventually be deprecated and never documented that they are supported.
-				if (strstr(token, "rend=\"ol\"") || strstr(token, "rend=\"x-overline\"") || strstr(token, "rend=\"overline\"")
-				   || strstr(token, "type=\"ol\"") || strstr(token, "type=\"x-overline\"") || strstr(token, "type=\"overline\"")) {
-					u->hiType = "overline";
+				else if (type == "ol"  || type == "overline" || type == "x-overline") {
+					outText("<span style=\"text-decoration:overline\">", buf, u);
 				}
-				else u->hiType = "";
-				u->suspendTextPassThru = true;
+
+				else if (type == "super") {
+					outText("<span class=\"sup\">", buf, u);
+				}
+				else if (type == "sub") {
+					outText("<span class=\"sub\">", buf, u);
+				}
+				else {	// all other types
+					outText("<i>", buf, u);
+				}
+				u->hiStack->push(tag.toString());
 			}
-		else if (!strncmp(token, "/hi", 3)) {
-			if (u->hiType == "overline") {
-				const unsigned char *b = (const unsigned char *)u->lastTextNode.c_str();
-				while (*b) {
-					const unsigned char *o = b;
-					if (getUniCharFromUTF8(&b)) {
-						while (o != b) buf.append(*(o++));
-						buf.append((unsigned char)0xCC);
-						buf.append((unsigned char)0x85);
-					}
+			else if (tag.isEndTag()) {
+				SWBuf type = "";
+				if (!u->hiStack->empty()) {
+					XMLTag tag(u->hiStack->top());
+					if (u->hiStack->size()) u->hiStack->pop();
+					type = tag.getAttribute("type");
+					if (!type.length()) type = tag.getAttribute("rend");
 				}
+				if (type == "bold" || type == "b" || type == "x-b") {
+					outText("</b>", buf, u);
+				}
+				else if (  	   type == "ol"
+						|| type == "super"
+						|| type == "sub") {
+					outText("</span>", buf, u);
+				}
+				else outText("</i>", buf, u);
 			}
-			else {
-				buf.append("*");
-				buf.append(u->lastTextNode);
-				buf.append("*");
+		}
+
+		// <q> quote
+		// Rules for a quote element:
+		// If the tag is empty with an sID or an eID then use whatever it specifies for quoting.
+		//    Note: empty elements without sID or eID are ignored.
+		// If the tag is <q> then use it's specifications and push it onto a stack for </q>
+		// If the tag is </q> then use the pushed <q> for specification
+		// If there is a marker attribute, possibly empty, this overrides osisQToTick.
+		// If osisQToTick, then output the marker, using level to determine the type of mark.
+		else if (!strcmp(tag.getName(), "q")) {
+			SWBuf type      = tag.getAttribute("type");
+			SWBuf who       = tag.getAttribute("who");
+			const char *tmp = tag.getAttribute("level");
+			int level       = (tmp) ? atoi(tmp) : 1;
+			tmp             = tag.getAttribute("marker");
+			bool hasMark    = tmp;
+			SWBuf mark      = tmp;
+
+			// open <q> or <q sID... />
+			if ((!tag.isEmpty() && !tag.isEndTag()) || (tag.isEmpty() && tag.getAttribute("sID"))) {
+				// if <q> then remember it for the </q>
+				if (!tag.isEmpty()) {
+					u->quoteStack->push(tag.toString());
+				}
+
+				// Do this first so quote marks are included as WoC
+				if (who == "Jesus")
+					outText(u->wordsOfChristStart, buf, u);
+
+				// first check to see if we've been given an explicit mark
+				if (hasMark)
+					outText(mark, buf, u);
+				//alternate " and '
+				else if (u->osisQToTick)
+					outText((level % 2) ? '\"' : '\'', buf, u);
 			}
-			u->suspendTextPassThru = false;
+			// close </q> or <q eID... />
+			else if ((tag.isEndTag()) || (tag.isEmpty() && tag.getAttribute("eID"))) {
+				// if it is </q> then pop the stack for the attributes
+				if (tag.isEndTag() && !u->quoteStack->empty()) {
+					XMLTag qTag(u->quoteStack->top());
+					if (u->quoteStack->size()) u->quoteStack->pop();
+
+					type    = qTag.getAttribute("type");
+					who     = qTag.getAttribute("who");
+					tmp     = qTag.getAttribute("level");
+					level   = (tmp) ? atoi(tmp) : 1;
+					tmp     = qTag.getAttribute("marker");
+					hasMark = tmp;
+					mark    = tmp;
+				}
+
+				// first check to see if we've been given an explicit mark
+				if (hasMark)
+					outText(mark, buf, u);
+				// finally, alternate " and ', if config says we should supply a mark
+				else if (u->osisQToTick)
+					outText((level % 2) ? '\"' : '\'', buf, u);
+
+				// Do this last so quote marks are included as WoC
+				if (who == "Jesus")
+					outText(u->wordsOfChristEnd, buf, u);
+			}
 		}
 
-                // <milestone type="line"/>
-                else if (!strncmp(token, "milestone", 9)) {
-			const char* type = strstr(token+10, "type=\"");
-			if (type && strncmp(type+6, "line", 4)) { //we check for type != line
-				userData->supressAdjacentWhitespace = true;
-        			buf.append('\n');
+		// <transChange>
+		else if (!strcmp(tag.getName(), "transChange")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				SWBuf type = tag.getAttribute("type");
+				u->lastTransChange = type;
+
+				// just do all transChange tags this way for now
+				if ((type == "added") || (type == "supplied"))
+					outText("<span class=\"transChangeSupplied\">", buf, u);
+				else if (type == "tenseChange")
+					buf += "*";
 			}
-                }
+			else if (tag.isEndTag()) {
+				SWBuf type = u->lastTransChange;
+				if ((type == "added") || (type == "supplied"))
+					outText("</span>", buf, u);
+			}
+			else {	// empty transChange marker?
+			}
+		}
 
+		// image
+		else if (!strcmp(tag.getName(), "figure")) {
+			const char *src = tag.getAttribute("src");
+			if (src) {		// assert we have a src attribute 
+				SWBuf filepath;
+				if (userData->module) {
+					filepath = userData->module->getConfigEntry("AbsoluteDataPath");
+					if ((filepath.size()) && (filepath[filepath.size()-1] != '/') && (src[0] != '/'))
+						filepath += '/';
+				}
+				filepath += src;
+
+				// images become clickable, if the UI supports showImage.
+				outText("<a href=\"passagestudy.jsp?action=showImage&value=", buf, u);
+				outText(URL::encode(filepath.c_str()).c_str(), buf, u);
+				outText("&module=", buf, u);
+				outText(URL::encode(u->version.c_str()).c_str(), buf, u);
+				outText("\">", buf, u);
+
+				outText("<img src=\"file:", buf, u);
+				outText(filepath, buf, u);
+				outText("\" border=\"0\" />", buf, u);
+
+				outText("</a>", buf, u);
+			}
+		}
+
+		// ok to leave these in
+		else if (!strcmp(tag.getName(), "div")) {
+			SWBuf type = tag.getAttribute("type");
+			if (type == "bookGroup") {
+			}
+			else if (type == "book") {
+			}
+			else if (type == "section") {
+			}
+			else if (type == "majorSection") {
+			}
+			else {
+				buf += tag;
+			}
+		}
+		else if (!strcmp(tag.getName(), "span")) {
+			buf += tag;
+		}
+		else if (!strcmp(tag.getName(), "br")) {
+			buf += tag;
+		}
+		else if (!strcmp(tag.getName(), "table")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				buf += "<table><tbody>\n";
+			}
+			else if (tag.isEndTag()) {
+				buf += "</tbody></table>\n";
+				++u->consecutiveNewlines;
+				u->supressAdjacentWhitespace = true;
+			}
+			
+		}
+		else if (!strcmp(tag.getName(), "row")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				buf += "\t<tr>";
+			}
+			else if (tag.isEndTag()) {
+				buf += "</tr>\n";
+			}
+			
+		}
+		else if (!strcmp(tag.getName(), "cell")) {
+			if ((!tag.isEndTag()) && (!tag.isEmpty())) {
+				buf += "<td>";
+			}
+			else if (tag.isEndTag()) {
+				buf += "</td>";
+			}
+		}
 		else {
+			if (!u->supressAdjacentWhitespace) u->consecutiveNewlines = 0;
 			return false;  // we still didn't handle token
 		}
 	}
+	if (!u->supressAdjacentWhitespace) u->consecutiveNewlines = 0;
 	return true;
 }
 




More information about the sword-cvs mailing list