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 1607 2007-08-04 17:13:24Z 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.util.Language;
30  import org.crosswire.common.xml.JDOMSAXEventProvider;
31  import org.crosswire.common.xml.SAXEventProvider;
32  import org.crosswire.jsword.passage.Key;
33  import org.jdom.Content;
34  import org.jdom.Document;
35  import org.jdom.Element;
36  import org.jdom.Namespace;
37  import org.jdom.Text;
38  
39  /**
40   * BookData is the assembler of the OSIS that is returned by the filters.
41   * As such it puts that into an OSIS document. When several books are
42   * supplied, it gets the data from each and puts it into a parallel or
43   * 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.
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      /**
56       * Ctor
57       */
58      public BookData(Book book, Key key)
59      {
60          assert book != null;
61          assert key != null;
62  
63          this.key = key;
64  
65          books = new Book[1];
66          books[0] = book;
67      }
68  
69      /**
70       * Create BookData for multiple books.
71       */
72      public BookData(Book[] books, Key key, boolean compare)
73      {
74          assert books != null && books.length > 0;
75          assert key != null;
76  
77          this.books = (Book[]) books.clone();
78          this.key = key;
79          this.comparingBooks = compare;
80      }
81  
82      /**
83       * Accessor for the root OSIS element
84       */
85      public Element getOsis() throws BookException
86      {
87          if (osis == null)
88          {
89              // TODO(DMS): Determine the proper representation of the OSISWork name for multiple books.
90              osis = OSISUtil.createOsisFramework(getFirstBook().getBookMetaData());
91              Element text = osis.getChild(OSISUtil.OSIS_ELEMENT_OSISTEXT);
92              Element div = getOsisFragment();
93              text.addContent(div);
94          }
95  
96          return osis;
97      }
98  
99      /**
100      * Accessor for the root OSIS element
101      */
102     public Element getOsisFragment() throws BookException
103     {
104         if (fragment == null)
105         {
106             fragment = getOsisContent();
107         }
108 
109         return fragment;
110     }
111 
112     /**
113      * Output the current data as a SAX stream.
114      * @return A way of posting SAX events
115      */
116     public SAXEventProvider getSAXEventProvider() throws BookException
117     {
118         // If the fragment is already in a document, then use that.
119         Element frag = getOsisFragment();
120         Document doc = frag.getDocument();
121         if (doc == null)
122         {
123             doc = new Document(frag);
124         }
125         return new JDOMSAXEventProvider(doc);
126     }
127 
128     /**
129      * Who created this data.
130      * @return Returns the book.
131      */
132     public Book[] getBooks()
133     {
134         return books == null ? null : (Book[]) books.clone();
135     }
136 
137     /**
138      * Get the first book.
139      */
140     public Book getFirstBook()
141     {
142         return books != null && books.length > 0 ? books[0] : null;
143     }
144 
145     /**
146      * The key used to obtain data from one or more books.
147      * @return Returns the key.
148      */
149     public Key getKey()
150     {
151         return key;
152     }
153 
154     /**
155      * @return whether the books should be compared.
156      */
157     public boolean isComparingBooks()
158     {
159         return comparingBooks;
160     }
161 
162     private Element getOsisContent() throws BookException
163     {
164         Element div = OSISUtil.factory().createDiv();
165 
166         if (books.length == 1)
167         {
168             Iterator iter = books[0].getOsisIterator(key, false);
169             while (iter.hasNext())
170             {
171                 Content content = (Content) iter.next();
172                 div.addContent(content);
173             }
174         }
175         else
176         {
177             Element table = OSISUtil.factory().createTable();
178             Element row = OSISUtil.factory().createRow();
179             Element cell = null;
180 
181             table.addContent(row);
182 
183             Iterator[] iters = new Iterator[books.length];
184             boolean[] showDiffs = new boolean[books.length - 1];
185             boolean doDiffs = false;
186 
187             for (int i = 0; i < books.length; i++)
188             {
189                 Book book = books[i];
190 
191                 cell = OSISUtil.factory().createHeaderCell();
192 
193                 if (i > 0)
194                 {
195                     Book prevBook = books[i - 1];
196                     BookCategory category = book.getBookCategory();
197 
198                     BookCategory prevCategory = prevBook.getBookCategory();
199                     String prevName = prevBook.getInitials();
200                     showDiffs[i - 1] = comparingBooks
201                                             && BookCategory.BIBLE.equals(category)
202                                             && category.equals(prevCategory)
203                                             && book.getLanguage().equals(prevBook.getLanguage())
204                                             && !book.getInitials().equals(prevName);
205 
206                     if (showDiffs[i - 1])
207                     {
208                         doDiffs = true;
209                         StringBuffer buf = new StringBuffer(prevBook.getInitials());
210                         buf.append(" ==> "); //$NON-NLS-1$
211                         buf.append(book.getInitials());
212 
213                         cell.addContent(OSISUtil.factory().createText(buf.toString()));
214                         row.addContent(cell);
215                         cell = OSISUtil.factory().createHeaderCell();
216                     }
217                 }
218 
219                 cell.addContent(OSISUtil.factory().createText(book.getInitials()));
220                 row.addContent(cell);
221 
222                 iters[i] = book.getOsisIterator(key, true);
223             }
224 
225             Content content = null;
226 
227             int cellCount = 0;
228             int rowCount = 0;
229             while (true)
230             {
231                 cellCount = 0;
232 
233                 row = OSISUtil.factory().createRow();
234 
235                 String lastText = ""; //$NON-NLS-1$
236 
237                 for (int i = 0; i < iters.length; i++)
238                 {
239                     Book book = books[i];
240                     cell = OSISUtil.factory().createCell();
241                     Language lang = (Language) book.getProperty(BookMetaData.KEY_XML_LANG);
242                     cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
243                     row.addContent(cell);
244                     if (iters[i].hasNext())
245                     {
246                         content = (Content) iters[i].next();
247 
248                         if (doDiffs)
249                         {
250                             String thisText = ""; //$NON-NLS-1$
251                             if (content instanceof Element)
252                             {
253                                 thisText = OSISUtil.getCanonicalText((Element) content);
254                             }
255                             else if (content instanceof Text)
256                             {
257                                 thisText = ((Text) content).getText();
258                             }
259 
260                             if (i > 0 && showDiffs[i - 1])
261                             {
262                                 List diffs = new Diff(lastText, thisText, false).compare();
263                                 DiffCleanup.cleanupSemantic(diffs);
264                                 cell.addContent(OSISUtil.diffToOsis(diffs));
265 
266                                 // Since we used that cell create another
267                                 cell = OSISUtil.factory().createCell();
268                                 lang = (Language) book.getProperty(BookMetaData.KEY_XML_LANG);
269                                 cell.setAttribute(OSISUtil.OSIS_ATTR_LANG, lang.getCode(), Namespace.XML_NAMESPACE);
270                                 row.addContent(cell);
271                             }
272                             lastText = thisText;
273                         }
274                         cell.addContent(content);
275                         cellCount++;
276                     }
277                 }
278 
279                 if (cellCount == 0)
280                 {
281                     break;
282                 }
283 
284                 table.addContent(row);
285                 rowCount++;
286             }
287             if (rowCount > 0)
288             {
289                 div.addContent(table);
290             }
291         }
292 
293         return div;
294     }
295 
296     /**
297      * What key was used to create this data
298      */
299     private Key key;
300 
301     /**
302      * The books to which the key should be applied.
303      */
304     private Book[] books;
305 
306     /**
307      * Whether the Books should be compared.
308      */
309     private boolean comparingBooks;
310 
311     /**
312      * The complete OSIS container for the element
313      */
314     private Element osis;
315 
316     /**
317      * Just the element
318      */
319     private Element fragment;
320 }
321