Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
SwordBook |
|
| 4.416666666666667;4.417 |
1 | /** | |
2 | * Distribution License: | |
3 | * JSword is free software; you can redistribute it and/or modify it under | |
4 | * the terms of the GNU Lesser General Public License, version 2.1 or later | |
5 | * as published by the Free Software Foundation. This program is distributed | |
6 | * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even | |
7 | * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
8 | * See the GNU Lesser General Public License for more details. | |
9 | * | |
10 | * The License is available on the internet at: | |
11 | * http://www.gnu.org/copyleft/lgpl.html | |
12 | * or by writing to: | |
13 | * Free Software Foundation, Inc. | |
14 | * 59 Temple Place - Suite 330 | |
15 | * Boston, MA 02111-1307, USA | |
16 | * | |
17 | * © CrossWire Bible Society, 2005 - 2016 | |
18 | * | |
19 | */ | |
20 | package org.crosswire.jsword.book.sword; | |
21 | ||
22 | import java.util.List; | |
23 | ||
24 | import org.crosswire.jsword.JSOtherMsg; | |
25 | import org.crosswire.jsword.book.BookException; | |
26 | import org.crosswire.jsword.book.BookMetaData; | |
27 | import org.crosswire.jsword.book.OSISUtil; | |
28 | import org.crosswire.jsword.book.basic.AbstractPassageBook; | |
29 | import org.crosswire.jsword.book.filter.SourceFilter; | |
30 | import org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor; | |
31 | import org.crosswire.jsword.passage.Key; | |
32 | import org.crosswire.jsword.passage.KeyUtil; | |
33 | import org.crosswire.jsword.passage.NoSuchKeyException; | |
34 | import org.crosswire.jsword.passage.PassageKeyFactory; | |
35 | import org.crosswire.jsword.passage.VerseKey; | |
36 | import org.crosswire.jsword.versification.Versification; | |
37 | import org.jdom2.Attribute; | |
38 | import org.jdom2.Content; | |
39 | import org.jdom2.Element; | |
40 | import org.slf4j.Logger; | |
41 | import org.slf4j.LoggerFactory; | |
42 | ||
43 | /** | |
44 | * SwordBook is a base class for all verse based Sword type books. | |
45 | * | |
46 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
47 | * @author Joe Walker | |
48 | */ | |
49 | 0 | public class SwordBook extends AbstractPassageBook { |
50 | /** | |
51 | * Construct an SwordBook given the BookMetaData and the AbstractBackend. | |
52 | * | |
53 | * @param sbmd the metadata that describes the book | |
54 | * @param backend the means by which the resource is accessed | |
55 | */ | |
56 | public SwordBook(SwordBookMetaData sbmd, Backend<?> backend) { | |
57 | 0 | super(sbmd, backend); |
58 | ||
59 | 0 | this.filter = sbmd.getFilter(); |
60 | ||
61 | 0 | if (backend == null) { |
62 | 0 | throw new IllegalArgumentException("AbstractBackend must not be null."); |
63 | } | |
64 | 0 | } |
65 | ||
66 | /* (non-Javadoc) | |
67 | * @see org.crosswire.jsword.book.Book#getGlobalKeyList() | |
68 | */ | |
69 | public final Key getGlobalKeyList() { | |
70 | 0 | if (global == null) { |
71 | try { | |
72 | 0 | global = getBackend().getGlobalKeyList(); |
73 | 0 | return global; |
74 | 0 | } catch (UnsupportedOperationException ex) { |
75 | // fail silently, operation not supported by the backend | |
76 | 0 | log.debug(ex.getMessage()); |
77 | 0 | } catch (BookException ex) { |
78 | // failing silently, as previous behavior was to attempt to | |
79 | // return as much as we can using the slower method | |
80 | 0 | log.debug(ex.getMessage()); |
81 | 0 | } |
82 | ||
83 | 0 | Versification v11n = super.getVersification(); |
84 | ||
85 | 0 | global = super.createEmptyKeyList(); |
86 | 0 | Key all = PassageKeyFactory.instance().getGlobalKeyList(v11n); |
87 | ||
88 | 0 | for (Key key : all) { |
89 | 0 | if (contains(key)) { |
90 | 0 | global.addAll(key); |
91 | } | |
92 | } | |
93 | } | |
94 | ||
95 | 0 | return global; |
96 | } | |
97 | ||
98 | /* (non-Javadoc) | |
99 | * @see org.crosswire.jsword.book.basic.AbstractBook#getScope() | |
100 | */ | |
101 | @Override | |
102 | public Key getScope() { | |
103 | 0 | SwordBookMetaData sbmd = (SwordBookMetaData) getBookMetaData(); |
104 | //if the book type doesn't have verses, then leave it. | |
105 | 0 | if (sbmd.getProperty(BookMetaData.KEY_VERSIFICATION) == null) { |
106 | //then we're not looking at a versified book | |
107 | 0 | return null; |
108 | } | |
109 | ||
110 | 0 | Object keyString = sbmd.getProperty(BookMetaData.KEY_SCOPE); |
111 | ||
112 | 0 | if (keyString != null) { |
113 | try { | |
114 | 0 | return getKey((String) keyString); |
115 | 0 | } catch (NoSuchKeyException ex) { |
116 | //the scope defined is not correct | |
117 | 0 | log.error("Unable to parse scope from book", ex); |
118 | 0 | return null; |
119 | } | |
120 | } | |
121 | ||
122 | //need to calculate the scope | |
123 | //now comes the expensive part | |
124 | 0 | Key bookKeys = getGlobalKeyList(); |
125 | ||
126 | //this is practically impossible, but cater for it just in case. | |
127 | 0 | if (!(bookKeys instanceof VerseKey)) { |
128 | 0 | log.error("Global key list isn't a verse key. A very expensive no-op has just occurred."); |
129 | 0 | return null; |
130 | } | |
131 | ||
132 | 0 | return bookKeys; |
133 | } | |
134 | ||
135 | /* (non-Javadoc) | |
136 | * @see org.crosswire.jsword.book.Book#contains(org.crosswire.jsword.passage.Key) | |
137 | */ | |
138 | public boolean contains(Key key) { | |
139 | 0 | return getBackend().contains(key); |
140 | } | |
141 | ||
142 | /* (non-Javadoc) | |
143 | * @see org.crosswire.jsword.book.Book#getRawText(org.crosswire.jsword.passage.Key) | |
144 | */ | |
145 | public String getRawText(Key key) throws BookException { | |
146 | 0 | return getBackend().getRawText(key); |
147 | } | |
148 | ||
149 | @Override | |
150 | protected List<Content> getOsis(Key key, RawTextToXmlProcessor processor) throws BookException { | |
151 | 0 | List<Content> result = getBackend().readToOsis(key, processor); |
152 | 0 | assert result != null; |
153 | 0 | return result; |
154 | } | |
155 | ||
156 | @Override | |
157 | public void addOSIS(Key key, Element div, List<Content> osisContent) { | |
158 | // See if the text is marked up with verses | |
159 | // If it is then just add it. | |
160 | 0 | for (Content content : osisContent) { |
161 | 0 | if (content instanceof Element) { |
162 | 0 | Element ele = (Element) content; |
163 | 0 | if (ele.getName().equals(OSISUtil.OSIS_ELEMENT_VERSE)) { |
164 | 0 | super.addOSIS(key, div, osisContent); |
165 | 0 | return; |
166 | } | |
167 | 0 | } |
168 | } | |
169 | ||
170 | // If we get here then the text is not marked up with verse | |
171 | // In this case we add the verse markup, if the verse is not 0. | |
172 | 0 | if (KeyUtil.getVerse(key).getVerse() == 0) { |
173 | 0 | super.addOSIS(key, div, osisContent); |
174 | } else { | |
175 | // In a SWORD module, the verse element is to be put | |
176 | // after the "preverse" material. | |
177 | 0 | Element everse = OSISUtil.factory().createVerse(); |
178 | 0 | everse.setAttribute(OSISUtil.OSIS_ATTR_OSISID, key.getOsisID()); |
179 | 0 | div.addContent(everse); |
180 | 0 | super.addOSIS(key, everse, osisContent); |
181 | } | |
182 | 0 | } |
183 | ||
184 | @Override | |
185 | public void addOSIS(Key key, List<Content> contentList, List<Content> osisContent) { | |
186 | // If there is no content, then there is nothing to do. | |
187 | 0 | if (osisContent.size() == 0) { |
188 | 0 | return; |
189 | } | |
190 | ||
191 | // Note: Verse 0 is an introduction and not a verse so it never gets verse markup. | |
192 | // However, it should be wrapped by a div as it should be isolated as an intro. | |
193 | 0 | if (KeyUtil.getVerse(key).getVerse() == 0) { |
194 | 0 | Element div = OSISUtil.factory().createDiv(); |
195 | 0 | div.setAttribute(OSISUtil.OSIS_ATTR_OSISID, key.getOsisID()); |
196 | // Mark it as generated | |
197 | 0 | div.setAttribute(OSISUtil.OSIS_ATTR_TYPE, OSISUtil.GENERATED_CONTENT); |
198 | // Put the OSIS into the div | |
199 | 0 | div.addContent(osisContent); |
200 | // Then put the div at the end of the contentList | |
201 | 0 | contentList.add(div); |
202 | 0 | return; |
203 | } | |
204 | ||
205 | // SWORD modules typically do not have the verse marker. When they don't | |
206 | // then the verse element needs to wrap the verse content. | |
207 | // However, the verse content may have "pre-verse" content. | |
208 | // This is marked up in one of two ways: | |
209 | // 1) old way | |
210 | // <title subType="x-preverse">...</title> | |
211 | // There may be more than one title marked as such. | |
212 | // 2) current way | |
213 | // <div sID="xxx" type="x-milestone" subType="x-preverse"/> | |
214 | // ... | |
215 | // <div eID="xxx" type="x-milestone" subType="x-preverse"/> | |
216 | // verse content | |
217 | // In this we only need to look for the ending. | |
218 | // The critical observation is that the verse marker is to | |
219 | // follow the last element marked x-preverse. | |
220 | // Also, there are a good number of modules that have a title marked | |
221 | // type="psalm" and not canonical="true" which they should be. | |
222 | ||
223 | // See if the text is marked up with verse elements | |
224 | // If it is then just add it. | |
225 | 0 | int start = 0; |
226 | 0 | int found = -1; |
227 | 0 | boolean wrapped = false; |
228 | 0 | Element preverse = null; |
229 | 0 | for (Content content : osisContent) { |
230 | 0 | if (content instanceof Element) { |
231 | 0 | Element ele = (Element) content; |
232 | 0 | String name = ele.getName(); |
233 | 0 | if (OSISUtil.OSIS_ELEMENT_VERSE.equals(name)) { |
234 | 0 | wrapped = true; |
235 | 0 | continue; |
236 | } | |
237 | 0 | Attribute typeAttr = ele.getAttribute(OSISUtil.OSIS_ATTR_TYPE); |
238 | 0 | Attribute subTypeAttr = ele.getAttribute(OSISUtil.OSIS_ATTR_SUBTYPE); |
239 | 0 | if (subTypeAttr != null && "x-preverse".equals(subTypeAttr.getValue())) { |
240 | 0 | if (OSISUtil.OSIS_ELEMENT_DIV.equals(name) || OSISUtil.OSIS_ELEMENT_TITLE.equals(name)) { |
241 | 0 | preverse = ele; |
242 | 0 | found = start; |
243 | } | |
244 | 0 | } else if (typeAttr != null && "psalm".equals(typeAttr.getValue()) && OSISUtil.OSIS_ELEMENT_TITLE.equals(name)) { |
245 | // Psalm titles should be both canonical and preverse | |
246 | // set the appropriate attributes if not already set. | |
247 | 0 | Attribute canonicalAttr = ele.getAttribute(OSISUtil.OSIS_ATTR_CANONICAL); |
248 | 0 | if (canonicalAttr == null) { |
249 | 0 | ele.setAttribute(OSISUtil.OSIS_ATTR_CANONICAL, "true"); |
250 | } | |
251 | 0 | if (subTypeAttr == null) { |
252 | 0 | ele.setAttribute(OSISUtil.OSIS_ATTR_SUBTYPE, "x-preverse"); |
253 | 0 | preverse = ele; |
254 | 0 | found = start; |
255 | } | |
256 | } | |
257 | } | |
258 | 0 | start++; |
259 | } | |
260 | ||
261 | // Check to see whether the text is marked up with verse | |
262 | 0 | if (wrapped) { |
263 | 0 | super.addOSIS(key, contentList, osisContent); |
264 | 0 | return; |
265 | } | |
266 | ||
267 | // If we get here then the text is not marked up with verse | |
268 | // In this case we add the verse markup, if the verse is not 0. | |
269 | 0 | Element everse = OSISUtil.factory().createVerse(); |
270 | 0 | everse.setAttribute(OSISUtil.OSIS_ATTR_OSISID, key.getOsisID()); |
271 | 0 | if (preverse == null) { |
272 | 0 | everse.addContent(osisContent); |
273 | } else { | |
274 | 0 | List<Content> sublist = osisContent.subList(found + 1, osisContent.size()); |
275 | 0 | everse.addContent(sublist); |
276 | // a sub list is actually part of the original list | |
277 | // clearing it removes it from the original list | |
278 | 0 | sublist.clear(); |
279 | // Now append shortened list | |
280 | 0 | super.addOSIS(key, contentList, osisContent); |
281 | } | |
282 | // Then put the verse at the end of the contentList | |
283 | 0 | contentList.add(everse); |
284 | 0 | } |
285 | ||
286 | @Override | |
287 | public boolean isWritable() { | |
288 | 0 | return getBackend().isWritable(); |
289 | } | |
290 | ||
291 | /* (non-Javadoc) | |
292 | * @see org.crosswire.jsword.book.Book#setRawText(org.crosswire.jsword.passage.Key, java.lang.String) | |
293 | */ | |
294 | public void setRawText(Key key, String rawData) throws BookException { | |
295 | 0 | throw new BookException(JSOtherMsg.lookupText("This Book is read-only.")); |
296 | } | |
297 | ||
298 | /* (non-Javadoc) | |
299 | * @see org.crosswire.jsword.book.Book#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key) | |
300 | */ | |
301 | public void setAliasKey(Key alias, Key source) throws BookException { | |
302 | 0 | getBackend().setAliasKey(alias, source); |
303 | 0 | } |
304 | ||
305 | /* (non-Javadoc) | |
306 | * @see org.crosswire.jsword.book.basic.AbstractPassageBook#getFilter() | |
307 | */ | |
308 | @Override | |
309 | protected SourceFilter getFilter() { | |
310 | 0 | return filter; |
311 | } | |
312 | ||
313 | /** | |
314 | * The filter to use to convert to OSIS. | |
315 | */ | |
316 | private SourceFilter filter; | |
317 | ||
318 | /** | |
319 | * A cached representation of the global key list. | |
320 | */ | |
321 | private Key global; | |
322 | ||
323 | /** | |
324 | * The log stream | |
325 | */ | |
326 | 0 | private static final Logger log = LoggerFactory.getLogger(SwordBook.class); |
327 | } |