1
20 package org.crosswire.jsword.book;
21
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.TreeMap;
27
28 import org.crosswire.common.diff.Diff;
29 import org.crosswire.common.diff.DiffCleanup;
30 import org.crosswire.common.diff.Difference;
31 import org.crosswire.common.util.Language;
32 import org.crosswire.common.xml.JDOMSAXEventProvider;
33 import org.crosswire.common.xml.SAXEventProvider;
34 import org.crosswire.jsword.passage.Key;
35 import org.crosswire.jsword.passage.KeyUtil;
36 import org.crosswire.jsword.passage.Passage;
37 import org.crosswire.jsword.passage.RestrictionType;
38 import org.crosswire.jsword.passage.Verse;
39 import org.crosswire.jsword.versification.Versification;
40 import org.crosswire.jsword.versification.VersificationsMapper;
41 import org.crosswire.jsword.versification.system.Versifications;
42 import org.jdom2.Content;
43 import org.jdom2.Document;
44 import org.jdom2.Element;
45 import org.jdom2.Namespace;
46 import org.jdom2.Text;
47
48
60 public class BookData implements BookProvider {
61
67 public BookData(Book book, Key key) {
68 assert book != null;
69 assert key != null;
70
71 this.key = key;
72
73 books = new Book[1];
74 books[0] = book;
75 }
76
77
84 public BookData(Book[] books, Key key, boolean compare) {
85 assert books != null && books.length > 0;
86 assert key != null;
87
88 this.books = books.clone();
89 this.key = key;
90 this.comparingBooks = compare;
91 }
92
93
99 public Element getOsis() throws BookException {
100 if (osis == null) {
101 osis = OSISUtil.createOsisFramework(getFirstBook().getBookMetaData());
104 Element text = osis.getChild(OSISUtil.OSIS_ELEMENT_OSISTEXT);
105 Element div = getOsisFragment();
106 text.addContent(div);
107 }
108
109 return osis;
110 }
111
112
118 public Element getOsisFragment() throws BookException {
119 if (fragment == null) {
120 fragment = getOsisContent(true);
121 }
122
123 return fragment;
124 }
125
126
133 public Element getOsisFragment(boolean allowGenTitles) throws BookException {
134 if (fragment == null) {
135 fragment = getOsisContent(allowGenTitles);
136 }
137
138 return fragment;
139 }
140
141
147 public SAXEventProvider getSAXEventProvider() throws BookException {
148 Element frag = getOsisFragment();
150 Document doc = frag.getDocument();
151 if (doc == null) {
152 doc = new Document(frag);
153 }
154 return new JDOMSAXEventProvider(doc);
155 }
156
157
162 public Book[] getBooks() {
163 return books == null ? null : (Book[]) books.clone();
164 }
165
166
171 public Book getFirstBook() {
172 return books != null && books.length > 0 ? books[0] : null;
173 }
174
175
180 public Key getKey() {
181 return key;
182 }
183
184
187 public boolean isComparingBooks() {
188 return comparingBooks;
189 }
190
191 private Element getOsisContent(boolean allowGenTitles) throws BookException {
192 Element div = OSISUtil.factory().createDiv();
193
194 if (books.length == 1) {
195 Iterator<Content> iter = books[0].getOsisIterator(key, false, allowGenTitles);
196 while (iter.hasNext()) {
197 Content content = iter.next();
198 div.addContent(content);
199 }
200 } else {
201 Element table = OSISUtil.factory().createTable();
202 Element row = OSISUtil.factory().createRow();
203 Element cell = OSISUtil.factory().createCell();
204
205 table.addContent(row);
206
207 Iterator<Content>[] iters = new Iterator[books.length];
208 Passage[] passages = new Passage[books.length];
209 boolean[] showDiffs = new boolean[books.length - 1];
210 boolean doDiffs = false;
211
212 boolean[] ommittedVerses = new boolean[books.length];
215 int numRangesInMasterPassage = 0;
216 for (int i = 0; i < books.length; i++) {
217 passages[i] = VersificationsMapper.instance().map(KeyUtil.getPassage(key), getVersification(i));
220
221 iters[i] = books[i].getOsisIterator(passages[i], true, true);
223
224 if (i == 0) {
225 ommittedVerses[i] = false;
227 numRangesInMasterPassage = passages[i].countRanges(RestrictionType.NONE);
228 } else {
229 ommittedVerses[i] = passages[i].countRanges(RestrictionType.NONE) > numRangesInMasterPassage;
232 }
233 }
234
235
236 BookVerseContent[] booksContents = new BookVerseContent[books.length];
238 for (int i = 0; i < books.length; i++) {
239 doDiffs |= addHeaderAndSetShowDiffsState(row, showDiffs, i, ommittedVerses[i]);
240 booksContents[i] = keyIteratorContentByVerse(
241 getVersification(i),
242 iters[i]);
243 }
244
245 int cellCount = 0;
246 int rowCount = 0;
247
248 for (Map.Entry<Verse, List<Content>> verseContent : booksContents[0].entrySet()) {
250 cellCount = 0;
251 row = OSISUtil.factory().createRow();
252 String firstText = "";
253
254 for (int i = 0; i < books.length; i++) {
255 Book book = books[i];
256 cell = OSISUtil.factory().createCell();
257 Language lang = book.getLanguage();
258 if (lang != null) {
259 cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
260 }
261
262 row.addContent(cell);
263
264 StringBuilder newText = new StringBuilder(doDiffs ? 32 : 0);
265
266 Key verseInRelavantBookContents = VersificationsMapper.instance().mapVerse(verseContent.getKey(), getVersification(i));
269
270 Passage passageOfInterest = KeyUtil.getPassage(verseInRelavantBookContents);
272 Iterator<Key> passageKeys = passageOfInterest.iterator();
273 while (passageKeys.hasNext()) {
274 Key singleKey = passageKeys.next();
275 if (!(singleKey instanceof Verse)) {
278 throw new UnsupportedOperationException("Iterating through a passage gives non-verses");
279 }
280
281 List<Content> xmlContent = booksContents[i].get(singleKey);
282
283 if (xmlContent == null) {
286 xmlContent = new ArrayList<Content>(0);
287 }
288
289 addText(doDiffs, newText, xmlContent);
290
291 if (doDiffs) {
292 String thisText = newText.toString();
293 if (unaccenter != null) {
294 thisText = unaccenter.unaccent(thisText);
295 }
296
297 if (i > 0 && showDiffs[i - 1]) {
298 List<Difference> diffs = new Diff(firstText, thisText, false).compare();
299 DiffCleanup.cleanupSemantic(diffs);
300 cell.addContent(OSISUtil.diffToOsis(diffs));
301
302 cell = OSISUtil.factory().createCell();
304 lang = book.getLanguage();
305 cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
306 row.addContent(cell);
307 }
308 if (i == 0) {
309 firstText = thisText;
310 }
311 }
312
313 addContentSafely(cell, xmlContent);
317 cellCount++;
318 }
319 }
320
321 if (cellCount == 0) {
322 break;
323 }
324
325 table.addContent(row);
326 rowCount++;
327 }
328 if (rowCount > 0) {
329 div.addContent(table);
330 }
331 }
332
333 return div;
334 }
335
336
344 private void addContentSafely(final Element cell, final List<Content> xmlContent) {
345 Element note = null;
346 for (Content c : xmlContent) {
347 if (c.getParent() == null) {
348 cell.addContent(c);
349 } else if (note != null) {
350 note.addContent(c.clone());
351 } else {
352 note = appendVersificationNotice(cell, "duplicate");
355 note.addContent(c.clone());
356 }
357 }
358 }
359
360
367 private Element appendVersificationNotice(Element parent, final String notice) {
368 Element note = OSISUtil.factory().createDiv();
369 note.setAttribute(OSISUtil.OSIS_ATTR_TYPE, OSISUtil.GENERATED_CONTENT);
370 note.setAttribute(OSISUtil.OSIS_ATTR_SUBTYPE, OSISUtil.TYPE_X_PREFIX + notice);
371 parent.addContent(note);
372 return note;
373 }
374
375
379 private Versification getVersification(final int i) {
380 return Versifications.instance().getVersification(
381 books[i].getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION));
382 }
383
384
385
394 private BookVerseContent keyIteratorContentByVerse(Versification v11n, final Iterator<Content> iter) throws BookException {
395 BookVerseContent contentsByOsisID = new BookVerseContent();
396
397 Verse currentVerse = null;
399 Content content;
400
401 List<Content> contents = new ArrayList<Content>();
402 while (iter.hasNext()) {
403 content = iter.next();
404 if (content instanceof Element && OSISUtil.OSIS_ELEMENT_VERSE.equals(((Element) content).getName())) {
405 if (currentVerse != null) {
406 contentsByOsisID.put(currentVerse, contents);
407 contents = new ArrayList<Content>();
408 }
409
410 currentVerse = OSISUtil.getVerse(v11n, (Element) content);
411
412 if (contents.size() > 0) {
417 Verse previousVerse = new Verse(currentVerse.getVersification(), currentVerse.getOrdinal() - 1);
418 contentsByOsisID.put(previousVerse, contents);
419 contents = new ArrayList<Content>();
420 }
421 }
422
423 contents.add(content);
424 }
425
426 if (currentVerse != null) {
428 contentsByOsisID.put(currentVerse, contents);
429 }
430
431 return contentsByOsisID;
432 }
433
434
441 private boolean addHeaderAndSetShowDiffsState(final Element row, final boolean[] showDiffs, final int i, final boolean ommittedVerse) {
442 boolean doDiffs = false;
443 Book book = books[i];
444 Element cell = OSISUtil.factory().createHeaderCell();
445
446 if (i > 0) {
447 Book firstBook = books[0];
448 BookCategory category = book.getBookCategory();
449
450 BookCategory prevCategory = firstBook.getBookCategory();
451 String prevName = firstBook.getInitials();
452 showDiffs[i - 1] = comparingBooks && BookCategory.BIBLE.equals(category) && category.equals(prevCategory)
453 && book.getLanguage().equals(firstBook.getLanguage()) && !book.getInitials().equals(prevName);
454
455 if (showDiffs[i - 1]) {
456 doDiffs = true;
457 StringBuilder buf = new StringBuilder(firstBook.getInitials());
458 buf.append(" ==> ");
459 buf.append(book.getInitials());
460
461 cell.addContent(OSISUtil.factory().createText(buf.toString()));
462 row.addContent(cell);
463 cell = OSISUtil.factory().createHeaderCell();
464 }
465 }
466
467 final Text text = OSISUtil.factory().createText(book.getInitials());
468 if (ommittedVerse) {
469 Element notice = this.appendVersificationNotice(cell, "omitted-verses");
470 notice.addContent(text);
471 } else {
472 cell.addContent(text);
473 }
474 row.addContent(cell);
475 return doDiffs;
476 }
477
478
485 private void addText(boolean doDiffs, StringBuilder newText, List<Content> contents) {
486 for (Content c : contents) {
487 addText(doDiffs, newText, c);
488 }
489 }
490
491
498 private void addText(boolean doDiffs, StringBuilder newText, Content content) {
499 if (doDiffs) {
500 if (newText.length() != 0) {
502 newText.append(' ');
503 }
504
505 if (content instanceof Element) {
506 newText.append(OSISUtil.getCanonicalText((Element) content));
507 } else if (content instanceof Text) {
508 newText.append(((Text) content).getText());
509 }
510 }
511 }
512
513
516 public void setUnaccenter(UnAccenter unaccenter) {
517 this.unaccenter = unaccenter;
518 }
519
520
524 class BookVerseContent extends TreeMap<Verse, List<Content>> {
525
528 private static final long serialVersionUID = -6508118172314227362L;
529 }
530
531
534 private Key key;
535
536
539 private Book[] books;
540
541
544 private boolean comparingBooks;
545
546
549 private Element osis;
550
551
554 private Element fragment;
555
556 private UnAccenter unaccenter;
557 }
558