Moved responsibility of getting bible passage out of KwBiblePassage into KwBibleModul...
[kworship.git] / kworship / bible / KwBibleModuleBibleGateway.cpp
blobfd15417d56af2bc4a4a17e1c812d30866bbc6410
1 /***************************************************************************
2 * This file is part of KWorship. *
3 * Copyright 2008 James Hogan <james@albanarts.com> *
4 * *
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. *
9 * *
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. *
14 * *
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 ***************************************************************************/
20 /**
21 * @file KwBibleModuleBibleGateway.cpp
22 * @brief A BibleGateway bible module.
23 * @author James Hogan <james@albanarts.com>
26 #include "KwBibleModuleBibleGateway.h"
28 #include <KIO/NetAccess>
29 #include <KMessageBox>
30 #include <KLocale>
31 #include <dom/html_document.h>
32 #include <dom/html_element.h>
33 #include <dom/html_block.h>
35 #include <QStringList>
36 #include <QFile>
39 * Constructors + destructor
42 /// Default constructor.
43 KwBibleModuleBibleGateway::KwBibleModuleBibleGateway(int id)
44 : KwBibleModule()
46 KUrl url(QString("http://www.biblegateway.com/versions/index.php?action=getVersionInfo&vid=%1").arg(id));
48 QString tmpFile;
49 if (KIO::NetAccess::download(url, tmpFile, 0))
51 QFile file(tmpFile);
52 if (file.open(QFile::ReadOnly | QFile::Text))
54 QByteArray rawPage = file.readAll();
55 file.close();
57 DOM::HTMLDocument doc;
58 doc.loadXML(QString::fromUtf8(rawPage));
59 DOM::Element bookList = doc.getElementById("booklist");
60 bool tableFound = false;
61 if (!bookList.isNull())
63 // Get the next table
64 DOM::Node sibling = bookList.nextSibling();
65 while (!sibling.isNull() && sibling.nodeType() != DOM::Node::ELEMENT_NODE)
67 sibling = sibling.nextSibling();
69 DOM::Element tableElement(sibling);
70 if (!tableElement.isNull() && tableElement.tagName() == "table")
72 tableFound = true;
73 // Each row except header is a book
74 DOM::NodeList rows = tableElement.getElementsByTagName("tr");
75 for (unsigned int row = 0; row < rows.length(); ++row)
77 DOM::NodeList cells = DOM::Element(rows.item(row)).getElementsByTagName("td");
78 if (cells.length() == 2)
80 // First cell is the name
81 m_bookList.push_back(Book());
82 Book* book = &m_bookList[m_bookList.size()-1];
83 book->name = DOM::HTMLElement(cells.item(0)).innerText().string();
84 // Second cell is the chapter links
85 DOM::NodeList chapterLinks = DOM::Element(cells.item(1)).getElementsByTagName("a");
86 for (unsigned int chapter = 0; chapter < chapterLinks.length(); ++chapter)
88 // Check the chapter number is right
89 DOM::HTMLElement link = chapterLinks.item(chapter);
90 bool numeric;
91 int check = link.innerText().string().toInt(&numeric);
92 if (!numeric || check != (int)chapter+1)
94 KMessageBox::error(0, i18n("Error parsing webpage: %1", i18n("Non sequential chapter list in book '%1'", book->name)));
95 break;
97 // Get the link
98 book->chapters.push_back(Chapter());
99 Chapter* chapter = &book->chapters[book->chapters.size()-1];
100 chapter->url = "http://www.biblegateway.com" + link.getAttribute("href").string();
101 chapter->fetched = false;
107 if (!tableFound)
109 // Book list reference node not found
110 KMessageBox::error(0, i18n("Error parsing webpage: %1", i18n("Book list table not found")));
114 KIO::NetAccess::removeTempFile(tmpFile);
116 else
118 KMessageBox::error(0, KIO::NetAccess::lastErrorString());
122 /// Destructor.
123 KwBibleModuleBibleGateway::~KwBibleModuleBibleGateway()
128 * Main interface
131 QString KwBibleModuleBibleGateway::name()
133 return QString();
136 QString KwBibleModuleBibleGateway::description()
138 return QString();
141 int KwBibleModuleBibleGateway::numChapters(int book)
143 if (book >= 0 && book < m_bookList.size())
145 return m_bookList[book].chapters.size();
147 return 0;
150 int KwBibleModuleBibleGateway::numVerses(int book, int chapter)
152 Chapter* chap = fetchChapter(book, chapter);
153 if (0 != chap)
155 return chap->verses.size();
157 else
159 return 0;
163 bool KwBibleModuleBibleGateway::fillPassage(const Key& key, KwBiblePassage* outPassage) const
165 return false;
168 QString KwBibleModuleBibleGateway::renderText(const KwBibleModule::Key& key)
170 QString result;
171 Chapter* chapter = fetchChapter(key.start.book, key.start.chapter);
172 if (0 != chapter)
174 if (key.start.verse >= 0 && key.start.verse < chapter->verses.size())
176 return chapter->verses[key.start.verse];
178 else
180 return chapter->verses.join("");
183 return result;
187 * Protected virtual interface
190 void KwBibleModuleBibleGateway::obtainBooks()
192 QStringList list;
193 for (int book = 0; book < m_bookList.size(); ++book)
195 list << m_bookList[book].name;
197 setBooks(list);
201 * Private functions
204 /// Ensure chapter contents are fetched.
205 KwBibleModuleBibleGateway::Chapter* KwBibleModuleBibleGateway::fetchChapter(int book, int chapter)
207 if (book >= 0 && book < m_bookList.size())
209 Book* bookObj = &m_bookList[book];
210 if (chapter >= 0 && chapter < m_bookList[book].chapters.size())
212 Chapter* chap = &bookObj->chapters[chapter];
213 if (!chap->fetched)
215 QString tmpFile;
216 if (KIO::NetAccess::download(chap->url, tmpFile, 0))
218 QFile file(tmpFile);
219 if (file.open(QFile::ReadOnly | QFile::Text))
221 QByteArray rawPage = file.readAll();
222 file.close();
224 DOM::HTMLDocument doc;
225 doc.loadXML(QString::fromUtf8(rawPage));
227 // Find all spans with class="sup"
228 DOM::NodeList sups = doc.body().getElementsByClassName("sup");
229 int verse = 0;
230 for (unsigned int i = 0; i < sups.length(); ++i)
232 DOM::HTMLElement span = sups.item(i);
233 if (span.tagName() == "span")
235 // Get the verse number and validate
236 bool numeric;
237 QString verseNumber = span.innerText().string();
238 int check = verseNumber.toInt(&numeric);
239 if (!numeric)
241 // Its not going to be a verse if it isn't numeric
242 KMessageBox::error(0, i18n("Error parsing webpage: %1", i18n("Non numeric superscript encountered: '%1'. It may correspond to a verse range.", verseNumber)));
243 continue;
245 ++verse;
246 if (check != verse)
248 KMessageBox::error(0, i18n("Error parsing webpage: %1", i18n("Non sequential verse list in chapter %1 of book '%2'. Expected verse %3 but got verse %4.", (chapter+1), bookObj->name, verse, check)));
249 break;
251 QString content;
253 // Get any headers before it
254 DOM::Node sibling;
255 sibling = span.previousSibling();
256 while (!sibling.isNull())
258 DOM::Element siblingElement = sibling;
259 if (!siblingElement.isNull())
261 // Stop at a span class="sup"
262 if (siblingElement.tagName() == "span" && siblingElement.getAttribute("class") == "sup")
264 break;
266 // See if its an interesting heading
267 DOM::HTMLHeadingElement heading = siblingElement;
268 if (!heading.isNull())
270 content = heading.toHTML() + content;
273 sibling = sibling.previousSibling();
276 // Add verse number
277 content += QString("<sup>%1</sup>").arg(verse);
279 // Get any text after it until the next span
280 sibling = span.nextSibling();
281 while (!sibling.isNull())
283 DOM::Element siblingElement = sibling;
284 if (!siblingElement.isNull())
286 // Stop at a span class="sup"
287 if (siblingElement.tagName() == "span" && siblingElement.getAttribute("class") == "sup")
289 break;
291 // Also stop at headings
292 DOM::HTMLHeadingElement heading = siblingElement;
293 if (!heading.isNull())
295 break;
298 content += sibling.toHTML();
299 sibling = sibling.nextSibling();
302 chap->verses.push_back(content);
307 KIO::NetAccess::removeTempFile(tmpFile);
309 else
311 KMessageBox::error(0, KIO::NetAccess::lastErrorString());
313 chap->fetched = true;
316 return chap;
319 return 0;