| GedcomHandler.java |
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