1 /***************************************************************************
2 * This file is part of KWorship. *
3 * Copyright 2008 James Hogan <james@albanarts.com> *
5 * KWorship is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 2 of the License, or *
8 * (at your option) any later version. *
10 * KWorship is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with KWorship. If not, write to the Free Software Foundation, *
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ***************************************************************************/
21 * @file KwBiblePassage.cpp
22 * @brief A passage of rendered bible text.
23 * @author James Hogan <james@albanarts.com>
26 #include "KwBiblePassage.h"
30 #include <QDomDocument>
31 #include <QDomElement>
34 * Constructors + destructor
37 /// Default constructor.
38 KwBiblePassage::KwBiblePassage()
41 , m_rightToLeft(false)
49 KwBiblePassage::~KwBiblePassage()
57 /// Import the passage in a DOM.
58 void KwBiblePassage::importFromDom(const QDomElement
& element
)
61 QDomElement managerElem
= element
.firstChildElement("manager");
62 if (!managerElem
.isNull())
64 m_managerId
= managerElem
.text();
68 m_managerId
= QString();
72 QDomElement moduleElem
= element
.firstChildElement("module");
73 if (!moduleElem
.isNull())
75 m_moduleId
= moduleElem
.text();
79 m_moduleId
= QString();
82 // Text direction element
83 QDomElement directionElem
= element
.firstChildElement("direction");
84 if (!directionElem
.isNull())
86 m_rightToLeft
= (directionElem
.text().toLower() == "rtl");
90 m_rightToLeft
= false;
96 QDomElement booksElem
= element
.firstChildElement("books");
97 if (!booksElem
.isNull())
99 int firstBook
= booksElem
.attribute("first").toInt(&success
);
104 int numBooks
= booksElem
.attribute("count").toInt(&success
);
109 initBooks(firstBook
, numBooks
);
112 QDomNodeList books
= booksElem
.elementsByTagName("book");
113 for (int book
= 0; book
< books
.count(); ++book
)
115 QDomElement bookElem
= books
.at(book
).toElement();
117 int bookId
= bookElem
.attribute("num").toInt(&success
);
118 if (success
&& bookId
>= m_firstBook
&& bookId
< m_firstBook
+m_numBooks
)
121 QDomElement chaptersElem
= bookElem
.firstChildElement("chapters");
122 if (!chaptersElem
.isNull())
124 int firstChapter
= chaptersElem
.attribute("first").toInt(&success
);
129 int numChapters
= chaptersElem
.attribute("count").toInt(&success
);
134 initBook(bookId
, bookElem
.attribute("name", "unknown"), firstChapter
, numChapters
);
137 QDomNodeList chapters
= chaptersElem
.elementsByTagName("chapter");
138 for (int chapter
= 0; chapter
< chapters
.count(); ++chapter
)
140 QDomElement chapterElem
= chapters
.at(chapter
).toElement();
142 int chapterId
= chapterElem
.attribute("num").toInt(&success
);
143 if (success
&& chapterId
>= firstChapter
&& chapterId
< firstChapter
+numChapters
)
146 QDomElement versesElem
= chapterElem
.firstChildElement("verses");
147 if (!versesElem
.isNull())
149 int firstVerse
= versesElem
.attribute("first").toInt(&success
);
154 int numVerses
= versesElem
.attribute("count").toInt(&success
);
159 initChapter(bookId
, chapterId
, firstVerse
, numVerses
);
162 QDomNodeList verses
= versesElem
.elementsByTagName("verse");
163 for (int verse
= 0; verse
< verses
.count(); ++verse
)
165 QDomElement verseElem
= verses
.at(verse
).toElement();
167 int verseId
= verseElem
.attribute("num").toInt(&success
);
168 if (success
&& verseId
>= firstVerse
&& verseId
< firstVerse
+numVerses
)
170 QString headings
, content
;
173 QDomElement headingsElem
= verseElem
.firstChildElement("headings");
174 if (!headingsElem
.isNull())
176 headings
= headingsElem
.text();
180 QDomElement contentElem
= verseElem
.firstChildElement("content");
181 if (!contentElem
.isNull())
183 content
= contentElem
.text();
186 initVerse(bookId
, chapterId
, verseId
, headings
, content
);
198 /// Export the passage using DOM.
199 void KwBiblePassage::exportToDom(QDomDocument
& document
, QDomElement
& element
) const
201 // Manager id element
202 QDomElement managerElem
= document
.createElement("manager");
203 element
.appendChild(managerElem
);
204 managerElem
.appendChild(document
.createTextNode(m_managerId
));
207 QDomElement moduleElem
= document
.createElement("module");
208 element
.appendChild(moduleElem
);
209 moduleElem
.appendChild(document
.createTextNode(m_moduleId
));
211 // Text direction element
212 QDomElement directionElem
= document
.createElement("direction");
213 element
.appendChild(directionElem
);
214 directionElem
.appendChild(document
.createTextNode(m_rightToLeft
? "rtl" : "ltr"));
217 QDomElement booksElem
= document
.createElement("books");
218 element
.appendChild(booksElem
);
219 booksElem
.setAttribute("first", m_firstBook
);
220 booksElem
.setAttribute("count", m_numBooks
);
222 Book
* book
= m_books
;
223 for (int bookInd
= 0; bookInd
< m_numBooks
; ++bookInd
)
226 QDomElement bookElem
= document
.createElement("book");
227 booksElem
.appendChild(bookElem
);
228 bookElem
.setAttribute("num", m_firstBook
+ bookInd
);
229 bookElem
.setAttribute("name", book
->name
);
232 QDomElement chaptersElem
= document
.createElement("chapters");
233 bookElem
.appendChild(chaptersElem
);
234 chaptersElem
.setAttribute("first", book
->firstChapter
);
235 chaptersElem
.setAttribute("count", book
->numChapters
);
237 Chapter
* chapter
= book
->chapters
;
238 for (int chapterInd
= 0; chapterInd
< book
->numChapters
; ++chapterInd
)
241 QDomElement chapterElem
= document
.createElement("chapter");
242 chaptersElem
.appendChild(chapterElem
);
243 chapterElem
.setAttribute("num", book
->firstChapter
+ chapterInd
);
246 QDomElement versesElem
= document
.createElement("verses");
247 chapterElem
.appendChild(versesElem
);
248 versesElem
.setAttribute("first", chapter
->firstVerse
);
249 versesElem
.setAttribute("count", chapter
->numVerses
);
251 Verse
* verse
= chapter
->verses
;
252 for (int verseInd
= 0; verseInd
< chapter
->numVerses
; ++verseInd
)
255 QDomElement verseElem
= document
.createElement("verse");
256 versesElem
.appendChild(verseElem
);
257 verseElem
.setAttribute("num", chapter
->firstVerse
+ verseInd
);
260 if (!verse
->headings
.isEmpty())
262 QDomElement headingsElem
= document
.createElement("headings");
263 verseElem
.appendChild(headingsElem
);
264 headingsElem
.appendChild(document
.createTextNode(verse
->headings
));
268 if (!verse
->content
.isEmpty())
270 QDomElement contentElem
= document
.createElement("content");
271 verseElem
.appendChild(contentElem
);
272 contentElem
.appendChild(document
.createTextNode(verse
->content
));
289 /// Clear the passage.
290 void KwBiblePassage::clearBooks()
292 // Deep cleanup operation
295 int lastBook
= m_firstBook
+ m_numBooks
;
296 for (int bookNumber
= m_firstBook
; bookNumber
< lastBook
; ++bookNumber
)
298 clearChapters(bookNumber
);
305 /// Clear the chapters in a book.
306 void KwBiblePassage::clearChapters(int bookNumber
)
308 Q_ASSERT(0 != m_books
&& bookNumber
>= m_firstBook
&& bookNumber
< m_firstBook
+m_numBooks
);
309 Book
* book
= &m_books
[bookNumber
-m_firstBook
];
310 if (0 != book
->chapters
)
312 int maxChapter
= book
->firstChapter
+ book
->numChapters
;
313 for (int chapterNumber
= book
->firstChapter
; chapterNumber
< maxChapter
; ++chapterNumber
)
315 clearVerses(bookNumber
, chapterNumber
);
317 delete [] book
->chapters
;
322 /// Clear the verses in a chapter.
323 void KwBiblePassage::clearVerses(int bookNumber
, int chapterNumber
)
325 bookNumber
-= m_firstBook
;
326 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
327 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
328 Q_ASSERT(0 != m_books
[bookNumber
].chapters
&& chapterNumber
>= 0 && chapterNumber
< m_books
[bookNumber
].numChapters
);
329 Chapter
* chapter
= &m_books
[bookNumber
].chapters
[chapterNumber
];
330 if (0 != chapter
->verses
)
332 delete [] chapter
->verses
;
337 /// Set the source module.
338 void KwBiblePassage::setSource(const QString
& managerId
, const QString
& moduleId
, bool rightToLeft
)
340 m_managerId
= managerId
;
341 m_moduleId
= moduleId
;
342 m_rightToLeft
= rightToLeft
;
345 /// Initialise the books in the passage.
346 void KwBiblePassage::initBooks(int firstBook
, int numBooks
)
349 m_firstBook
= firstBook
;
350 m_numBooks
= numBooks
;
353 m_books
= new Book
[numBooks
];
354 for (int i
= 0; i
< numBooks
; ++i
)
356 m_books
[i
].firstChapter
= -1;
357 m_books
[i
].numChapters
= 0;
358 m_books
[i
].chapters
= 0;
363 /// Initialise a book in the passage.
364 void KwBiblePassage::initBook(int bookNumber
,
365 const QString
& name
, int firstChapter
, int numChapters
)
367 clearChapters(bookNumber
);
368 bookNumber
-= m_firstBook
;
369 Book
* book
= &m_books
[bookNumber
];
371 book
->firstChapter
= firstChapter
;
372 book
->numChapters
= numChapters
;
375 book
->chapters
= new Chapter
[numChapters
];
376 for (int i
= 0; i
< numChapters
; ++i
)
378 book
->chapters
[i
].firstVerse
= -1;
379 book
->chapters
[i
].numVerses
= 0;
380 book
->chapters
[i
].verses
= 0;
385 /// Initialise a chapter in the passage.
386 void KwBiblePassage::initChapter(int bookNumber
, int chapterNumber
,
387 int firstVerse
, int numVerses
)
389 clearVerses(bookNumber
, chapterNumber
);
390 bookNumber
-= m_firstBook
;
391 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
392 Chapter
* chapter
= &m_books
[bookNumber
].chapters
[chapterNumber
];
393 chapter
->firstVerse
= firstVerse
;
394 chapter
->numVerses
= numVerses
;
397 chapter
->verses
= new Verse
[numVerses
];
401 /// Initialise a verse in the passage.
402 void KwBiblePassage::initVerse(int bookNumber
, int chapterNumber
, int verseNumber
,
403 const QString
& headings
, const QString
& content
)
405 bookNumber
-= m_firstBook
;
406 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
407 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
408 Q_ASSERT(0 != m_books
[bookNumber
].chapters
&& chapterNumber
>= 0 && chapterNumber
< m_books
[bookNumber
].numChapters
);
409 verseNumber
-= m_books
[bookNumber
].chapters
[chapterNumber
].firstVerse
;
410 Q_ASSERT(0 != m_books
[bookNumber
].chapters
[chapterNumber
].verses
&& verseNumber
>= 0 && verseNumber
< m_books
[bookNumber
].chapters
[chapterNumber
].numVerses
);
412 Verse
* verse
= &m_books
[bookNumber
].chapters
[chapterNumber
].verses
[verseNumber
];
413 verse
->headings
= headings
;
414 verse
->content
= content
;
421 /// Find whether the passage is empty.
422 bool KwBiblePassage::isEmpty() const
424 return (m_books
== 0);
427 /// Get textual key of this passage.
428 QString
KwBiblePassage::textualKey() const
433 return i18nc("empty bible passage", "empty");
435 Q_ASSERT(m_books
[0].numChapters
> 0);
436 Q_ASSERT(m_books
[0].chapters
[0].numVerses
> 0);
439 * @bug 2008-dec-22: There is a bug in Qt's handling of right-to-left scripts somewhere
440 * in QString::arg(QString) - in this case from m_books[i].name.
441 * It results in the substitutions taking place in the wrong order.
442 * There also appear to be similar problems rendering even if the
443 * substitutions appear to take place correctly.
444 * I haven't managed to find a satisfactory work-around for this problem
445 * so I'm ignoring it for the time being.
448 QString result
= i18nc("bible index (book, chapter:verse)", "%1 %2",
450 i18nc("bible index (chapter:verse)", "%1:%2",
451 m_books
[0].firstChapter
,
452 m_books
[0].chapters
[0].firstVerse
));
453 Book
* lastBook
= &m_books
[m_numBooks
- 1];
454 Chapter
* lastChapter
= &lastBook
->chapters
[lastBook
->numChapters
- 1];
455 // Overlapping multiple books
458 result
= i18nc("bible passage range (book chapter:verse, book chapter:verse)", "%1 - %2", result
,
459 i18nc("bible index (book, chapter:verse)", "%1 %2",
461 i18nc("bible index (chapter:verse)", "%1:%2",
462 lastBook
->firstChapter
+ lastBook
->numChapters
- 1,
463 lastChapter
->firstVerse
+ lastChapter
->numVerses
- 1)));
465 // Overlapping multiple chapters
466 else if (lastBook
->numChapters
> 1)
468 result
= i18nc("bible passage range (book chapter:verse, chapter:verse)", "%1 - %2", result
,
469 i18nc("bible index (chapter:verse)", "%1:%2",
470 lastBook
->firstChapter
+ lastBook
->numChapters
- 1,
471 lastChapter
->firstVerse
+ lastChapter
->numVerses
- 1));
473 // Overlapping multiple verses
474 else if (lastChapter
->numVerses
> 1)
476 result
= i18nc("bible passage range (book chapter:verse, verse)", "%1-%2", result
,
477 lastChapter
->firstVerse
+ lastChapter
->numVerses
- 1);
480 // Module name is quite important too
481 return i18nc("bible index (range, version)", "%1 (%2)", result
, m_moduleId
);
484 /// Find whether the text is right to left.
485 bool KwBiblePassage::isRightToLeft() const
487 return m_rightToLeft
;
490 /// Get the first book number in the passage.
491 int KwBiblePassage::firstBookNumber() const
496 /// Get the last book number in the passage.
497 int KwBiblePassage::lastBookNumber() const
499 return m_firstBook
+ m_numBooks
- 1;
502 /// Get the first chapter number in a book of the passage.
503 int KwBiblePassage::firstChapterNumber(int bookNumber
) const
505 bookNumber
-= m_firstBook
;
506 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
508 Book
* book
= &m_books
[bookNumber
];
509 return book
->firstChapter
;
512 /// Get the last chapter number in a book of the passage.
513 int KwBiblePassage::lastChapterNumber(int bookNumber
) const
515 bookNumber
-= m_firstBook
;
516 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
518 Book
* book
= &m_books
[bookNumber
];
519 return book
->firstChapter
+ book
->numChapters
- 1;
522 /// Get the first verse number in a chapter of the passage.
523 int KwBiblePassage::firstVerseNumber(int bookNumber
, int chapterNumber
) const
525 bookNumber
-= m_firstBook
;
526 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
527 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
528 Q_ASSERT(0 != m_books
[bookNumber
].chapters
&& chapterNumber
>= 0 && chapterNumber
< m_books
[bookNumber
].numChapters
);
530 Chapter
* chapter
= &m_books
[bookNumber
].chapters
[chapterNumber
];
531 return chapter
->firstVerse
;
534 /// Get the last verse number in a chapter of the passage.
535 int KwBiblePassage::lastVerseNumber(int bookNumber
, int chapterNumber
) const
537 bookNumber
-= m_firstBook
;
538 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
539 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
540 Q_ASSERT(0 != m_books
[bookNumber
].chapters
&& chapterNumber
>= 0 && chapterNumber
< m_books
[bookNumber
].numChapters
);
542 Chapter
* chapter
= &m_books
[bookNumber
].chapters
[chapterNumber
];
543 return chapter
->firstVerse
+ chapter
->numVerses
- 1;
546 /// Get the headings before a verse.
547 QString
KwBiblePassage::verseHeadings(int bookNumber
, int chapterNumber
, int verseNumber
, bool plain
) const
549 bookNumber
-= m_firstBook
;
550 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
551 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
552 Q_ASSERT(0 != m_books
[bookNumber
].chapters
&& chapterNumber
>= 0 && chapterNumber
< m_books
[bookNumber
].numChapters
);
553 verseNumber
-= m_books
[bookNumber
].chapters
[chapterNumber
].firstVerse
;
554 Q_ASSERT(0 != m_books
[bookNumber
].chapters
[chapterNumber
].verses
&& verseNumber
>= 0 && verseNumber
< m_books
[bookNumber
].chapters
[chapterNumber
].numVerses
);
556 Verse
* verse
= &m_books
[bookNumber
].chapters
[chapterNumber
].verses
[verseNumber
];
557 QString result
= verse
->headings
;
560 result
.replace( QRegExp("<[^>]*>"), "" );
565 /// Get the content of a verse.
566 QString
KwBiblePassage::verseContent(int bookNumber
, int chapterNumber
, int verseNumber
, bool plain
) const
568 bookNumber
-= m_firstBook
;
569 Q_ASSERT(0 != m_books
&& bookNumber
>= 0 && bookNumber
< m_numBooks
);
570 chapterNumber
-= m_books
[bookNumber
].firstChapter
;
571 Q_ASSERT(0 != m_books
[bookNumber
].chapters
&& chapterNumber
>= 0 && chapterNumber
< m_books
[bookNumber
].numChapters
);
572 verseNumber
-= m_books
[bookNumber
].chapters
[chapterNumber
].firstVerse
;
573 Q_ASSERT(0 != m_books
[bookNumber
].chapters
[chapterNumber
].verses
&& verseNumber
>= 0 && verseNumber
< m_books
[bookNumber
].chapters
[chapterNumber
].numVerses
);
575 Verse
* verse
= &m_books
[bookNumber
].chapters
[chapterNumber
].verses
[verseNumber
];
576 QString result
= verse
->content
;
579 result
.replace( QRegExp("<[^>]*>"), "" );
588 /// Get the entire passaage rendered as HTML.
589 QString
KwBiblePassage::renderedText() const
592 result
+= QString("<div dir=\"%1\">").arg(m_rightToLeft
? "rtl" : "ltr");
594 Book
* book
= m_books
;
595 for (int bookIndex
= 0; bookIndex
< m_numBooks
; ++bookIndex
)
597 Chapter
* chapter
= book
->chapters
;
598 for (int chapterIndex
= 0; chapterIndex
< book
->numChapters
; ++chapterIndex
)
600 Verse
* verse
= chapter
->verses
;
601 for (int verseIndex
= 0; verseIndex
< chapter
->numVerses
; ++verseIndex
)
603 if (!verse
->headings
.isEmpty())
605 result
+= verse
->headings
;
607 if (!verse
->content
.isEmpty())
609 result
+= QString("<sup>%1</sup>").arg(chapter
->firstVerse
+ verseIndex
);
610 result
+= verse
->content
;