package edu.pdx.cs410J.family; import edu.pdx.cs410J.family.Person.Gender; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.util.Calendar; import java.util.Date; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * This class parses an XML file generated by <code>XmlDumper</code> * and creates a family tree. * * @author David Whitlock */ public class XmlParser extends XmlHelper implements Parser { private FamilyTree tree; // The family tree we're building private Reader reader; // Read XML file from here /** * Creates a new XML parser that reads its input from a file of a * given name. */ public XmlParser(String fileName) throws FileNotFoundException { this(new File(fileName)); } /** * Creates a new XML parser that reads its input from the given * file. */ public XmlParser(File file) throws FileNotFoundException { this(new FileReader(file)); } /** * Creates a new XML parser that reads itsinput from the given * <code>Reader</code>. This lets us read from a source other * than a file. */ public XmlParser(Reader reader) { this.reader = reader; } /** * Examines a chuck of a DOM tree and extracts a String from its * text. */ private static String extractString(Node node) { return node.getFirstChild().getNodeValue(); } /** * Examines a chunk of a DOM tree and extracts an int from its * text. */ private static int extractInteger(Node node) throws FamilyTreeException { String text = extractString(node); try { return Integer.parseInt(text); } catch (NumberFormatException ex) { throw new FamilyTreeException("Bad integer: " + text); } } /** * Examines a chunk of a DOM tree and extracts a <code>Date</code> * from it. */ private static Date extractDate(Element root) throws FamilyTreeException { // Make sure we're dealing with a data if (!root.getNodeName().equals("date")) { throw new FamilyTreeException("Not a <date>: " + root.getNodeName() + ", '" + root.getNodeValue() + "'"); } Calendar cal = Calendar.getInstance(); NodeList children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (!(node instanceof Element)) { continue; } Element element = (Element) node; if (element.getNodeName().equals("month")) { cal.set(Calendar.MONTH, extractInteger(element)); } else if (element.getNodeName().equals("day")) { cal.set(Calendar.DATE, extractInteger(element)); } else if (element.getNodeName().equals("year")) { cal.set(Calendar.YEAR, extractInteger(element)); } else { String s = "Invalidate element in date: " + element.getNodeName(); throw new FamilyTreeException(s); } } return cal.getTime(); } /** * Examines a chunk of a DOM tree and adds a person to the family * tree. */ private void handlePerson(Element root) throws FamilyTreeException { // Make sure that we're dealing with a person here if (!root.getNodeName().equals("person")) { throw new FamilyTreeException("Expecting a <person>"); } Person person = null; int id; try { id = Integer.parseInt(root.getAttribute("id")); } catch (NumberFormatException ex) { String s = "Person id \"" + root.getAttribute("id") + "\" is not a valid id"; throw new FamilyTreeException(s); } Gender gender = (root.getAttribute("gender").equals("male") ? Person.MALE : Person.FEMALE); person = this.tree.getPerson(id); if (person == null) { person = new Person(id, gender); this.tree.addPerson(person); } else { if (gender != person.getGender()) { String s = "Expecting " + person + " to be " + (gender == Person.MALE ? "MALE" : " FEMALE"); throw new FamilyTreeException(s); } } NodeList elements = root.getChildNodes(); for (int i = 0; i < elements.getLength(); i++) { Node node = elements.item(i); if (!(node instanceof Element)) { continue; } Element element = (Element) node; if (element.getNodeName().equals("first-name")) { person.setFirstName(extractString(element)); } else if (element.getNodeName().equals("last-name")) { person.setLastName(extractString(element)); } else if (element.getNodeName().equals("middle-name")) { person.setMiddleName(extractString(element)); } else if (element.getNodeName().equals("dob")) { Element dob = null; NodeList list = element.getChildNodes(); for (int j = 0; j < list.getLength(); j++) { Node n = list.item(j); if (n instanceof Element) { dob = (Element) n; break; } } if (dob == null) { throw new FamilyTreeException("No <date> in <dob>?"); } person.setDateOfBirth(extractDate(dob)); } else if (element.getNodeName().equals("dod")) { Element dod = null; NodeList list = element.getChildNodes(); for (int j = 0; j < list.getLength(); j++) { Node n = list.item(j); if (n instanceof Element) { dod = (Element) n; break; } } if (dod == null) { throw new FamilyTreeException("No <date> in <dod>?"); } person.setDateOfDeath(extractDate(dod)); } else if (element.getNodeName().equals("father-id")) { String s = extractString(element); int fid = 0; try { fid = Integer.parseInt(s); } catch (NumberFormatException ex) { throw new FamilyTreeException("Bad father-id: " + s); } Person father = this.tree.getPerson(fid); if (father == null) { father = new Person(fid, Person.MALE); this.tree.addPerson(father); } person.setFather(father); } else if (element.getNodeName().equals("mother-id")) { String s = extractString(element); int mid = 0; try { mid = Integer.parseInt(s); } catch (NumberFormatException ex) { throw new FamilyTreeException("Bad mother-id: " + s); } Person mother = this.tree.getPerson(mid); if (mother == null) { mother = new Person(mid, Person.FEMALE); this.tree.addPerson(mother); } person.setMother(mother); } } } /** * Examines a chunk of a DOM tree and makes note of a marriage. */ private void handleMarriage(Element root) throws FamilyTreeException { // Make sure we're dealing with a marriage if (!root.getNodeName().equals("marriage")) { throw new FamilyTreeException(""); } int husband_id = 0; int wife_id = 0; // Extract the husband and wife id's NamedNodeMap attrs = root.getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { Node attr = attrs.item(i); if (attr.getNodeName().equals("husband-id")) { String id = attr.getNodeValue(); try { husband_id = Integer.parseInt(id); } catch (NumberFormatException ex) { throw new FamilyTreeException("Bad husband id: " + id); } } else if (attr.getNodeName().equals("wife-id")) { String id = attr.getNodeValue(); try { wife_id = Integer.parseInt(id); } catch (NumberFormatException ex) { throw new FamilyTreeException("Bad wife id: " + id); } } } // Make a Marriage Person husband = this.tree.getPerson(husband_id); Person wife = this.tree.getPerson(wife_id); Marriage marriage = new Marriage(husband, wife); husband.addMarriage(marriage); wife.addMarriage(marriage); // Fill in info about the marriage NodeList elements = root.getChildNodes(); for (int i = 0; i < elements.getLength(); i++) { Node node = elements.item(i); if (!(node instanceof Element)) { continue; } Element element = (Element) node; if (element.getNodeName().equals("location")) { marriage.setLocation(extractString(element)); } else if (element.getNodeName().equals("date")) { marriage.setDate(extractDate(element)); } } } /** * Parses the specified input source in XML format and from it * creates a family tree. */ public FamilyTree parse() throws FamilyTreeException { this.tree = new FamilyTree(); // Create a DOM tree from the XML source Document doc = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setErrorHandler(this); builder.setEntityResolver(this); doc = builder.parse(new InputSource(this.reader)); } catch (ParserConfigurationException ex) { throw new FamilyTreeException("While parsing XML source: " + ex, ex); } catch (SAXException ex) { throw new FamilyTreeException("While parsing XML source: " + ex, ex); } catch (IOException ex) { throw new FamilyTreeException("While parsing XML source: " + ex, ex); } Element root = (Element) doc.getChildNodes().item(1); // Make sure that we are really dealing with a family tree if (!root.getNodeName().equals("family-tree")) { throw new FamilyTreeException("Not a family tree XML source: " + root.getNodeName()); } NodeList stuff = root.getChildNodes(); for (int i = 0; i < stuff.getLength(); i++) { Node node = stuff.item(i); if (!(node instanceof Element)) { // Ignore whitespace text and other stuff continue; } Element element = (Element) node; if (element.getNodeName().equals("person")) { handlePerson(element); } else if (element.getNodeName().equals("marriage")) { handleMarriage(element); } else { String s = "A family tree should not have a " + element.getNodeName(); throw new FamilyTreeException(s); } } return this.tree; } /** * Test program. Parses an XML file specified on the command line * and prints the resulting family tree to standard out. */ public static void main(String[] args) { if (args.length == 0) { System.err.println("** Missing file name"); System.exit(1); } // Parse the input file String fileName = args[0]; try { Parser parser = new XmlParser(fileName); FamilyTree tree = parser.parse(); PrintWriter out = new PrintWriter(System.out, true); PrettyPrinter pretty = new PrettyPrinter(out); pretty.dump(tree); } catch (FileNotFoundException ex) { System.err.println("** Could not find file " + fileName); } catch (FamilyTreeException ex) { ex.printStackTrace(System.err); } } }