1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.layout.genealogy.iohandler;
15  
16  import org.xml.sax.helpers.DefaultHandler;
17  import org.xml.sax.SAXException;
18  import org.xml.sax.Attributes;
19  
20  import java.io.Writer;
21  import java.io.OutputStream;
22  
23  /**
24  *  A SAX ContentHandler that writes the events to standard output
25  *  in GEDCOM format.
26  *  <p>This class expects to receive the data in normalised form, that is,
27  *  each contiguous piece of character data arrives in a single call of
28  *  the characters() interface.</p>
29   * http://homepage.ntlworld.com/michael.h.kay/gedml/index.html
30  *
31  *  @version 21 January 2001: modified to conform to SAX2
32  */
33  
34  public class GedcomOutputter extends DefaultHandler {
35      int level = 0;
36      boolean acceptCharacters = true;
37      GedcomLine line = new GedcomLine();
38      Writer writer;
39      OutputStream out;
40  
41    public GedcomOutputter(OutputStream out) {
42      this.out = out;
43    }
44  
45    /**
46      * Start of the document.
47      */
48      public void startDocument () throws SAXException {
49          // Create/open the output file
50          try {
51            if (out != null) {
52              writer = new AnselOutputStreamWriter(out);
53            } else {
54              writer = new AnselOutputStreamWriter(System.out);
55            }
56          } catch (Exception err) {
57              throw new SAXException("Failed to create output stream", err);
58          }
59      }
60  
61      /**
62      * End of the document.
63      */
64  
65      public void endDocument () throws SAXException
66      {
67          try {
68              if (line.level>=0) flushLine();
69              writer.write("0 TRLR\n");
70              writer.close();
71              if (out != null) {
72                out.close();
73              }
74          } catch (java.io.IOException err) {
75              throw new SAXException(err);
76          }
77      }
78  
79      /**
80      * Start of an element.
81      */
82  
83      public void startElement (String pref, String ns, String name, Attributes attributes) throws SAXException
84      {
85          if (name.equals("GED")) return;
86  
87          if (line.level>=0) flushLine();
88  
89          line.level = level;
90          line.id = attributes.getValue("", "ID");
91          line.tag = name;
92          line.ref = attributes.getValue("", "REF");
93          line.text.setLength(0);
94  
95          acceptCharacters = true;
96          level++;
97  
98      }
99  
100     /**
101     * End of an element.
102     */
103 
104     public void endElement (String prefix, String ns, String name) throws SAXException
105     {
106         level--;
107         if (line.level>=0) flushLine();
108         acceptCharacters = false;
109     }
110 
111     /**
112     * Character data.
113     */
114 
115     public void characters (char[] ch, int start, int length) throws SAXException
116     {
117         if (!acceptCharacters) {
118             for (int i=start; i<start+length; i++) {
119                 if (!Character.isWhitespace(ch[i])) {
120                     throw new SAXException("Character data not allowed after end tag");
121                 }
122             }
123         } else {
124             line.text.append(ch, start, length);
125         }
126 
127     }
128 
129     /**
130     * Ignore ignorable whitespace.
131     */
132 
133     public void ignorableWhitespace (char[] ch, int start, int length)
134     {}
135 
136 
137     /**
138     * Handle a processing instruction.
139     */
140 
141     public void processingInstruction (String target, String data)
142     {}
143 
144     /**
145     * Flush the accumulated output line
146     */
147 
148     private void flushLine() throws SAXException {
149         String text = line.text.toString().trim();
150         int base = line.level;
151         int prevnl = 0;
152         // if the line contains a newline char, add a CONT element
153         while (true) {
154             int nl = text.indexOf('\n');
155             String textline = text;
156             if (nl>=0) {
157                 line.text.setLength(nl);
158                 textline = text.substring(0,nl);
159             } else {
160                 textline = text;
161             }
162 
163             // if the line is longer than the GEDCOM limit, add CONC elements
164             while (true) {
165                 int split = -1;
166                 int len = line.tag.length() + 4 +
167                             (line.id==null ? 0 : line.id.length()+3) +
168                             (line.ref==null ? 0 : line.ref.length()+3) +
169                             textline.length();
170                 if (len > 255) {                    // exceeds the limit
171                     // split the line if possible between two non-space characters
172                     split = 80;
173                     while (split>0 &&
174                              (Character.isWhitespace(text.charAt(split)) ||
175                               Character.isWhitespace(text.charAt(split+1)))) {
176                         split--;
177                     }
178                     if (split==0) split = 80;       // failed: split at column 80
179                     line.text.setLength(split);
180                     line.write();
181 
182                     line.level = base+1;
183                     line.id = null;
184                     line.tag = "CONC";
185                     line.ref = null;
186                     line.text.setLength(0);
187                     textline = textline.substring(split);
188                     line.text.append(textline);
189                 }
190 
191                 line.write();
192                 if (split<0) break;
193             }
194 
195             // prepare next line of output
196             if (nl > 0 && nl+1 < text.length()) {
197                 line.level = base+1;
198                 line.id = null;
199                 line.tag = "CONT";
200                 line.ref = null;
201                 line.text.setLength(0);
202                 text = text.substring(nl+1);
203                 line.text.append(text);
204             } else {
205                 break;
206             }
207         }
208         line.text.setLength(0);
209         line.level = -1;
210     }
211 
212     /**
213     * Inner class representing a line of GEDCOM output
214     */
215 
216     private class GedcomLine {
217         public int level = -1;
218         public String id;
219         public String tag;
220         public String ref;
221         public StringBuffer text = new StringBuffer();
222 
223         /**
224         * Write the GEDCOM line using the current writer
225         */
226 
227         public void write() throws SAXException {
228             try {
229                 writer.write(level + " ");
230                 if (id!=null) {
231                     writer.write('@' + id + "@ ");
232                 }
233                 writer.write(tag + ' ');
234                 if (ref!=null) {
235                     writer.write('@' + ref + "@ ");
236                 }
237                 writer.write(text.toString() + '\n');
238             } catch (java.io.IOException err) {
239                 throw new SAXException(err);
240             }
241         }
242     }
243 
244 }
245