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}