1 /******************************************************************************
2 *
3 * osislatex.cpp - Render filter for LaTeX of an OSIS module
4 *
5 * $Id: osislatex.cpp 3548 2017-12-10 05:11:38Z 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 <osislatex.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
34 SWORD_NAMESPACE_START
35
getHeader() const36 const char *OSISLaTeX::getHeader() const {
37 // can be used to return static start-up info, like packages to load. Not sure yet if I want to retain it.
38
39 const static char *header = "\
40 \\LoadClass[11pt,a4paper,twoside,headinclude=true,footinclude=true,BCOR=0mm,DIV=calc]{scrbook}\n\
41 \\LoadClass[11pt,a4paper,twoside,headinclude=true,footinclude=true,BCOR=0mm,DIV=calc]{scrbook}\n\
42 \\NeedsTeXFormat{LaTeX2e}\n\
43 \\ProvidesClass{sword}[2015/03/29 CrossWire LaTeX class for Biblical texts]\n\
44 %\\sworddiclink{%s}{%s}{\n\
45 %\\sworddictref{%s}{%s}{\n\
46 %\\sworddict{%s}{\n\
47 %\\sworddivinename}{%s}{\n\
48 %\\swordfont{\n\
49 %\\swordfootnote[%c]{%s}{%s}{%s}{%s}{\n\
50 %\\swordfootnote{%s}{%s}{%s}{\n\
51 %\\swordfootnote{%s}{%s}{%s}{%s}{\n\
52 %\\swordmorph{\n\
53 %\\swordmorph[Greek]{%s}\n\
54 %\\swordmorph[lemma]{%s}\n\
55 %\\swordmorph{%s}\n\
56 %\\swordpoetryline{\n\
57 %\\swordquote{\n\
58 %\\swordref{%s}{%s}{\n\
59 %\\swordsection{\n\
60 %\\swordsection{}{\n\
61 %\\swordsection{book}{\n\
62 %\\swordsection{sechead}{\n\
63 %\\swordstrong[Greek]{\n\
64 %\\swordstrong[Greektense]{\n\
65 %\\swordstrong[Hebrew]{\n\
66 %\\swordstrong[Hebrewtense]{\n\
67 %\\swordstrong[%s]{%s}{\n\
68 %\\swordstrong{%s}{%s}\n\
69 %\\swordtitle{\n\
70 %\\swordtranschange{supplied}{\n\
71 %\\swordtranschange{tense}{\n\
72 %\\swordwoj{\n\
73 %\\swordxref{\n\
74 %\\swordxref{%s}{\n\
75 %\\swordxref{%s}{%s}{\n\
76 ";
77 return header;
78 }
79
80
81 namespace {
82
83 // though this might be slightly slower, possibly causing an extra bool check, this is a renderFilter
84 // so speed isn't the absolute highest priority, and this is a very minor possible hit
outText(const char * t,SWBuf & o,BasicFilterUserData * u)85 static inline void outText(const char *t, SWBuf &o, BasicFilterUserData *u) { if (!u->suspendTextPassThru) o += t; else u->lastSuspendSegment += t; }
outText(char t,SWBuf & o,BasicFilterUserData * u)86 static inline void outText(char t, SWBuf &o, BasicFilterUserData *u) { if (!u->suspendTextPassThru) o += t; else u->lastSuspendSegment += t; }
87
processLemma(bool suspendTextPassThru,XMLTag & tag,SWBuf & buf)88 void processLemma(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf) {
89 const char *attrib;
90 const char *val;
91 if ((attrib = tag.getAttribute("lemma"))) {
92 int count = tag.getAttributePartCount("lemma", ' ');
93 int i = (count > 1) ? 0 : -1; // -1 for whole value cuz it's faster, but does the same thing as 0
94 do {
95 attrib = tag.getAttribute("lemma", i, ' ');
96 if (i < 0) i = 0; // to handle our -1 condition
97 val = strchr(attrib, ':');
98 val = (val) ? (val + 1) : attrib;
99 SWBuf gh;
100 if(*val == 'G')
101 gh = "Greek";
102 if(*val == 'H')
103 gh = "Hebrew";
104 const char *val2 = val;
105 if ((strchr("GH", *val)) && (isdigit(val[1])))
106 val2++;
107 //if ((!strcmp(val2, "3588")) && (lastText.length() < 1))
108 // show = false;
109 //else {
110 if (!suspendTextPassThru) {
111 buf.appendFormatted("\\swordstrong{%s}{%s}",
112 (gh.length()) ? gh.c_str() : "",
113 val2);
114 }
115 //}
116
117 } while (++i < count);
118 }
119 }
120
121
122
processMorph(bool suspendTextPassThru,XMLTag & tag,SWBuf & buf)123 void processMorph(bool suspendTextPassThru, XMLTag &tag, SWBuf &buf) {
124 const char * attrib;
125 const char *val;
126 if ((attrib = tag.getAttribute("morph"))) { // && (show)) {
127 SWBuf savelemma = tag.getAttribute("savlm");
128 //if ((strstr(savelemma.c_str(), "3588")) && (lastText.length() < 1))
129 // show = false;
130 //if (show) {
131 int count = tag.getAttributePartCount("morph", ' ');
132 int i = (count > 1) ? 0 : -1; // -1 for whole value cuz it's faster, but does the same thing as 0
133 do {
134 attrib = tag.getAttribute("morph", i, ' ');
135 if (i < 0) i = 0; // to handle our -1 condition
136 val = strchr(attrib, ':');
137 val = (val) ? (val + 1) : attrib;
138 const char *val2 = val;
139 if ((*val == 'T') && (strchr("GH", val[1])) && (isdigit(val[2])))
140 val2+=2;
141 if (!suspendTextPassThru) {
142 buf.appendFormatted("\\swordmorph{%s}",
143 tag.getAttribute("morph")
144 );
145 }
146 } while (++i < count);
147 //}
148 }
149 }
150
151
152 } // end anonymous namespace
153
createUserData(const SWModule * module,const SWKey * key)154 BasicFilterUserData *OSISLaTeX::createUserData(const SWModule *module, const SWKey *key) {
155 return new MyUserData(module, key);
156 }
157
158
OSISLaTeX()159 OSISLaTeX::OSISLaTeX() {
160 setTokenStart("<");
161 setTokenEnd(">");
162
163 setEscapeStart("&");
164 setEscapeEnd(";");
165
166 setEscapeStringCaseSensitive(true);
167 setPassThruNumericEscapeString(true);
168
169 addAllowedEscapeString("quot");
170 addAllowedEscapeString("apos");
171 addAllowedEscapeString("amp");
172 addAllowedEscapeString("lt");
173 addAllowedEscapeString("gt");
174
175 setTokenCaseSensitive(true);
176
177 // addTokenSubstitute("lg", "<br />");
178 // addTokenSubstitute("/lg", "<br />");
179
180 morphFirst = false;
181 renderNoteNumbers = false;
182 }
183
184 class OSISLaTeX::TagStack : public std::stack<SWBuf> {
185 };
186
MyUserData(const SWModule * module,const SWKey * key)187 OSISLaTeX::MyUserData::MyUserData(const SWModule *module, const SWKey *key) : BasicFilterUserData(module, key), quoteStack(new TagStack()), hiStack(new TagStack()), titleStack(new TagStack()), lineStack(new TagStack()) {
188 inXRefNote = false;
189 suspendLevel = 0;
190 divLevel = "module";
191 wordsOfChristStart = "\\swordwoj{";
192 wordsOfChristEnd = "}";
193 consecutiveNewlines = 0;
194 firstCell = false;
195 }
196
~MyUserData()197 OSISLaTeX::MyUserData::~MyUserData() {
198 delete quoteStack;
199 delete hiStack;
200 delete titleStack;
201 delete lineStack;
202 }
203
outputNewline(SWBuf & buf)204 void OSISLaTeX::MyUserData::outputNewline(SWBuf &buf) {
205 if (++consecutiveNewlines <= 2) {
206 outText("//\n", buf, this);
207 supressAdjacentWhitespace = true;
208 }
209 }
handleToken(SWBuf & buf,const char * token,BasicFilterUserData * userData)210 bool OSISLaTeX::handleToken(SWBuf &buf, const char *token, BasicFilterUserData *userData) {
211 MyUserData *u = (MyUserData *)userData;
212 SWBuf scratch;
213
214 bool sub = (u->suspendTextPassThru) ? substituteToken(scratch, token) : substituteToken(buf, token);
215 if (!sub) {
216 // manually process if it wasn't a simple substitution
217 XMLTag tag(token);
218
219 // <w> tag
220 if (!strcmp(tag.getName(), "w")) {
221
222 // start <w> tag
223 if ((!tag.isEmpty()) && (!tag.isEndTag())) {
224 u->w = token;
225 }
226
227 // end or empty <w> tag
228 else {
229 bool endTag = tag.isEndTag();
230 SWBuf lastText;
231 //bool show = true; // to handle unplaced article in kjv2003-- temporary till combined
232
233 if (endTag) {
234 tag = u->w.c_str();
235 lastText = u->lastTextNode.c_str();
236 }
237 else lastText = "stuff";
238
239 const char *attrib;
240 const char *val;
241 if ((attrib = tag.getAttribute("xlit"))) {
242 val = strchr(attrib, ':');
243 val = (val) ? (val + 1) : attrib;
244 outText(" ", buf, u);
245 outText(val, buf, u);
246 }
247 if ((attrib = tag.getAttribute("gloss"))) {
248 // I'm sure this is not the cleanest way to do it, but it gets the job done
249 // for rendering ruby chars properly ^_^
250 buf -= lastText.length();
251
252 outText("\\ruby{", buf, u);
253 outText(lastText, buf, u);
254 outText("}{", buf, u);
255 outText(attrib, buf, u);
256 outText("}", buf, u);
257 }
258 if (!morphFirst) {
259 processLemma(u->suspendTextPassThru, tag, buf);
260 processMorph(u->suspendTextPassThru, tag, buf);
261 }
262 else {
263 processMorph(u->suspendTextPassThru, tag, buf);
264 processLemma(u->suspendTextPassThru, tag, buf);
265 }
266 if ((attrib = tag.getAttribute("POS"))) {
267 val = strchr(attrib, ':');
268 val = (val) ? (val + 1) : attrib;
269 outText(" ", buf, u);
270 outText(val, buf, u);
271 }
272
273
274 }
275 }
276
277 // <note> tag
278
279 else if (!strcmp(tag.getName(), "note")) {
280 if (!tag.isEndTag()) {
281 SWBuf type = tag.getAttribute("type");
282 bool strongsMarkup = (type == "x-strongsMarkup" || type == "strongsMarkup"); // the latter is deprecated
283 if (strongsMarkup) {
284 tag.setEmpty(false); // handle bug in KJV2003 module where some note open tags were <note ... />
285 }
286
287 if (!tag.isEmpty()) {
288
289 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
290 SWBuf footnoteNumber = tag.getAttribute("swordFootnote");
291 SWBuf footnoteBody = "";
292 if (u->module){
293 footnoteBody += u->module->getEntryAttributes()["Footnote"][footnoteNumber]["body"];
294 }
295 SWBuf noteName = tag.getAttribute("n");
296
297 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
298 // u->inXRefNote = (ch == 'x');
299
300 if (u->vkey) {
301 //printf("URL = %s\n",URL::encode(u->vkey->getText()).c_str());
302 buf.appendFormatted("\\swordfootnote{%s}{%s}{%s}{%s}{%s}{",
303
304 footnoteNumber.c_str(),
305 u->version.c_str(),
306 u->vkey->getText(),
307 tag.getAttribute("type"),
308 (renderNoteNumbers ? noteName.c_str() : ""));
309 if (u->module) {
310 outText( u->module->renderText(footnoteBody).c_str(), buf, u);
311 }
312 }
313 else {
314 buf.appendFormatted("\\swordfootnote{%s}{%s}{%s}{%s}{%s}{",
315 footnoteNumber.c_str(),
316 u->version.c_str(),
317 u->key->getText(),
318 tag.getAttribute("type"),
319 (renderNoteNumbers ? noteName.c_str() : ""));
320 if (u->module) {
321 outText( u->module->renderText(footnoteBody).c_str(), buf, u);
322 }
323 }
324 }
325 }
326 u->suspendTextPassThru = (++u->suspendLevel);
327 }
328 if (tag.isEndTag()) {
329 u->suspendTextPassThru = (--u->suspendLevel);
330 u->inXRefNote = false;
331 u->lastSuspendSegment = ""; // fix/work-around for nasb divineName in note bug
332 outText("}", buf, u);
333 }
334 }
335
336 // <p> paragraph and <lg> linegroup tags
337 else if (!strcmp(tag.getName(), "p") || !strcmp(tag.getName(), "lg")) {
338 if ((!tag.isEndTag()) && (!tag.isEmpty())) { // non-empty start tag
339 u->outputNewline(buf);
340 }
341 else if (tag.isEndTag()) { // end tag
342 u->outputNewline(buf);
343 }
344 else { // empty paragraph break marker
345 u->outputNewline(buf);
346 }
347 }
348
349 // Milestoned paragraphs, created by osis2mod
350 // <div type="paragraph" sID.../>
351 // <div type="paragraph" eID.../>
352 else if (tag.isEmpty() && !strcmp(tag.getName(), "div") && tag.getAttribute("type") && (!strcmp(tag.getAttribute("type"), "x-p") || !strcmp(tag.getAttribute("type"), "paragraph"))) {
353 // <div type="paragraph" sID... />
354 if (tag.getAttribute("sID")) { // non-empty start tag
355 u->outputNewline(buf);
356 }
357 // <div type="paragraph" eID... />
358 else if (tag.getAttribute("eID")) {
359 u->outputNewline(buf);
360 }
361 }
362
363 // <reference> tag
364 else if (!strcmp(tag.getName(), "reference")) {
365 if (!u->inXRefNote) { // only show these if we're not in an xref note
366 if (!tag.isEndTag()) {
367 SWBuf target;
368 SWBuf work;
369 SWBuf ref;
370 bool is_scripRef = false;
371
372 target = tag.getAttribute("osisRef");
373 const char* the_ref = strchr(target, ':');
374
375 if(!the_ref) {
376 // No work
377 ref = target;
378 is_scripRef = true;
379 }
380 else {
381 // Compensate for starting :
382 ref = the_ref + 1;
383
384 int size = (int)(target.size() - ref.size() - 1);
385 work.setSize(size);
386 strncpy(work.getRawData(), target, size);
387
388 // For Bible:Gen.3.15 or Bible.vulgate:Gen.3.15
389 if(!strncmp(work, "Bible", 5))
390 is_scripRef = true;
391 }
392
393 if(is_scripRef)
394 {
395 buf.appendFormatted("\\swordxref{%s}{",
396 ref.c_str()
397 // (work.size()) ? URL::encode(work.c_str()).c_str() : "")
398 );
399 }
400 else
401 {
402 // Dictionary link, or something
403 buf.appendFormatted("\\sworddiclink{%s}{%s}{", // work, entry
404 work.c_str(),
405 ref.c_str()
406 );
407 }
408 }
409 else {
410 outText("}", buf, u);
411 }
412 }
413 }
414
415 // <l> poetry, etc
416 else if (!strcmp(tag.getName(), "l")) {
417 // start line marker
418 if (tag.getAttribute("sID") || (!tag.isEndTag() && !tag.isEmpty())) {
419 // nested lines plus if the line itself has an x-indent type attribute value
420 outText("\\swordpoetryline{", buf, u);
421 u->lineStack->push(tag.toString());
422 }
423 // end line marker
424 else if (tag.getAttribute("eID") || tag.isEndTag()) {
425 outText("}", buf, u);
426 u->outputNewline(buf);
427 if (u->lineStack->size()) u->lineStack->pop();
428 }
429 // <l/> without eID or sID
430 // Note: this is improper osis. This should be <lb/>
431 else if (tag.isEmpty() && !tag.getAttribute("sID")) {
432 u->outputNewline(buf);
433 }
434 }
435
436 // <lb.../>
437 else if (!strcmp(tag.getName(), "lb") && (!tag.getAttribute("type") || strcmp(tag.getAttribute("type"), "x-optional"))) {
438 u->outputNewline(buf);
439 }
440 // <milestone type="line"/>
441 // <milestone type="x-p"/>
442 // <milestone type="cQuote" marker="x"/>
443 else if ((!strcmp(tag.getName(), "milestone")) && (tag.getAttribute("type"))) {
444 if (!strcmp(tag.getAttribute("type"), "line")) {
445 u->outputNewline(buf);
446 if (tag.getAttribute("subType") && !strcmp(tag.getAttribute("subType"), "x-PM")) {
447 u->outputNewline(buf);
448 }
449 }
450 else if (!strcmp(tag.getAttribute("type"),"x-p")) {
451 if (tag.getAttribute("marker"))
452 outText(tag.getAttribute("marker"), buf, u);
453 else outText("<!p>", buf, u);
454 }
455 else if (!strcmp(tag.getAttribute("type"), "cQuote")) {
456 const char *tmp = tag.getAttribute("marker");
457 bool hasMark = tmp;
458 SWBuf mark = tmp;
459 tmp = tag.getAttribute("level");
460 int level = (tmp) ? atoi(tmp) : 1;
461
462 // first check to see if we've been given an explicit mark
463 if (hasMark)
464 outText(mark, buf, u);
465 // finally, alternate " and ', if config says we should supply a mark
466 else if (u->osisQToTick)
467 outText((level % 2) ? '\"' : '\'', buf, u);
468 }
469 }
470
471 // <title>
472
473 else if (!strcmp(tag.getName(), "title")) {
474 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
475 const char *tmp = tag.getAttribute("type");
476 bool hasType = tmp;
477 SWBuf type = tmp;
478
479 outText("\n\\swordtitle{", buf, u);
480 outText(u->divLevel, buf, u);
481 outText("}{", buf, u);
482
483 if (hasType) outText(type, buf, u);
484 else outText("", buf, u);
485
486 outText("}{", buf, u);
487 }
488 else if (tag.isEndTag()) {
489 outText( "}", buf, u);
490 ++u->consecutiveNewlines;
491 u->supressAdjacentWhitespace = true;
492 }
493 }
494
495 // <list>
496 else if (!strcmp(tag.getName(), "list")) {
497 if((!tag.isEndTag()) && (!tag.isEmpty())) {
498 outText("\n\\begin{itemize}", buf, u);
499 }
500 else if (tag.isEndTag()) {
501 outText("\n\\end{itemize}", buf, u);
502 ++u->consecutiveNewlines;
503 u->supressAdjacentWhitespace = true;
504 }
505 }
506
507 // <item>
508 else if (!strcmp(tag.getName(), "item")) {
509 if((!tag.isEndTag()) && (!tag.isEmpty())) {
510 outText("\n\\item ", buf, u);
511 }
512 else if (tag.isEndTag()) {
513 ++u->consecutiveNewlines;
514 u->supressAdjacentWhitespace = true;
515 }
516 }
517 // <catchWord> & <rdg> tags (italicize)
518 else if (!strcmp(tag.getName(), "rdg") || !strcmp(tag.getName(), "catchWord")) {
519 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
520 outText("\\emph{", buf, u);
521 }
522 else if (tag.isEndTag()) {
523 outText("}", buf, u);
524 }
525 }
526
527 // divineName
528 else if (!strcmp(tag.getName(), "divineName")) {
529 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
530 outText( "\\sworddivinename{", buf, u);
531 u->suspendTextPassThru = (++u->suspendLevel);
532 }
533 else if (tag.isEndTag()) {
534 SWBuf lastText = u->lastSuspendSegment.c_str();
535 u->suspendTextPassThru = (--u->suspendLevel);
536 if (lastText.size()) {
537 scratch.setFormatted("%s}", lastText.c_str());
538 outText(scratch.c_str(), buf, u);
539 }
540 }
541 }
542
543 // <hi> text highlighting
544 else if (!strcmp(tag.getName(), "hi")) {
545 SWBuf type = tag.getAttribute("type");
546
547 // handle tei rend attribute if type doesn't exist
548 if (!type.length()) type = tag.getAttribute("rend");
549
550 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
551 if (type == "bold" || type == "b" || type == "x-b") {
552 outText("\\textbold{", buf, u);
553 }
554
555 // there is no officially supported OSIS overline attribute,
556 // thus either TEI overline or OSIS x-overline would be best,
557 // but we have used "ol" in the past, as well. Once a valid
558 // OSIS overline attribute is made available, these should all
559 // eventually be deprecated and never documented that they are supported.
560 else if (type == "ol" || type == "overline" || type == "x-overline") {
561 outText("\\textoverline{", buf, u);
562 }
563
564 else if (type == "super") {
565 outText("\\textsuperscript{", buf, u);
566 }
567 else if (type == "sub") {
568 outText("\\textsubscript{", buf, u);
569 }
570 else { // all other types
571 outText("\\emph {", buf, u);
572 }
573 u->hiStack->push(tag.toString());
574 }
575 else if (tag.isEndTag()) {
576 outText("}", buf, u);
577 }
578 }
579
580 // <q> quote
581 // Rules for a quote element:
582 // If the tag is empty with an sID or an eID then use whatever it specifies for quoting.
583 // Note: empty elements without sID or eID are ignored.
584 // If the tag is <q> then use it's specifications and push it onto a stack for </q>
585 // If the tag is </q> then use the pushed <q> for specification
586 // If there is a marker attribute, possibly empty, this overrides osisQToTick.
587 // If osisQToTick, then output the marker, using level to determine the type of mark.
588 else if (!strcmp(tag.getName(), "q")) {
589 SWBuf type = tag.getAttribute("type");
590 SWBuf who = tag.getAttribute("who");
591 const char *tmp = tag.getAttribute("level");
592 int level = (tmp) ? atoi(tmp) : 1;
593 tmp = tag.getAttribute("marker");
594 bool hasMark = tmp;
595 SWBuf mark = tmp;
596
597 // open <q> or <q sID... />
598 if ((!tag.isEmpty() && !tag.isEndTag()) || (tag.isEmpty() && tag.getAttribute("sID"))) {
599 // if <q> then remember it for the </q>
600 if (!tag.isEmpty()) {
601 u->quoteStack->push(tag.toString());
602 }
603
604 // Do this first so quote marks are included as WoC
605 if (who == "Jesus")
606 outText(u->wordsOfChristStart, buf, u);
607
608 // first check to see if we've been given an explicit mark
609 if (hasMark)
610 outText(mark, buf, u);
611 //alternate " and '
612 else if (u->osisQToTick)
613 outText((level % 2) ? '\"' : '\'', buf, u);
614 }
615 // close </q> or <q eID... />
616 else if ((tag.isEndTag()) || (tag.isEmpty() && tag.getAttribute("eID"))) {
617 // if it is </q> then pop the stack for the attributes
618 if (tag.isEndTag() && !u->quoteStack->empty()) {
619 XMLTag qTag(u->quoteStack->top());
620 if (u->quoteStack->size()) u->quoteStack->pop();
621
622 type = qTag.getAttribute("type");
623 who = qTag.getAttribute("who");
624 tmp = qTag.getAttribute("level");
625 level = (tmp) ? atoi(tmp) : 1;
626 tmp = qTag.getAttribute("marker");
627 hasMark = tmp;
628 mark = tmp;
629 }
630
631 // first check to see if we've been given an explicit mark
632 if (hasMark)
633 outText(mark, buf, u);
634 // finally, alternate " and ', if config says we should supply a mark
635 else if (u->osisQToTick)
636 outText((level % 2) ? '\"' : '\'', buf, u);
637
638 // Do this last so quote marks are included as WoC
639 if (who == "Jesus")
640 outText(u->wordsOfChristEnd, buf, u);
641 }
642 }
643
644 // <transChange>
645 else if (!strcmp(tag.getName(), "transChange")) {
646 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
647 SWBuf type = tag.getAttribute("type");
648 u->lastTransChange = type;
649
650 // just do all transChange tags this way for now
651 if ((type == "added") || (type == "supplied"))
652 outText("\\swordtranschange{supplied}{", buf, u);
653 else if (type == "tenseChange")
654 outText( "\\swordtranschange{tense}{", buf, u);
655 }
656 else if (tag.isEndTag()) {
657 outText("}", buf, u);
658 }
659 else { // empty transChange marker?
660 }
661 }
662
663 // image
664 else if (!strcmp(tag.getName(), "figure")) {
665 const char *src = tag.getAttribute("src");
666 if (src) { // assert we have a src attribute
667 SWBuf filepath;
668 if (userData->module) {
669 filepath = userData->module->getConfigEntry("AbsoluteDataPath");
670 if ((filepath.size()) && (filepath[filepath.size()-1] != '/') && (src[0] != '/'))
671 filepath += '/';
672 }
673 filepath += src;
674
675 outText("\\figure{", buf, u);
676 outText("\\includegraphics{", buf, u);
677 outText(filepath.c_str(), buf, u);
678 outText("}}", buf, u);
679
680 }
681 }
682
683 // ok to leave these in
684 else if (!strcmp(tag.getName(), "div")) {
685 SWBuf type = tag.getAttribute("type");
686 if (type == "module") {
687 u->divLevel = type;
688 outText("\n", buf, u);
689 }
690 else if (type == "testament") {
691 u->divLevel = type;
692 outText("\n", buf, u);
693 }
694 else if (type == "bookGroup") {
695 u->divLevel = type;
696 outText("\n", buf, u);
697 }
698 else if (type == "book") {
699 u->divLevel = type;
700 outText("\n", buf, u);
701 }
702 else if (type == "majorSection") {
703 u->divLevel = type;
704 outText("\n", buf, u);
705 }
706 else if (type == "section") {
707 u->divLevel = type;
708 outText("\n", buf, u);
709 }
710 else if (type == "paragraph") {
711 u->divLevel = type;
712 outText("\n", buf, u);
713 }
714 }
715 else if (!strcmp(tag.getName(), "span")) {
716 outText( "", buf, u);
717 }
718 else if (!strcmp(tag.getName(), "br")) {
719 outText( "\\", buf, u);
720 }
721 else if (!strcmp(tag.getName(), "table")) {
722 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
723 outText( "\n\\begin{tabular}", buf, u);
724 }
725 else if (tag.isEndTag()) {
726 outText( "\n\\end{tabular}", buf, u);
727 ++u->consecutiveNewlines;
728 u->supressAdjacentWhitespace = true;
729 }
730
731 }
732 else if (!strcmp(tag.getName(), "row")) {
733 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
734 outText( "\n", buf, u);
735 u->firstCell = true;
736 }
737 else if (tag.isEndTag()) {
738 outText( "//", buf, u);
739 u->firstCell = false;
740 }
741
742 }
743 else if (!strcmp(tag.getName(), "cell")) {
744 if ((!tag.isEndTag()) && (!tag.isEmpty())) {
745 if (u->firstCell == false) {
746 outText( " & ", buf, u);
747 }
748 else {
749 u->firstCell = false;
750 }
751 }
752 else if (tag.isEndTag()) {
753 outText( "", buf, u);
754 }
755 }
756 else {
757 if (!u->supressAdjacentWhitespace) u->consecutiveNewlines = 0;
758 return false; // we still didn't handle token
759 }
760 }
761 if (!u->supressAdjacentWhitespace) u->consecutiveNewlines = 0;
762 return true;
763 }
764
765
766 SWORD_NAMESPACE_END
767