001package edu.pdx.cs410J.family;
002
003import edu.pdx.cs410J.family.Person.Gender;
004
005import java.io.File;
006import java.io.FileNotFoundException;
007import java.io.FileReader;
008import java.io.IOException;
009import java.io.PrintWriter;
010import java.io.Reader;
011import java.util.Calendar;
012import java.util.Date;
013
014import javax.xml.parsers.DocumentBuilder;
015import javax.xml.parsers.DocumentBuilderFactory;
016import javax.xml.parsers.ParserConfigurationException;
017
018import org.w3c.dom.Document;
019import org.w3c.dom.Element;
020import org.w3c.dom.NamedNodeMap;
021import org.w3c.dom.Node;
022import org.w3c.dom.NodeList;
023import org.xml.sax.InputSource;
024import org.xml.sax.SAXException;
025
026/**
027 * This class parses an XML file generated by <code>XmlDumper</code>
028 * and creates a family tree.
029 *
030 * @author David Whitlock
031 */
032public class XmlParser extends XmlHelper implements Parser {
033
034  private FamilyTree tree;  // The family tree we're building
035  private Reader reader;    // Read XML file from here
036
037  /**
038   * Creates a new XML parser that reads its input from a file of a
039   * given name.
040   */
041  public XmlParser(String fileName) throws FileNotFoundException {
042    this(new File(fileName));
043  }
044
045  /**
046   * Creates a new XML parser that reads its input from the given
047   * file.
048   */
049  public XmlParser(File file) throws FileNotFoundException {
050    this(new FileReader(file));
051  }
052
053  /**
054   * Creates a new XML parser that reads itsinput from the given
055   * <code>Reader</code>.  This lets us read from a source other
056   * than a file.
057   */
058  public XmlParser(Reader reader) {
059    this.reader = reader;
060  }
061
062  /**
063   * Examines a chuck of a DOM tree and extracts a String from its
064   * text.
065   */
066  private static String extractString(Node node) {
067    return node.getFirstChild().getNodeValue();
068  }
069
070  /**
071   * Examines a chunk of a DOM tree and extracts an int from its
072   * text.
073   */
074  private static int extractInteger(Node node)
075    throws FamilyTreeException {
076
077    String text = extractString(node);
078    try {
079      return Integer.parseInt(text);
080
081    } catch (NumberFormatException ex) {
082      throw new FamilyTreeException("Bad integer: " + text);
083    }
084  }
085
086  /**
087   * Examines a chunk of a DOM tree and extracts a <code>Date</code>
088   * from it.
089   */
090  private static Date extractDate(Element root)
091    throws FamilyTreeException {
092
093    // Make sure we're dealing with a data
094    if (!root.getNodeName().equals("date")) {
095      throw new FamilyTreeException("Not a <date>: " +
096        root.getNodeName() + ", '" +
097        root.getNodeValue() + "'");
098    }
099
100    Calendar cal = Calendar.getInstance();
101
102    NodeList children = root.getChildNodes();
103    for (int i = 0; i < children.getLength(); i++) {
104      Node node = children.item(i);
105      if (!(node instanceof Element)) {
106        continue;
107      }
108
109      Element element = (Element) node;
110      if (element.getNodeName().equals("month")) {
111        cal.set(Calendar.MONTH, extractInteger(element));
112
113      } else if (element.getNodeName().equals("day")) {
114        cal.set(Calendar.DATE, extractInteger(element));
115
116      } else if (element.getNodeName().equals("year")) {
117        cal.set(Calendar.YEAR, extractInteger(element));
118
119      } else {
120        String s = "Invalidate element in date: " +
121          element.getNodeName();
122        throw new FamilyTreeException(s);
123      }
124    }
125
126    return cal.getTime();
127  }
128
129  /**
130   * Examines a chunk of a DOM tree and adds a person to the family
131   * tree.
132   */
133  private void handlePerson(Element root) throws FamilyTreeException {
134    // Make sure that we're dealing with a person here
135    if (!root.getNodeName().equals("person")) {
136      throw new FamilyTreeException("Expecting a <person>");
137    }
138
139    Person person = null;
140    int id;
141    try {
142      id = Integer.parseInt(root.getAttribute("id"));
143
144    } catch (NumberFormatException ex) {
145      String s = "Person id \"" + root.getAttribute("id") +
146        "\" is not a valid id";
147      throw new FamilyTreeException(s);
148    }
149
150    Gender gender = (root.getAttribute("gender").equals("male")
151                  ? Person.MALE : Person.FEMALE);
152    person = this.tree.getPerson(id);
153    if (person == null) {
154      person = new Person(id, gender);
155      this.tree.addPerson(person);
156
157    } else {
158      if (gender != person.getGender()) {
159        String s = "Expecting " + person + " to be " +
160          (gender == Person.MALE ? "MALE" : " FEMALE");
161        throw new FamilyTreeException(s);
162      }
163    }
164
165    NodeList elements = root.getChildNodes();
166    for (int i = 0; i < elements.getLength(); i++) {
167      Node node = elements.item(i);
168      if (!(node instanceof Element)) {
169        continue;
170      }
171
172      Element element = (Element) node;
173
174      if (element.getNodeName().equals("first-name")) {
175        person.setFirstName(extractString(element));
176
177      } else if (element.getNodeName().equals("last-name")) {
178        person.setLastName(extractString(element));
179
180      } else if (element.getNodeName().equals("middle-name")) {
181        person.setMiddleName(extractString(element));
182
183      } else if (element.getNodeName().equals("dob")) {
184        Element dob = null;
185
186        NodeList list = element.getChildNodes();
187        for (int j = 0; j < list.getLength(); j++) {
188          Node n = list.item(j);
189          if (n instanceof Element) {
190            dob = (Element) n;
191            break;
192          }
193        }
194
195        if (dob == null) {
196          throw new FamilyTreeException("No <date> in <dob>?");
197        }
198
199        person.setDateOfBirth(extractDate(dob));
200
201      } else if (element.getNodeName().equals("dod")) {
202        Element dod = null;
203
204        NodeList list = element.getChildNodes();
205        for (int j = 0; j < list.getLength(); j++) {
206          Node n = list.item(j);
207          if (n instanceof Element) {
208            dod = (Element) n;
209            break;
210          }
211        }
212
213        if (dod == null) {
214          throw new FamilyTreeException("No <date> in <dod>?");
215        }
216
217        person.setDateOfDeath(extractDate(dod));
218
219      } else if (element.getNodeName().equals("father-id")) {
220        String s = extractString(element);
221        int fid = 0;
222        try {
223          fid = Integer.parseInt(s);
224
225        } catch (NumberFormatException ex) {
226          throw new FamilyTreeException("Bad father-id: " + s);
227        }
228
229        Person father = this.tree.getPerson(fid);
230        if (father == null) {
231          father = new Person(fid, Person.MALE);
232          this.tree.addPerson(father);
233        }
234        person.setFather(father);
235
236      } else if (element.getNodeName().equals("mother-id")) {
237        String s = extractString(element);
238        int mid = 0;
239        try {
240          mid = Integer.parseInt(s);
241
242        } catch (NumberFormatException ex) {
243          throw new FamilyTreeException("Bad mother-id: " + s);
244        }
245
246        Person mother = this.tree.getPerson(mid);
247        if (mother == null) {
248          mother = new Person(mid, Person.FEMALE);
249          this.tree.addPerson(mother);
250        }
251        person.setMother(mother);
252      }
253    }
254  }
255
256  /**
257   * Examines a chunk of a DOM tree and makes note of a marriage.
258   */
259  private void handleMarriage(Element root) throws FamilyTreeException {
260    // Make sure we're dealing with a marriage
261    if (!root.getNodeName().equals("marriage")) {
262      throw new FamilyTreeException("");
263    }
264
265    int husband_id = 0;
266    int wife_id = 0;
267
268    // Extract the husband and wife id's
269    NamedNodeMap attrs = root.getAttributes();
270    for (int i = 0; i < attrs.getLength(); i++) {
271      Node attr = attrs.item(i);
272      if (attr.getNodeName().equals("husband-id")) {
273        String id = attr.getNodeValue();
274
275        try {
276          husband_id = Integer.parseInt(id);
277
278        } catch (NumberFormatException ex) {
279          throw new FamilyTreeException("Bad husband id: " + id);
280        }
281
282      } else if (attr.getNodeName().equals("wife-id")) {
283        String id = attr.getNodeValue();
284
285        try {
286          wife_id = Integer.parseInt(id);
287        } catch (NumberFormatException ex) {
288          throw new FamilyTreeException("Bad wife id: " + id);
289        }
290      }
291    }
292
293    // Make a Marriage
294    Person husband = this.tree.getPerson(husband_id);
295    Person wife = this.tree.getPerson(wife_id);
296
297    Marriage marriage = new Marriage(husband, wife);
298    husband.addMarriage(marriage);
299    wife.addMarriage(marriage);
300
301    // Fill in info about the marriage
302    NodeList elements = root.getChildNodes();
303    for (int i = 0; i < elements.getLength(); i++) {
304      Node node = elements.item(i);
305      if (!(node instanceof Element)) {
306        continue;
307      }
308
309      Element element = (Element) node;
310      if (element.getNodeName().equals("location")) {
311        marriage.setLocation(extractString(element));
312
313      } else if (element.getNodeName().equals("date")) {
314        marriage.setDate(extractDate(element));
315      }
316    }
317  }
318
319  /**
320   * Parses the specified input source in XML format and from it
321   * creates a family tree.
322   */
323  public FamilyTree parse() throws FamilyTreeException {
324    this.tree = new FamilyTree();
325
326    // Create a DOM tree from the XML source
327    Document doc = null;
328    try {
329      DocumentBuilderFactory factory =
330        DocumentBuilderFactory.newInstance();
331      factory.setValidating(true);
332
333      DocumentBuilder builder =
334        factory.newDocumentBuilder();
335      builder.setErrorHandler(this);
336      builder.setEntityResolver(this);
337
338      doc = builder.parse(new InputSource(this.reader));
339
340    } catch (ParserConfigurationException ex) {
341      throw new FamilyTreeException("While parsing XML source: " + ex, ex);
342
343    } catch (SAXException ex) {
344      throw new FamilyTreeException("While parsing XML source: " + ex, ex);
345
346    } catch (IOException ex) {
347      throw new FamilyTreeException("While parsing XML source: " + ex, ex);
348    }
349
350    Element root = (Element) doc.getChildNodes().item(1);
351
352    // Make sure that we are really dealing with a family tree
353    if (!root.getNodeName().equals("family-tree")) {
354      throw new FamilyTreeException("Not a family tree XML source: " +
355        root.getNodeName());
356    }
357
358    NodeList stuff = root.getChildNodes();
359    for (int i = 0; i < stuff.getLength(); i++) {
360      Node node = stuff.item(i);
361
362      if (!(node instanceof Element)) {
363        // Ignore whitespace text and other stuff
364        continue;
365      }
366
367      Element element = (Element) node;
368
369      if (element.getNodeName().equals("person")) {
370        handlePerson(element);
371
372      } else if (element.getNodeName().equals("marriage")) {
373        handleMarriage(element);
374
375      } else {
376        String s = "A family tree should not have a " +
377          element.getNodeName();
378        throw new FamilyTreeException(s);
379      }
380    }
381
382    return this.tree;
383  }
384
385  /**
386   * Test program.  Parses an XML file specified on the command line
387   * and prints the resulting family tree to standard out.
388   */
389  public static void main(String[] args) {
390    if (args.length == 0) {
391      System.err.println("** Missing file name");
392      System.exit(1);
393    }
394
395    // Parse the input file
396    String fileName = args[0];
397    try {
398      Parser parser = new XmlParser(fileName);
399      FamilyTree tree = parser.parse();
400
401      PrintWriter out = new PrintWriter(System.out, true);
402      PrettyPrinter pretty = new PrettyPrinter(out);
403      pretty.dump(tree);
404
405    } catch (FileNotFoundException ex) {
406      System.err.println("** Could not find file " + fileName);
407
408    } catch (FamilyTreeException ex) {
409      ex.printStackTrace(System.err);
410    }
411  }
412
413}