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   * Copyright: 2005-2013
18   *     The copyright to this program is held by it's authors.
19   *
20   */
21  package org.crosswire.jsword.book;
22  
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import org.crosswire.common.diff.Diff;
28  import org.crosswire.common.diff.DiffCleanup;
29  import org.crosswire.common.diff.Difference;
30  import org.crosswire.common.util.Language;
31  import org.crosswire.common.xml.JDOMSAXEventProvider;
32  import org.crosswire.common.xml.SAXEventProvider;
33  import org.crosswire.jsword.passage.Key;
34  import org.jdom2.Content;
35  import org.jdom2.Document;
36  import org.jdom2.Element;
37  import org.jdom2.Namespace;
38  import org.jdom2.Text;
39  
40  /**
41   * BookData is the assembler of the OSIS that is returned by the filters. As
42   * such it puts that into an OSIS document. When several books are supplied, it
43   * gets the data from each and puts it into a parallel or interlinear view.
44   * Note: it is critical that all the books are able to understand the same key.
45   * That does not mean that each has to have content for each key. Missing keys
46   * are represented by empty cells.
47   * 
48   * @see gnu.lgpl.License for license details.<br>
49   *      The copyright to this program is held by it's authors.
50   * @author Joe Walker [joe at eireneh dot com]
51   * @author DM Smith
52   */
53  public class BookData implements BookProvider {
54      /**
55       * Ctor
56       */
57      public BookData(Book book, Key key) {
58          assert book != null;
59          assert key != null;
60  
61          this.key = key;
62  
63          books = new Book[1];
64          books[0] = book;
65      }
66  
67      /**
68       * Create BookData for multiple books.
69       */
70      public BookData(Book[] books, Key key, boolean compare) {
71          assert books != null && books.length > 0;
72          assert key != null;
73  
74          this.books = books.clone();
75          this.key = key;
76          this.comparingBooks = compare;
77      }
78  
79      /**
80       * Accessor for the root OSIS element
81       */
82      public Element getOsis() throws BookException {
83          if (osis == null) {
84              // TODO(DMS): Determine the proper representation of the OSISWork
85              // name for multiple books.
86              osis = OSISUtil.createOsisFramework(getFirstBook().getBookMetaData());
87              Element text = osis.getChild(OSISUtil.OSIS_ELEMENT_OSISTEXT);
88              Element div = getOsisFragment();
89              text.addContent(div);
90          }
91  
92          return osis;
93      }
94  
95      /**
96       * Accessor for the root OSIS element
97       */
98      public Element getOsisFragment() throws BookException {
99          if (fragment == null) {
100             fragment = getOsisContent();
101         }
102 
103         return fragment;
104     }
105 
106     /**
107      * Output the current data as a SAX stream.
108      * 
109      * @return A way of posting SAX events
110      */
111     public SAXEventProvider getSAXEventProvider() throws BookException {
112         // If the fragment is already in a document, then use that.
113         Element frag = getOsisFragment();
114         Document doc = frag.getDocument();
115         if (doc == null) {
116             doc = new Document(frag);
117         }
118         return new JDOMSAXEventProvider(doc);
119     }
120 
121     /**
122      * Who created this data.
123      * 
124      * @return Returns the book.
125      */
126     public Book[] getBooks() {
127         return books == null ? null : (Book[]) books.clone();
128     }
129 
130     /**
131      * Get the first book.
132      */
133     public Book getFirstBook() {
134         return books != null && books.length > 0 ? books[0] : null;
135     }
136 
137     /**
138      * The key used to obtain data from one or more books.
139      * 
140      * @return Returns the key.
141      */
142     public Key getKey() {
143         return key;
144     }
145 
146     /**
147      * @return whether the books should be compared.
148      */
149     public boolean isComparingBooks() {
150         return comparingBooks;
151     }
152 
153     private Element getOsisContent() throws BookException {
154         Element div = OSISUtil.factory().createDiv();
155 
156         if (books.length == 1) {
157             Iterator<Content> iter = books[0].getOsisIterator(key, false);
158             while (iter.hasNext()) {
159                 Content content = iter.next();
160                 div.addContent(content);
161             }
162         } else {
163             Element table = OSISUtil.factory().createTable();
164             Element row = OSISUtil.factory().createRow();
165             Element cell = null;
166 
167             table.addContent(row);
168 
169             Iterator<Content>[] iters = new Iterator[books.length];
170             boolean[] showDiffs = new boolean[books.length - 1];
171             boolean doDiffs = false;
172 
173             for (int i = 0; i < books.length; i++) {
174                 Book book = books[i];
175 
176                 cell = OSISUtil.factory().createHeaderCell();
177 
178                 if (i > 0) {
179                     Book firstBook = books[0];
180                     BookCategory category = book.getBookCategory();
181 
182                     BookCategory prevCategory = firstBook.getBookCategory();
183                     String prevName = firstBook.getInitials();
184                     showDiffs[i - 1] = comparingBooks && BookCategory.BIBLE.equals(category) && category.equals(prevCategory)
185                             && book.getLanguage().equals(firstBook.getLanguage()) && !book.getInitials().equals(prevName);
186 
187                     if (showDiffs[i - 1]) {
188                         doDiffs = true;
189                         StringBuilder buf = new StringBuilder(firstBook.getInitials());
190                         buf.append(" ==> ");
191                         buf.append(book.getInitials());
192 
193                         cell.addContent(OSISUtil.factory().createText(buf.toString()));
194                         row.addContent(cell);
195                         cell = OSISUtil.factory().createHeaderCell();
196                     }
197                 }
198 
199                 cell.addContent(OSISUtil.factory().createText(book.getInitials()));
200                 row.addContent(cell);
201 
202                 iters[i] = book.getOsisIterator(key, true);
203             }
204 
205             Content content = null;
206 
207             int cellCount = 0;
208             int rowCount = 0;
209             while (true) {
210                 cellCount = 0;
211 
212                 row = OSISUtil.factory().createRow();
213 
214                 String firstText = "";
215 
216                 for (int i = 0; i < iters.length; i++) {
217                     Book book = books[i];
218                     cell = OSISUtil.factory().createCell();
219                     Language lang = (Language) book.getProperty(BookMetaData.KEY_XML_LANG);
220                     if (lang != null) {
221                         cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
222                     }
223 
224                     row.addContent(cell);
225 
226                     StringBuilder newText = new StringBuilder(doDiffs ? 32 : 0);
227 
228                     if (iters[i].hasNext()) {
229                         List<Content> contents = new ArrayList<Content>(1);
230 
231                         do {
232                             content = iters[i].next();
233                             contents.add(content);
234                             addText(doDiffs, newText, content);
235                         } while (!isNextVerse(content));
236 
237                         if (doDiffs) {
238                             String thisText = newText.toString();
239                             if (unaccenter != null) {
240                                 thisText = unaccenter.unaccent(thisText);
241                             }
242 
243                             if (i > 0 && showDiffs[i - 1]) {
244                                 List<Difference> diffs = new Diff(firstText, thisText, false).compare();
245                                 DiffCleanup.cleanupSemantic(diffs);
246                                 cell.addContent(OSISUtil.diffToOsis(diffs));
247 
248                                 // Since we used that cell create another
249                                 cell = OSISUtil.factory().createCell();
250                                 lang = (Language) book.getProperty(BookMetaData.KEY_XML_LANG);
251                                 cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
252                                 row.addContent(cell);
253                             }
254                             if (i == 0) {
255                                 firstText = thisText;
256                             }
257                         }
258                         cell.addContent(contents);
259                         cellCount++;
260                     }
261                 }
262 
263                 if (cellCount == 0) {
264                     break;
265                 }
266 
267                 table.addContent(row);
268                 rowCount++;
269             }
270             if (rowCount > 0) {
271                 div.addContent(table);
272             }
273         }
274 
275         return div;
276     }
277 
278     private boolean isNextVerse(Content content) {
279         if (content instanceof Element) {
280             return OSISUtil.OSIS_ELEMENT_VERSE.equals(((Element) content).getName());
281         }
282 
283         return false;
284     }
285 
286     private void addText(boolean doDiffs, StringBuilder newText, Content content) {
287         if (doDiffs) {
288             // if we already have content, let's add a space to avoid chaining words together
289             if (newText.length() != 0) {
290                 newText.append(' ');
291             }
292 
293             if (content instanceof Element) {
294                 newText.append(OSISUtil.getCanonicalText((Element) content));
295             } else if (content instanceof Text) {
296                 newText.append(((Text) content).getText());
297             }
298         }
299     }
300 
301     /**
302      * @param unaccenter the unaccenter to set
303      */
304     public void setUnaccenter(UnAccenter unaccenter) {
305         this.unaccenter = unaccenter;
306     }
307 
308     /**
309      * What key was used to create this data
310      */
311     private Key key;
312 
313     /**
314      * The books to which the key should be applied.
315      */
316     private Book[] books;
317 
318     /**
319      * Whether the Books should be compared.
320      */
321     private boolean comparingBooks;
322 
323     /**
324      * The complete OSIS container for the element
325      */
326     private Element osis;
327 
328     /**
329      * Just the element
330      */
331     private Element fragment;
332 
333     private UnAccenter unaccenter;
334 }
335