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 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * 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
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: BookData.java 2050 2010-12-09 15:31:45Z dmsmith $
21   */
22  package org.crosswire.jsword.book;
23  
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.jdom.Content;
35  import org.jdom.Document;
36  import org.jdom.Element;
37  import org.jdom.Namespace;
38  import org.jdom.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 [dmsmith555 at yahoo dot com]
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 prevBook = books[i - 1];
180                     BookCategory category = book.getBookCategory();
181 
182                     BookCategory prevCategory = prevBook.getBookCategory();
183                     String prevName = prevBook.getInitials();
184                     showDiffs[i - 1] = comparingBooks && BookCategory.BIBLE.equals(category) && category.equals(prevCategory)
185                             && book.getLanguage().equals(prevBook.getLanguage()) && !book.getInitials().equals(prevName);
186 
187                     if (showDiffs[i - 1]) {
188                         doDiffs = true;
189                         StringBuilder buf = new StringBuilder(prevBook.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 lastText = "";
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                     cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
221                     row.addContent(cell);
222                     if (iters[i].hasNext()) {
223                         content = iters[i].next();
224 
225                         if (doDiffs) {
226                             String thisText = "";
227                             if (content instanceof Element) {
228                                 thisText = OSISUtil.getCanonicalText((Element) content);
229                             } else if (content instanceof Text) {
230                                 thisText = ((Text) content).getText();
231                             }
232 
233                             if (i > 0 && showDiffs[i - 1]) {
234                                 List<Difference> diffs = new Diff(lastText, thisText, false).compare();
235                                 DiffCleanup.cleanupSemantic(diffs);
236                                 cell.addContent(OSISUtil.diffToOsis(diffs));
237 
238                                 // Since we used that cell create another
239                                 cell = OSISUtil.factory().createCell();
240                                 lang = (Language) book.getProperty(BookMetaData.KEY_XML_LANG);
241                                 cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
242                                 row.addContent(cell);
243                             }
244                             lastText = thisText;
245                         }
246                         cell.addContent(content);
247                         cellCount++;
248                     }
249                 }
250 
251                 if (cellCount == 0) {
252                     break;
253                 }
254 
255                 table.addContent(row);
256                 rowCount++;
257             }
258             if (rowCount > 0) {
259                 div.addContent(table);
260             }
261         }
262 
263         return div;
264     }
265 
266     /**
267      * What key was used to create this data
268      */
269     private Key key;
270 
271     /**
272      * The books to which the key should be applied.
273      */
274     private Book[] books;
275 
276     /**
277      * Whether the Books should be compared.
278      */
279     private boolean comparingBooks;
280 
281     /**
282      * The complete OSIS container for the element
283      */
284     private Element osis;
285 
286     /**
287      * Just the element
288      */
289     private Element fragment;
290 }
291