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.w3c.dom.Document;
17  import org.xml.sax.ContentHandler;
18  import org.xml.sax.InputSource;
19  import org.xml.sax.SAXException;
20  import org.xml.sax.XMLFilter;
21  import org.xml.sax.XMLReader;
22  import org.xml.sax.helpers.AttributesImpl;
23  import org.xml.sax.helpers.XMLReaderFactory;
24  import y.base.DataProvider;
25  import y.base.Node;
26  import y.base.NodeCursor;
27  import y.io.GraphMLIOHandler;
28  import y.io.IOHandler;
29  import y.layout.genealogy.FamilyTreeLayouter;
30  import y.view.Graph2D;
31  
32  import javax.xml.transform.OutputKeys;
33  import javax.xml.transform.Source;
34  import javax.xml.transform.Transformer;
35  import javax.xml.transform.TransformerConfigurationException;
36  import javax.xml.transform.TransformerException;
37  import javax.xml.transform.TransformerFactory;
38  import javax.xml.transform.dom.DOMResult;
39  import javax.xml.transform.dom.DOMSource;
40  import javax.xml.transform.sax.SAXSource;
41  import javax.xml.transform.sax.SAXTransformerFactory;
42  import javax.xml.transform.stream.StreamResult;
43  import javax.xml.transform.stream.StreamSource;
44  import java.io.IOException;
45  import java.io.InputStream;
46  import java.io.OutputStream;
47  
48  /**
49   * IOHandler that is able to read arbitrary GEDCOM files.
50   * (GEDCOM is a widely used format to store family trees, see http://www.phpgedview.net/ged551-5.pdf
51   * for the most recent specifications).
52   * <p/>
53   * The reader works the following way:
54   * <ul>
55   * <li>The gedcom format is transformed into XML</li>
56   * <li>The generated XML is transformed by an XSL stylesheet (resources/gedml2graphml.xsl) into GraphML</li>
57   * <li>The generated GraphML is read by a GraphMLIOHandler which has to be specified
58   * in the constructor or set by setReaderDelegate</li>
59   * </ul>
60   */
61  public class GedcomHandler extends IOHandler {
62    private GraphMLIOHandler readerDelegate;
63  
64    /** Default for GedCOM is ANSEL encoding, which does not exist in Java */
65    private String encoding = "ANSEL";
66  
67  
68    /**
69     * Gets the encoding String. Default for GedCOM is ANSEL encoding, which does not exist in Java.
70     * @return The encoding string
71     */
72    public String getEncoding() {
73      return encoding;
74    }
75  
76    /**
77     * Sets the encoding String. Default for GedCOM is ANSEL encoding, which does not exist in Java.
78     * @param encoding The encoding String
79     */
80    public void setEncoding(String encoding) {
81      this.encoding = encoding;
82    }
83  
84    /**
85     * Creates a new instance of the IOHandler.
86     */
87    public GedcomHandler() {
88    }
89  
90    /**
91     * Creates a new instance of the IOHandler with the given
92     * reader delegate.
93     */
94    public GedcomHandler(GraphMLIOHandler readerDelegate) {
95      setReaderDelegate(readerDelegate);
96    }
97  
98    /////////////////////////////////////////////////////////////////////////////
99    ///////////////////////////   IOHandler Interface  //////////////////////////
100   /////////////////////////////////////////////////////////////////////////////
101 
102   /**
103    * Returns a textual description of the IOHandler.
104    */
105   public String getFileFormatString() {
106     return "GedCOM files";
107   }
108 
109   /**
110    * Returns the file name extension for files this handler can handle.
111    */
112   public String getFileNameExtension() {
113     return "ged";
114   }
115 
116   /**
117    * Sets the IOHandler that will further process the XSL transformed
118    * XML input.
119    */
120   public void setReaderDelegate(GraphMLIOHandler reader) {
121     readerDelegate = reader;
122   }
123 
124   /**
125    * Returns the IOHandler that will further process the XSL transformed
126    * XML input.
127    */
128   public IOHandler getReaderDelegate() {
129     return readerDelegate;
130   }
131 
132 
133   /**
134    * Writes a graph to a Gedcom file. Uses the Michael H. Kay's GedcomOutputter as ContentHandler:
135    * http://homepage.ntlworld.com/michael.h.kay/gedml/index.html
136    * <p/>
137    * The data provider {@link y.layout.genealogy.FamilyTreeLayouter#DP_KEY_FAMILY_TYPE}
138    * has to be registered to the graph.
139    * <p/>
140    * For INDI entries, only the subentries SEX, NAME, FAMS and FAMC are supported<br/>
141    * For FAM entries, only the subentries WIFE, HUSB and CHIL are supported.<br/>
142    * @param graph The Graph to write.
143    * @param out The OutputStream to write to.
144    * @throws java.io.IOException
145    * @throws IllegalStateException if the data provider
146    * {@link y.layout.genealogy.FamilyTreeLayouter#DP_KEY_FAMILY_TYPE} is not registered.
147    */
148   public void write(Graph2D graph, OutputStream out) throws IOException {
149 
150     DataProvider dpType = graph.getDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE);
151     if (dpType == null) {
152       throw new IllegalStateException("Data Provider " + FamilyTreeLayouter.DP_KEY_FAMILY_TYPE + "not found.");
153     }
154     ContentHandler content = new GedcomOutputter(out);
155     AttributesImpl emptyAttList = new AttributesImpl();
156     AttributesImpl attList = new AttributesImpl();
157     try {
158       /* Creates the Document and the header*/
159       content.startDocument();
160       content.startElement("", "GED", "GED", emptyAttList);
161       content.startElement("", "HEAD", "HEAD", emptyAttList);
162       content.startElement("", "SOUR", "SOUR", emptyAttList);
163       content.characters("yFiles FamilyTreeDemo".toCharArray(), 0, "yFiles FamilyTreeDemo".length());
164       content.endElement("", "SOUR", "SOUR");
165       content.endElement("", "HEAD", "HEAD");
166 
167       /* Iterates through all nodes of the graph */
168       for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
169         Node n = nc.node();
170         if (!FamilyTreeLayouter.TYPE_FAMILY.equals(dpType.get(n))) {
171           /* Individual: write id, sex and name */
172           attList.clear();
173           attList.addAttribute("", "ID", "ID", "ID", "I" + n.index());
174           content.startElement("", "INDI", "INDI", attList);
175           content.startElement("", "SEX", "SEX", emptyAttList);
176           if (FamilyTreeLayouter.TYPE_MALE.equals(dpType.get(n))) {
177             content.characters("M".toCharArray(), 0, 1);
178           } else {
179             content.characters("F".toCharArray(), 0, 1);
180           }
181           content.endElement("", "SEX", "SEX");
182           content.startElement("", "NAME", "NAME", emptyAttList);
183           String name = graph.getLabelText(n);
184           content.characters(name.toCharArray(), 0, name.length());
185           content.endElement("", "NAME", "NAME");
186           /* For outgoing edges: write the references to the families*/
187           for (NodeCursor p = n.predecessors(); p.ok(); p.next()) {
188             attList.clear();
189             attList.addAttribute("", "REF", "REF", "REF", "F" + p.node().index());
190             content.startElement("", "FAMC", "FAMC", attList);
191             content.endElement("", "FAMC", "FAMC");
192           }
193           /* For ingoing edges: write the references to the families */
194           for (NodeCursor p = n.successors(); p.ok(); p.next()) {
195             attList.clear();
196             attList.addAttribute("", "REF", "REF", "REF", "F" + p.node().index());
197             content.startElement("", "FAMS", "FAMS", attList);
198             content.endElement("", "FAMS", "FAMS");
199           }
200           content.endElement("", "INDI", "INDI");
201         } else {
202           /* Family: id */
203           attList.clear();
204           attList.addAttribute("", "ID", "ID", "ID", "F" + n.index());
205           content.startElement("", "FAM", "FAM", attList);
206           /* Ingoing edges: write the references to wife and husband */
207           for (NodeCursor p = n.predecessors(); p.ok(); p.next()) {
208             attList.clear();
209             attList.addAttribute("", "REF", "REF", "REF", "I" + p.node().index());
210             String tag = "WIFE";
211             if (FamilyTreeLayouter.TYPE_MALE.equals(dpType.get(p.node()))) {
212               tag = "HUSB";
213             }
214             content.startElement("", tag, tag, attList);
215             content.endElement("", tag, tag);
216           }
217           /* Outgoing edges: write the references to the children */
218           for (NodeCursor p = n.successors(); p.ok(); p.next()) {
219             attList.clear();
220             attList.addAttribute("", "REF", "REF", "REF", "I" + p.node().index());
221             content.startElement("", "CHIL", "CHIL", attList);
222             content.endElement("", "CHIL", "CHIL");
223           }
224           content.endElement("", "FAM", "FAM");
225         }
226       }
227       content.endElement("", "GED", "GED");
228       content.endDocument();
229     } catch (SAXException e) {
230       e.printStackTrace(); //TODO handle
231     }
232   }
233 
234 //  /**
235 //   * Reads a Gedcom file into the given graph.
236 //   * @param graph The graph to write to.
237 //   * @param in The InputStream to read from.
238 //   * @throws java.io.IOException
239 //   * @throws IllegalStateException if no reader delegate is set.
240 //   */
241 //  public void read(Graph2D graph, InputStream in) throws IOException {
242 //    if (readerDelegate == null) {
243 //      throw new IllegalStateException("No reader delegate set.");
244 //    }
245 //
246 //    ////// Transform the Gedcom file into XML format //////
247 //    XMLReader reader = null;
248 //    Transformer transformer = null;
249 //    try {
250 //      reader = XMLReaderFactory.createXMLReader("demo.layout.genealogy.iohandler.GedcomParser");
251 //      SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
252 //      transformer = factory.newTransformer();
253 //
254 //      InputSource inputSource;
255 //      if ("ANSEL".equals(getEncoding())) {
256 //        //hack until Charset is finished
257 //        inputSource = new InputSource(new AnselInputStreamReader(in));
258 //      } else {
259 //        inputSource = new InputSource(in);
260 //      }
261 //
262 //      ///// Transform the generated XML into GML /////
263 //      String path = "resources/gedml2gml.xsl";
264 //      InputStream stream = this.getClass().getResourceAsStream(path);
265 //      if (stream == null) {
266 //        throw new IOException("Could not find style sheet \"" + path + "\".");
267 //      }
268 //      StreamSource xslStream = new StreamSource(stream);
269 //
270 //      XMLFilter convert2graphmlFilter = factory.newXMLFilter(xslStream);
271 //      convert2graphmlFilter.setParent(reader);
272 //
273 //      Source saxSource = new SAXSource(convert2graphmlFilter, inputSource);
274 //      ByteArrayOutputStream baos = new ByteArrayOutputStream();
275 //      OutputStreamWriter osw = new OutputStreamWriter(baos);
276 //      Result result = new StreamResult(osw);
277 //      transformer.transform(saxSource, result);
278 //
279 //      ///// Read the generated GML into the given graph /////
280 //      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
281 //      byte[] b = new byte[1];
282 //      do {
283 //        bais.read(b);
284 //      } while (b[0] != ">".getBytes()[0]);
285 //      readerDelegate.read(graph, bais);
286 //    }
287 //    catch (SAXException e) {
288 //      throw new IOException(e.getMessage());
289 //    }
290 //    catch (TransformerConfigurationException e) {
291 //      throw new IOException(e.getMessage());
292 //    } catch (TransformerException e) {
293 //      throw new IOException(e.getMessage());
294 //    }
295 //  }
296 
297 // GraphML users can use the following read method rather than the above
298 // GraphML offers the possibility to map additional attributes to the graph
299 // Using the provided XSL sheet, the boolean attributes NodeTypeIndividual and NodeIsMale
300 // can be used as data providers which return true if the node is an individual and male, respectively.
301 // Also, the original entry is preserved (as XML) in the GedcomData attribute.
302 
303   /**
304    * Reads a Gedcom file into the given graph
305    * @param graph The graph to write to.
306    * @param in The InputStream to read from.
307    * @throws IOException
308    * @throws IllegalStateException if no reader delegate is set.
309    */
310   public void read(Graph2D graph, InputStream in) throws IOException {
311     if (readerDelegate == null) {
312       throw new IllegalStateException("No reader delegate set.");
313     }
314 
315     ////// Transform the Gedcom file into XML format //////
316     XMLReader reader = null;
317     Transformer transformer = null;
318     try {
319       reader = XMLReaderFactory.createXMLReader("demo.layout.genealogy.iohandler.GedcomParser");
320       SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
321       transformer = factory.newTransformer();
322 
323       InputSource inputSource;
324       if ("ANSEL".equals(getEncoding())) {
325         //hack until Charset is finished
326         inputSource = new InputSource(new AnselInputStreamReader(in));
327       } else {
328         inputSource = new InputSource(in);
329       }
330 
331       ///// Transform the generated XML into GraphML /////
332       String path = "resources/gedml2graphml.xsl";
333       InputStream stream = this.getClass().getResourceAsStream(path);
334       if (stream == null) {
335         throw new IOException("Could not find style sheet \"" + path + "\".");
336       }
337       StreamSource xslStream = new StreamSource(stream);
338 
339       XMLFilter convert2graphmlFilter = factory.newXMLFilter(xslStream);
340       convert2graphmlFilter.setParent(reader);
341 
342       Source saxSource = new SAXSource(convert2graphmlFilter, inputSource);
343       DOMResult xmlResult = new DOMResult();
344       transformer.transform(saxSource, xmlResult);
345 
346 //      StreamResult streamResult = new StreamResult(System.out);
347 //      DOMSource domSource = new DOMSource((Document) xmlResult.getNode());
348 //      transformer.setOutputProperty(OutputKeys.METHOD, "xml");
349 //      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
350 //      transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
351 //      try {
352 //        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
353 //      }
354 //      catch (IllegalArgumentException e) {
355 //        System.out.println("");
356 //      }
357 //      transformer.transform(domSource, streamResult);
358       ///// Read the generated GraphML into the given graph /////
359       readerDelegate.read(graph, (Document) xmlResult.getNode());
360     }
361     catch (SAXException e) {
362       throw new IOException(e.getMessage());
363     }
364     catch (TransformerConfigurationException e) {
365       throw new IOException(e.getMessage());
366     } catch (TransformerException e) {
367       throw new IOException(e.getMessage());
368     }
369   }
370 
371 
372 }
373