The SWORD Project  1.9.0.svnversion
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
osisxhtml.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  *
3  * osisxhtml.cpp - Render filter for classed XHTML of an OSIS module
4  *
5  * $Id: osisxhtml.cpp 3726 2020-04-26 17:53:51Z scribe $
6  *
7  * Copyright 2011-2014 CrossWire Bible Society (http://www.crosswire.org)
8  * CrossWire Bible Society
9  * P. O. Box 2528
10  * Tempe, AZ 85280-2528
11  *
12  * This program is free software; you can redistribute it and/or modify it
13  * under the terms of the GNU General Public License as published by the
14  * Free Software Foundation version 2.
15  *
16  * This program is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * General Public License for more details.
20  *
21  */
22 
23 #include <stdlib.h>
24 #include <ctype.h>
25 #include <osisxhtml.h>
26 #include <utilxml.h>
27 #include <utilstr.h>
28 #include <versekey.h>
29 #include <swmodule.h>
30 #include <url.h>
31 #include <stringmgr.h>
32 #include <stack>
33 
35 
36 const char *OSISXHTML::getHeader() const {
37  const static char *header = "\
38  .divineName { font-variant: small-caps; }\n\
39  .wordsOfJesus { color: red; }\n\
40  .transChange { font-style: italic; }\n\
41  .transChange.transChange-supplied { font-style: italic; }\n\
42  .transChange.transChange-added { font-style: italic; }\n\
43  .transChange.transChange-tenseChange::before { content: '*'; }\n\
44  .transChange.transChange-tenseChange { font-style: normal; }\n\
45  .transChange:lang(zh) { font-style: normal; text-decoration: dotted underline; }\n\
46  .overline { text-decoration: overline; }\n\
47  .indent1 { margin-left: 1em; }\n\
48  .indent2 { margin-left: 2em; }\n\
49  .indent3 { margin-left: 3em; }\n\
50  .indent4 { margin-left: 4em; }\n\
51  abbr { &:hover{ &:before{ content: attr(title); } } }\n\
52  .small-caps { font-variant: small-caps; }\n\
53  .otPassage { font-variant: small-caps; }\n\
54  .selah { text-align: right; width: 50%; margin: 0; padding: 0; }\n\
55  .acrostic { text-align: center; }\n\
56  .colophon {font-style: italic; font-size: small; display: block; }\n\
57  .rdg { font-style: italic; }\n\
58  .inscription {font-variant: small-caps; }\n\
59  .catchWord {font-style: bold; }\n\
60  .x-p-indent {text-indent: 1em; }\n\
61  ";
62  // Acrostic for things like the titles in Psalm 119
63  return header;
64 }
65 
66 
67 namespace {
68 
69 // though this might be slightly slower, possibly causing an extra bool check, this is a renderFilter
70 // so speed isn't the absolute highest priority, and this is a very minor possible hit
71 static inline void outText(const char *t, SWBuf &o, BasicFilterUserData *u) { if (!u->suspendTextPassThru) o += t; else u->lastSuspendSegment += t; }
72 static inline void outText(char t, SWBuf &o, BasicFilterUserData *u) { if (!u->suspendTextPassThru) o += t; else u->lastSuspendSegment += t; }
73 
74 void processLemma(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf) {
75  const char *attrib;
76  const char *val;
77  if ((attrib = tag.getAttribute("lemma"))) {
78  int count = tag.getAttributePartCount("lemma", ' ');
79  int i = (count > 1) ? 0 : -1; // -1 for whole value cuz it's faster, but does the same thing as 0
80  do {
81  attrib = tag.getAttribute("lemma", i, ' ');
82  SWBuf at = attrib;
83  const char *prefix = at.stripPrefix(':');
84  if (i < 0) i = 0; // to handle our -1 condition
85  val = strchr(attrib, ':');
86  val = (val) ? (val + 1) : attrib;
87  SWBuf gh;
88  if (*val == 'G') {
89  gh = "Greek";
90  }
91  else if (*val == 'H') {
92  gh = "Hebrew";
93  }
94  else if (prefix) gh = prefix;
95  const char *val2 = val;
96  if ((strchr("GH", *val)) && (isdigit(val[1])))
97  val2++;
98  //if ((!strcmp(val2, "3588")) && (lastText.length() < 1))
99  // show = false;
100  //else {
101  if (!suspendTextPassThru) {
102  buf.appendFormatted("<small><em class=\"strongs\">&lt;<a class=\"strongs\" href=\"passagestudy.jsp?action=showStrongs&type=%s&value=%s\" class=\"strongs\">%s</a>&gt;</em></small>",
103  (gh.length()) ? gh.c_str() : "",
104  URL::encode(val2).c_str(),
105  val2);
106  }
107  //}
108 
109  } while (++i < count);
110  }
111 }
112 
113 
114 
115 void processMorph(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf) {
116  const char * attrib;
117  const char *val;
118  if ((attrib = tag.getAttribute("morph"))) { // && (show)) {
119  SWBuf savelemma = tag.getAttribute("savlm");
120  //if ((strstr(savelemma.c_str(), "3588")) && (lastText.length() < 1))
121  // show = false;
122  //if (show) {
123  int count = tag.getAttributePartCount("morph", ' ');
124  int i = (count > 1) ? 0 : -1; // -1 for whole value cuz it's faster, but does the same thing as 0
125  do {
126  attrib = tag.getAttribute("morph", i, ' ');
127  if (i < 0) i = 0; // to handle our -1 condition
128  val = strchr(attrib, ':');
129  val = (val) ? (val + 1) : attrib;
130  const char *val2 = val;
131  if ((*val == 'T') && (strchr("GH", val[1])) && (isdigit(val[2])))
132  val2+=2;
133  if (!suspendTextPassThru) {
134  buf.appendFormatted("<small><em class=\"morph\">(<a class=\"morph\" href=\"passagestudy.jsp?action=showMorph&type=%s&value=%s\" class=\"morph\">%s</a>)</em></small>",
135  URL::encode(tag.getAttribute("morph")).c_str(),
136  URL::encode(val).c_str(),
137  val2);
138  }
139  } while (++i < count);
140  //}
141  }
142 }
143 
144 
145 } // end anonymous namespace
146 
148  return new MyUserData(module, key);
149 }
150 
151 
153  setTokenStart("<");
154  setTokenEnd(">");
155 
156  setEscapeStart("&");
157  setEscapeEnd(";");
158 
161 
162  addAllowedEscapeString("quot");
163  addAllowedEscapeString("apos");
164  addAllowedEscapeString("amp");
167 
168  setTokenCaseSensitive(true);
169 
170  // addTokenSubstitute("lg", "<br />");
171  // addTokenSubstitute("/lg", "<br />");
172 
173  morphFirst = false;
174  renderNoteNumbers = false;
175 }
176 
177 class OSISXHTML::TagStack : public std::stack<SWBuf> {
178 };
179 
180 OSISXHTML::MyUserData::MyUserData(const SWModule *module, const SWKey *key) : BasicFilterUserData(module, key), quoteStack(new TagStack()), hiStack(new TagStack()), titleStack(new TagStack()), lineStack(new TagStack()) {
181  inXRefNote = false;
182  suspendLevel = 0;
183  wordsOfChristStart = "<span class=\"wordsOfJesus\"> ";
184  wordsOfChristEnd = "</span> ";
185  interModuleLinkStart = "<a class=\"%s\" href=\"sword://%s/%s\">";
186  interModuleLinkEnd = "</a>";
187  isBiblicalText = false;
188  osisQToTick = true; // default
190  if (module) {
191  osisQToTick = ((!module->getConfigEntry("OSISqToTick")) || (strcmp(module->getConfigEntry("OSISqToTick"), "false")));
192  version = module->getName();
193  isBiblicalText = (!strcmp(module->getType(), "Biblical Texts"));
194  }
195 }
196 
198  delete quoteStack;
199  delete hiStack;
200  delete titleStack;
201  delete lineStack;
202 }
203 
204 
206  if (++consecutiveNewlines <= 2) {
207  // any newlines at the start of a verse should get appended to a preverse heading
208  // since preverse cause a newline, simply be sure we have a preverse
209  if (!buf.size() && vkey && vkey->getVerse() && module && module->isProcessEntryAttributes()) {
210  module->getEntryAttributes()["Heading"]["Preverse"]["0"] += "<div></div>";
211  }
212  else {
213  outText("<br />\n", buf, this);
214  }
215  supressAdjacentWhitespace = true;
216  }
217 }
218 bool OSISXHTML::handleToken(SWBuf &buf, const char *token, BasicFilterUserData *userData) {
219  MyUserData *u = (MyUserData *)userData;
220  SWBuf scratch;
221  bool sub = (u->suspendTextPassThru) ? substituteToken(scratch, token) : substituteToken(buf, token);
222  if (!sub) {
223  // manually process if it wasn't a simple substitution
224  XMLTag tag(token);
225 
226  // <w> tag
227  if (!strcmp(tag.getName(), "w")) {
228 
229  // start <w> tag
230  if ((!tag.isEmpty()) && (!tag.isEndTag())) {
231  u->w = token;
232  }
233 
234  // end or empty <w> tag
235  else {
236  bool endTag = tag.isEndTag();
237  SWBuf lastText;
238  //bool show = true; // to handle unplaced article in kjv2003-- temporary till combined
239 
240  if (endTag) {
241  tag = u->w.c_str();
242  lastText = u->lastTextNode.c_str();
243  }
244  else lastText = "stuff";
245 
246  const char *attrib;
247  const char *val;
248  if ((attrib = tag.getAttribute("xlit"))) {
249  val = strchr(attrib, ':');
250  val = (val) ? (val + 1) : attrib;
251  outText(" ", buf, u);
252  outText(val, buf, u);
253  }
254  if ((attrib = tag.getAttribute("gloss"))) {
255  // I'm sure this is not the cleanest way to do it, but it gets the job done
256  // for rendering ruby chars properly ^_^
257  buf -= lastText.length();
258 
259  outText("<ruby><rb>", buf, u);
260  outText(lastText, buf, u);
261  outText("</rb><rp>(</rp><rt>", buf, u);
262  val = strchr(attrib, ':');
263  val = (val) ? (val + 1) : attrib;
264  outText(val, buf, u);
265  outText("</rt><rp>)</rp></ruby>", buf, u);
266  }
267  if (!morphFirst) {
268  processLemma(u->suspendTextPassThru, tag, buf);
269  processMorph(u->suspendTextPassThru, tag, buf);
270  }
271  else {
272  processMorph(u->suspendTextPassThru, tag, buf);
273  processLemma(u->suspendTextPassThru, tag, buf);
274  }
275  if ((attrib = tag.getAttribute("POS"))) {
276  val = strchr(attrib, ':');
277  val = (val) ? (val + 1) : attrib;
278  outText(" ", buf, u);
279  outText(val, buf, u);
280  }
281 
282  /*if (endTag)
283  outText( "}", buf, u);*/
284  }
285  }
286 
287  // <note> tag
288  else if (!strcmp(tag.getName(), "note")) {
289  if (!tag.isEndTag()) {
290  SWBuf type = tag.getAttribute("type");
291 
292  // for backward compatibility
293  if (type == "strongsMarkup") type = "x-strongsMarkup";
294  if (type == "x-cross-ref") type = "crossReference";
295 
296  SWBuf subType = tag.getAttribute("subType");
297  SWBuf classExtras = "";
298 
299  if (type.size()) {
300  classExtras.append(" ").append(type);
301  }
302  if (subType.size()) {
303  classExtras.append(" ").append(subType);
304  }
305 
306  bool strongsMarkup = type == "x-strongsMarkup";
307  if (strongsMarkup) {
308  tag.setEmpty(false); // handle bug in KJV2003 module where some note open tags were <note ... />
309  }
310 
311  if (!tag.isEmpty()) {
312 
313  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
314  SWBuf footnoteNumber = tag.getAttribute("swordFootnote");
315  SWBuf noteName = tag.getAttribute("n");
316  char ch = (type == "crossReference" ? 'x':'n');
317 
318  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
319 // u->inXRefNote = (ch == 'x');
320 
321  if (u->vkey) {
322  //printf("URL = %s\n",URL::encode(u->vkey->getText()).c_str());
323  buf.appendFormatted("<a class=\"noteMarker%s\" href=\"passagestudy.jsp?action=showNote&type=%c&value=%s&module=%s&passage=%s\"><small><sup class=\"%c\">*%c%s</sup></small></a>",
324  classExtras.c_str(),
325  ch,
326  URL::encode(footnoteNumber.c_str()).c_str(),
327  URL::encode(u->version.c_str()).c_str(),
328  URL::encode(u->vkey->getText()).c_str(),
329  ch,
330  ch,
331  (renderNoteNumbers ? noteName.c_str() : ""));
332  }
333  else {
334  buf.appendFormatted("<a class=\"noteMarker%s\" href=\"passagestudy.jsp?action=showNote&type=%c&value=%s&module=%s&passage=%s\"><small><sup class=\"%c\">*%c%s</sup></small></a>",
335  classExtras.c_str(),
336  ch,
337  URL::encode(footnoteNumber.c_str()).c_str(),
338  URL::encode(u->version.c_str()).c_str(),
339  URL::encode(u->key->getText()).c_str(),
340  ch,
341  ch,
342  (renderNoteNumbers ? noteName.c_str() : ""));
343  }
344  }
345  }
346  u->suspendTextPassThru = (++u->suspendLevel);
347  }
348  if (tag.isEndTag()) {
349  u->suspendTextPassThru = (--u->suspendLevel);
350  u->inXRefNote = false;
351  u->lastSuspendSegment = ""; // fix/work-around for nasb divineName in note bug
352  }
353  }
354 
355  // <p> paragraph and <lg> linegroup tags except newline at start of verse (immediately after verse number)
356  else if (!strcmp(tag.getName(), "p") || !strcmp(tag.getName(), "lg")) {
357  if ((!tag.isEndTag()) && (!tag.isEmpty())) { // non-empty start tag
358  u->outputNewline(buf);
359  }
360  else if (tag.isEndTag()) { // end tag
361  u->outputNewline(buf);
362  }
363  else { // empty paragraph break marker
364  u->outputNewline(buf);
365  }
366  }
367 
368  // Milestoned paragraphs, created by osis2mod
369  // <div type="paragraph" sID.../>
370  // <div type="paragraph" eID.../>
371  else if (tag.isEmpty() && !strcmp(tag.getName(), "div") && tag.getAttribute("type") && (!strcmp(tag.getAttribute("type"), "x-p") || !strcmp(tag.getAttribute("type"), "paragraph") || !strcmp(tag.getAttribute("type"), "colophon"))) {
372  // <div type="paragraph" sID... />
373  if (tag.getAttribute("sID")) { // non-empty start tag
374  u->outputNewline(buf);
375  // safe because we've verified type is present from if statement above
376  if (!strcmp(tag.getAttribute("type"), "colophon")) {
377  outText("<div class=\"colophon\">", buf, u);
378  }
379 
380  }
381  // <div type="paragraph" eID... />
382  else if (tag.getAttribute("eID")) {
383  u->outputNewline(buf);
384  // safe because we've verified type is present from if statement above
385  if (!strcmp(tag.getAttribute("type"), "colophon")) {
386  outText("</div>", buf, u);
387  }
388 
389  }
390  }
391 
392  // <reference> tag
393  else if (!strcmp(tag.getName(), "reference")) {
394  if (!u->inXRefNote) { // only show these if we're not in an xref note
395  if (!tag.isEndTag()) {
396  SWBuf target;
397  SWBuf work;
398  SWBuf ref;
399  SWBuf type;
400  SWBuf classes = "";
401 
402  bool is_scripRef = false;
403 
404  target = tag.getAttribute("osisRef");
405  const char* the_ref = strchr(target, ':');
406  type = tag.getAttribute("type");
407 
408  if (type.size()) {
409  classes.append(type);
410  }
411 
412  if(!the_ref) {
413  // No work
414  ref = target;
415  is_scripRef = true;
416  }
417  else {
418  // Compensate for starting :
419  ref = the_ref + 1;
420 
421  int size = target.size() - ref.size() - 1;
422  work.setSize(size);
423  strncpy(work.getRawData(), target, size);
424 
425  // For Bible:Gen.3.15 or Bible.vulgate:Gen.3.15
426  if(!strncmp(work, "Bible", 5))
427  is_scripRef = true;
428  }
429 
430  if(is_scripRef)
431  {
432  buf.appendFormatted("<a class=\"%s\" href=\"passagestudy.jsp?action=showRef&type=scripRef&value=%s&module=\">",
433  classes.c_str(),
434  URL::encode(ref.c_str()).c_str()
435 // (work.size()) ? URL::encode(work.c_str()).c_str() : "")
436  );
437  }
438  else
439  {
440  // Dictionary link, or something
442  classes.c_str(),
443  URL::encode(work.c_str()).c_str(),
444  URL::encode(ref.c_str()).c_str()
445  );
446  }
447  }
448  else {
449  outText(u->interModuleLinkEnd, buf, u);
450  }
451  }
452  }
453 
454  // <l> poetry, etc
455  else if (!strcmp(tag.getName(), "l")) {
456  // start line marker
457  if (tag.getAttribute("sID") || (!tag.isEndTag() && !tag.isEmpty())) {
458  SWBuf type = tag.getAttribute("type");
459  if (type == "selah") {
460  outText("<p class=\"selah\">", buf, u);
461  } else {
462  // nested lines plus if the line itself has an x-indent type attribute value
463  outText(SWBuf("<span class=\"line indent").appendFormatted("%d\">", u->lineStack->size() + (SWBuf("x-indent") == tag.getAttribute("type")?1:0)).c_str(), buf, u);
464  }
465  u->lineStack->push(tag.toString());
466  }
467  // end line marker
468  else if (tag.getAttribute("eID") || tag.isEndTag()) {
469  SWBuf type = "";
470  if (!u->lineStack->empty()) {
471  XMLTag startTag(u->lineStack->top());
472  type = startTag.getAttribute("type");
473  }
474  if (type == "selah") {
475  outText("</p>", buf, u);
476  } else {
477  outText("</span>", buf, u);
478  u->outputNewline(buf);
479  }
480  if (u->lineStack->size()) u->lineStack->pop();
481  }
482  // <l/> without eID or sID
483  // Note: this is improper osis. This should be <lb/>
484  else if (tag.isEmpty() && !tag.getAttribute("sID")) {
485  u->outputNewline(buf);
486  }
487  }
488 
489  // <lb.../>
490  else if (!strcmp(tag.getName(), "lb") && (!tag.getAttribute("type") || strcmp(tag.getAttribute("type"), "x-optional"))) {
491  u->outputNewline(buf);
492  }
493  // <milestone type="line"/>
494  // <milestone type="x-p"/>
495  // <milestone type="cQuote" marker="x"/>
496  else if ((!strcmp(tag.getName(), "milestone")) && (tag.getAttribute("type"))) {
497  // safe because we've verified type is present from if statement above
498  const char *type = tag.getAttribute("type");
499  if (!strcmp(type, "line")) {
500  u->outputNewline(buf);
501  if (tag.getAttribute("subType") && !strcmp(tag.getAttribute("subType"), "x-PM")) {
502  u->outputNewline(buf);
503  }
504  }
505  else if (!strcmp(type,"x-p")) {
506  if (tag.getAttribute("marker"))
507  outText(tag.getAttribute("marker"), buf, u);
508  else outText("<!p>", buf, u);
509  }
510  else if (!strcmp(type, "cQuote")) {
511  const char *tmp = tag.getAttribute("marker");
512  bool hasMark = tmp;
513  SWBuf mark = tmp;
514  tmp = tag.getAttribute("level");
515  int level = (tmp) ? atoi(tmp) : 1;
516 
517  // first check to see if we've been given an explicit mark
518  if (hasMark)
519  outText(mark, buf, u);
520  // finally, alternate " and ', if config says we should supply a mark
521  else if (u->osisQToTick)
522  outText((level % 2) ? '\"' : '\'', buf, u);
523  }
524  else if (!strcmp(type, "x-importer")) {
525  //drop tag as not relevant
526  }
527 
528 
529  else {
530  SWBuf attVal = tag.getAttribute("type");
531  outText(SWBuf("<span class=\"") + attVal + "\"", buf,u);
532  StringList attributes = tag.getAttributeNames();
533  for (StringList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) {
534  attVal = tag.getAttribute(*it);
535  outText(SWBuf(" data-") + *it + "=\"" + attVal + "\"", buf,u);
536  }
537  outText(SWBuf("></span>"), buf,u);
538 
539  }
540  }
541 
542  // <title>
543  else if (!strcmp(tag.getName(), "title")) {
544  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
545  SWBuf type = tag.getAttribute("type");
546  SWBuf canonical = tag.getAttribute("canonical");
547 
548  SWBuf classExtras = "";
549 
550  if (type.size()) {
551  classExtras.append(" ").append(type);
552  }
553  if (canonical.size() && !strcmp(canonical,"true")) {
554  classExtras.append(" canonical");
555  }
556  if (u->vkey && !u->vkey->getVerse()) {
557  if (!u->vkey->getChapter()) {
558  if (!u->vkey->getBook()) {
559  if (!u->vkey->getTestament()) {
560  outText(SWBuf("<h1 class=\"moduleHeader") + classExtras + "\">", buf, u);
561  tag.setAttribute("pushed", "h1");
562  }
563  else {
564  outText(SWBuf("<h1 class=\"testamentHeader") + classExtras + "\">", buf, u);
565  tag.setAttribute("pushed", "h1");
566  }
567  }
568  else {
569  outText(SWBuf("<h1 class=\"bookHeader") + classExtras + "\">", buf, u);
570  tag.setAttribute("pushed", "h1");
571  }
572  }
573  else {
574  outText(SWBuf("<h2 class=\"chapterHeader") + classExtras + "\">", buf, u);
575  tag.setAttribute("pushed", "h2");
576  }
577  }
578  else {
579  outText(SWBuf("<h3 class=\"title") + classExtras + "\">", buf, u);
580  tag.setAttribute("pushed", "h3");
581  }
582  u->titleStack->push(tag.toString());
583  }
584  else if (tag.isEndTag()) {
585  if (!u->titleStack->empty()) {
586  XMLTag tag(u->titleStack->top());
587  if (u->titleStack->size()) u->titleStack->pop();
588  SWBuf pushed = tag.getAttribute("pushed");
589  if (pushed.size()) {
590  outText((SWBuf)"</" + pushed + ">\n\n", buf, u);
591  }
592  else {
593  outText( "</h3>\n\n", buf, u);
594  }
595  ++u->consecutiveNewlines;
596  u->supressAdjacentWhitespace = true;
597  }
598  }
599  }
600 
601  // <list>
602  else if (!strcmp(tag.getName(), "list")) {
603  if((!tag.isEndTag()) && (!tag.isEmpty())) {
604  outText("<ul>\n", buf, u);
605  }
606  else if (tag.isEndTag()) {
607  outText("</ul>\n", buf, u);
608  ++u->consecutiveNewlines;
609  u->supressAdjacentWhitespace = true;
610  }
611  }
612 
613  // <item>
614  else if (!strcmp(tag.getName(), "item")) {
615  if((!tag.isEndTag()) && (!tag.isEmpty())) {
616  outText("\t<li>", buf, u);
617  }
618  else if (tag.isEndTag()) {
619  outText("</li>\n", buf, u);
620  ++u->consecutiveNewlines;
621  u->supressAdjacentWhitespace = true;
622  }
623  }
624  // <catchWord> & <rdg> tags (italicize)
625  else if (!strcmp(tag.getName(), "rdg") || !strcmp(tag.getName(), "catchWord") || !strcmp(tag.getName(), "inscription")) {
626  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
627  outText("<span class=\"", buf, u);
628  outText(tag.getName(), buf, u);
629  outText("\">", buf, u);
630  }
631  else if (tag.isEndTag()) {
632  outText("</span>", buf, u);
633  }
634  }
635  // <seg>
636  else if (!strcmp(tag.getName(), "seg")) {
637  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
638  SWBuf type = tag.getAttribute("type");
639  outText("<span class=\"", buf, u);
640  outText(type, buf, u);
641  outText("\">", buf, u);
642  }
643  else if (tag.isEndTag()) {
644  outText("</span>", buf, u);
645  }
646  }
647 
648  // divineName
649  else if (!strcmp(tag.getName(), "divineName")) {
650  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
651  u->suspendTextPassThru = (++u->suspendLevel);
652  }
653  else if (tag.isEndTag()) {
654  SWBuf lastText = u->lastSuspendSegment.c_str();
655  u->suspendTextPassThru = (--u->suspendLevel);
656  if (lastText.size()) {
657  scratch.setFormatted("<span class=\"divineName\">%s</span>", lastText.c_str());
658  outText(scratch.c_str(), buf, u);
659  }
660  }
661  }
662 
663  // <hi> text highlighting
664  else if (!strcmp(tag.getName(), "hi")) {
665  SWBuf type = tag.getAttribute("type");
666 
667  // handle tei rend attribute if type doesn't exist
668  if (!type.length()) type = tag.getAttribute("rend");
669 
670  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
671  if (type == "bold" || type == "b" || type == "x-b") {
672  outText("<b>", buf, u);
673  }
674 
675  // there is no officially supported OSIS overline attribute,
676  // thus either TEI overline or OSIS x-overline would be best,
677  // but we have used "ol" in the past, as well. Once a valid
678  // OSIS overline attribute is made available, these should all
679  // eventually be deprecated and never documented that they are supported.
680  else if (type == "ol" || type == "overline" || type == "x-overline") {
681  outText("<span class=\"overline\">", buf, u);
682  }
683 
684  else if (type == "super") {
685  outText("<sup>", buf, u);
686  }
687  else if (type == "sub") {
688  outText("<sub>", buf, u);
689  }
690  else if (type == "i" || type == "italic") {
691  outText("<i>", buf, u);
692  } else { // all other types
693  if (type.startsWith("x-")) type << 2;
694  outText(SWBuf("<span class=\"") + type + SWBuf("\">"), buf, u);
695  }
696  u->hiStack->push(tag.toString());
697  }
698  else if (tag.isEndTag()) {
699  SWBuf type = "";
700  if (!u->hiStack->empty()) {
701  XMLTag tag(u->hiStack->top());
702  if (u->hiStack->size()) u->hiStack->pop();
703  type = tag.getAttribute("type");
704  if (!type.length()) type = tag.getAttribute("rend");
705  }
706  if (type == "bold" || type == "b" || type == "x-b") {
707  outText("</b>", buf, u);
708  }
709  else if (type == "ol") {
710  outText("</span>", buf, u);
711  }
712  else if (type == "super") {
713  outText("</sup>", buf, u);
714  }
715  else if (type == "sub") {
716  outText("</sub>", buf, u);
717  }
718  else if (type == "i" || type == "italic") {
719  outText("</i>", buf, u);
720  } else {
721  outText("</span>", buf, u);
722  }
723  }
724  }
725 
726  // <q> quote
727  // Rules for a quote element:
728  // If the tag is empty with an sID or an eID then use whatever it specifies for quoting.
729  // Note: empty elements without sID or eID are ignored.
730  // If the tag is <q> then use it's specifications and push it onto a stack for </q>
731  // If the tag is </q> then use the pushed <q> for specification
732  // If there is a marker attribute, possibly empty, this overrides osisQToTick.
733  // If osisQToTick, then output the marker, using level to determine the type of mark.
734  else if (!strcmp(tag.getName(), "q")) {
735  SWBuf type = tag.getAttribute("type");
736  SWBuf who = tag.getAttribute("who");
737  const char *tmp = tag.getAttribute("level");
738  int level = (tmp) ? atoi(tmp) : 1;
739  tmp = tag.getAttribute("marker");
740  bool hasMark = tmp;
741  SWBuf mark = tmp;
742 
743  // open <q> or <q sID... />
744  if ((!tag.isEmpty() && !tag.isEndTag()) || (tag.isEmpty() && tag.getAttribute("sID"))) {
745  // if <q> then remember it for the </q>
746  if (!tag.isEmpty()) {
747  u->quoteStack->push(tag.toString());
748  }
749 
750  // Do this first so quote marks are included as WoC
751  if (who == "Jesus")
752  outText(u->wordsOfChristStart, buf, u);
753 
754  // first check to see if we've been given an explicit mark
755  if (hasMark)
756  outText(mark, buf, u);
757  //alternate " and '
758  else if (u->osisQToTick)
759  outText((level % 2) ? '\"' : '\'', buf, u);
760  }
761  // close </q> or <q eID... />
762  else if ((tag.isEndTag()) || (tag.isEmpty() && tag.getAttribute("eID"))) {
763  // if it is </q> then pop the stack for the attributes
764  if (tag.isEndTag() && !u->quoteStack->empty()) {
765  XMLTag qTag(u->quoteStack->top());
766  if (u->quoteStack->size()) u->quoteStack->pop();
767 
768  type = qTag.getAttribute("type");
769  who = qTag.getAttribute("who");
770  tmp = qTag.getAttribute("level");
771  level = (tmp) ? atoi(tmp) : 1;
772  tmp = qTag.getAttribute("marker");
773  hasMark = tmp;
774  mark = tmp;
775  }
776 
777  // first check to see if we've been given an explicit mark
778  if (hasMark)
779  outText(mark, buf, u);
780  // finally, alternate " and ', if config says we should supply a mark
781  else if (u->osisQToTick)
782  outText((level % 2) ? '\"' : '\'', buf, u);
783 
784  // Do this last so quote marks are included as WoC
785  if (who == "Jesus")
786  outText(u->wordsOfChristEnd, buf, u);
787  }
788  }
789 
790  // <transChange>
791  else if (!strcmp(tag.getName(), "transChange")) {
792  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
793  SWBuf type = tag.getAttribute("type");
794  u->lastTransChange = type;
795 
796  outText("<span class=\"transChange", buf, u);
797  if (type.length()) {
798  outText(" transChange-", buf, u);
799  outText(type, buf, u);
800  }
801  outText("\">", buf, u);
802  }
803  else if (tag.isEndTag()) {
804  outText("</span>", buf, u);
805  }
806  else { // empty transChange marker?
807  }
808  }
809 
810  // image
811  else if (!strcmp(tag.getName(), "figure")) {
812  const char *src = tag.getAttribute("src");
813  if (src) { // assert we have a src attribute
814  SWBuf filepath;
815  if (userData->module) {
816  filepath = userData->module->getConfigEntry("AbsoluteDataPath");
817  if ((filepath.size()) && (filepath[filepath.size()-1] != '/') && (src[0] != '/'))
818  filepath += '/';
819  }
820  filepath += src;
821 
822  // images become clickable, if the UI supports showImage.
823  outText("<a href=\"passagestudy.jsp?action=showImage&value=", buf, u);
824  outText(URL::encode(filepath.c_str()).c_str(), buf, u);
825  outText("&module=", buf, u);
826  outText(URL::encode(u->version.c_str()).c_str(), buf, u);
827  outText("\">", buf, u);
828 
829  outText("<img src=\"file:", buf, u);
830  outText(filepath, buf, u);
831  outText("\" border=\"0\" />", buf, u);
832 
833  outText("</a>", buf, u);
834  }
835  }
836 
837  // ok to leave these in
838  else if (!strcmp(tag.getName(), "div")) {
839  SWBuf type = tag.getAttribute("type");
840  if (type == "bookGroup") {
841  }
842  else if (type == "book") {
843  }
844  else if (type == "section") {
845  }
846  else if (type == "majorSection") {
847  }
848  else if ((!tag.isEndTag()) && (!tag.isEmpty())) {
849  SWBuf type = tag.getAttribute("type");
850  outText("<div class=\"", buf, u);
851  outText(type, buf, u);
852  outText("\">", buf, u);
853  }
854  else if (tag.isEndTag()) {
855  outText("</div>", buf, u);
856  }
857  else if (!(type == "colophon")) {
858  if (tag.getAttribute("sID")) tag.setEmpty(false);
859  if (tag.getAttribute("eID")) tag.setEndTag(true);
860  outText(tag, buf, u);
861  }
862 
863  }
864  else if (!strcmp(tag.getName(), "span")) {
865  outText(tag, buf, u);
866  }
867  else if (!strcmp(tag.getName(), "abbr")) {
868  if (!tag.isEndTag()) {
869  SWBuf title = tag.getAttribute("expansion");
870  outText("<abbr title=\"", buf, u);
871  outText(title, buf, u);
872  outText("\">", buf, u);
873  }
874  else if (tag.isEndTag()) {
875  outText("</abbr>", buf, u);
876  }
877 
878  }
879  else if (!strcmp(tag.getName(), "br")) {
880  outText( tag, buf, u);
881  }
882  else if (!strcmp(tag.getName(), "table")) {
883  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
884  outText( "<table><tbody>\n", buf, u);
885  }
886  else if (tag.isEndTag()) {
887  outText( "</tbody></table>\n", buf, u);
888  ++u->consecutiveNewlines;
889  u->supressAdjacentWhitespace = true;
890  }
891 
892  }
893  else if (!strcmp(tag.getName(), "row")) {
894  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
895  outText( "\t<tr>", buf, u);
896  }
897  else if (tag.isEndTag()) {
898  outText( "</tr>\n", buf, u);
899  }
900 
901  }
902  else if (!strcmp(tag.getName(), "cell")) {
903  if ((!tag.isEndTag()) && (!tag.isEmpty())) {
904  outText( "<td>", buf, u);
905  }
906  else if (tag.isEndTag()) {
907  outText( "</td>", buf, u);
908  }
909  }
910  else {
912  return false; // we still didn't handle token
913  }
914  }
916  return true;
917 }
918 
919 
const char * getName() const
Definition: swmodule.cpp:204
SWBuf interModuleLinkStart
Definition: osisxhtml.h:54
#define SWORD_NAMESPACE_START
Definition: defs.h:39
SWBuf & appendFormatted(const char *format,...)
Definition: swbuf.cpp:81
void setTokenEnd(const char *tokenEnd)
void addAllowedEscapeString(const char *findString)
Definition: swbuf.h:47
unsigned long length() const
Definition: swbuf.h:197
TagStack * quoteStack
Definition: osisxhtml.h:56
const char * getType() const
Definition: swmodule.cpp:232
void setEmpty(bool value)
Definition: utilxml.h:66
const char * setAttribute(const char *attribName, const char *attribValue, int partNum=-1, char partSplit= '|')
Definition: utilxml.cpp:248
virtual const char * getConfigEntry(const char *key) const
Definition: swmodule.cpp:1159
const SWModule * module
Definition: swbasicfilter.h:42
const char * getName() const
Definition: utilxml.h:58
SWText * module
Definition: osis2mod.cpp:105
Definition: utilxml.h:38
bool startsWith(const SWBuf &prefix) const
Definition: swbuf.h:486
MyUserData(const SWModule *module, const SWKey *key)
Definition: osisxhtml.cpp:180
const char * toString() const
Definition: utilxml.cpp:285
void setTokenCaseSensitive(bool val)
void setEscapeStart(const char *escStart)
bool isEmpty() const
Definition: utilxml.h:60
bool substituteToken(SWBuf &buf, const char *token)
const VerseKey * vkey
Definition: swbasicfilter.h:44
virtual const char * getText() const
Definition: versekey.cpp:1242
virtual const char * getText() const
Definition: swkey.cpp:184
void processLemma(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf)
const StringList getAttributeNames() const
Definition: utilxml.cpp:188
void setTokenStart(const char *tokenStart)
virtual int getChapter() const
Definition: versekey.cpp:1522
char * getRawData()
Definition: swbuf.h:379
const char * c_str() const
Definition: swbuf.h:158
std::list< SWBuf > StringList
Definition: swmodule.cpp:91
SWBuf & append(const char *str, long max=-1)
Definition: swbuf.h:274
bool renderNoteNumbers
Definition: osisxhtml.h:37
void setEndTag(bool value)
Definition: utilxml.h:79
void setPassThruNumericEscapeString(bool val)
static SWORD_NAMESPACE_START const char * classes[]
Definition: swdisp.cpp:31
virtual char getBook() const
Definition: versekey.cpp:1510
static void outText(const char *t, SWBuf &o, BasicFilterUserData *u)
virtual int getVerse() const
Definition: versekey.cpp:1534
void processMorph(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf)
void outputNewline(SWBuf &buf)
Definition: osisxhtml.cpp:205
TagStack * hiStack
Definition: osisxhtml.h:57
unsigned long size() const
Definition: swbuf.h:185
const char * stripPrefix(char separator, bool endOfStringAsSeparator=false)
Definition: swbuf.h:457
const SWKey * key
Definition: swbasicfilter.h:43
virtual bool isProcessEntryAttributes() const
Definition: swmodule.h:832
virtual AttributeTypeList & getEntryAttributes() const
Definition: swmodule.h:817
const char * getAttribute(const char *attribName, int partNum=-1, char partSplit= '|') const
Definition: utilxml.cpp:230
int size
Definition: regex.c:5043
void setEscapeStringCaseSensitive(bool val)
bool isEndTag(const char *eID=0) const
Definition: utilxml.cpp:323
virtual BasicFilterUserData * createUserData(const SWModule *module, const SWKey *key)
Definition: osisxhtml.cpp:147
virtual const char * getHeader() const
Definition: osisxhtml.cpp:36
TagStack * lineStack
Definition: osisxhtml.h:59
void setEscapeEnd(const char *escEnd)
#define SWORD_NAMESPACE_END
Definition: defs.h:40
TagStack * titleStack
Definition: osisxhtml.h:58
SWBuf & setFormatted(const char *format,...)
Definition: swbuf.cpp:50
Definition: swkey.h:77
bool morphFirst
Definition: osisxhtml.h:36
static const SWBuf encode(const char *urlText)
Definition: url.cpp:231
void setSize(unsigned long len)
Definition: swbuf.h:255
virtual bool handleToken(SWBuf &buf, const char *token, BasicFilterUserData *userData)
Definition: osisxhtml.cpp:218
virtual char getTestament() const
Definition: versekey.cpp:1498
int getAttributePartCount(const char *attribName, char partSplit= '|') const
Definition: utilxml.cpp:218