"""address.py: Objects to represent XML address books using address.dtd $Revision: 1.6 $ $Date: 2002/11/06 06:57:46 $ Exports: class AddressBook: Represents the whole file. AddressBook ( fileName ): [ if fileName is a string -> if fileName is a readable, valid address file -> return a new AddressBook representing that file else -> raise IOError ] .headingText: [ content of as a string ] .date: [ date attribute as a string ] .addrList: [ a list of the Entry children of self ] .fileName: [ as passed to the constructor, read-only ] class Entry: Represents one . Entry ( entryNode ): [ if entryNode is a DOM Element object representing an ENTRY_GI element -> return a new Entry object representing that element ] .date: [ if self has a DATE_ATTR attribute -> that attribute's value as a string else -> None ] .status: [ if self has a STATUS_ATTR attribute -> that attribute's value as a string else -> None ] .hide: [ if self has a HIDE_ATTR attribute -> that attribute's value as a string else -> None ] .xrefList: [ a list of cross-reference strings from self's children ] .orgList: [ self's children as a list of Org objects ] .personList: [ self's children as a list of Person objects ] .eMailList: [ self's children as a list of EMail objects ] .locationList: [ self's children as a list of Mail objects ] .phoneList: [ self's children as a list of Phone objects ] .directions: [ if self has a child -> that child as a Directions object else -> None ] .notes: [ if self has a child -> that child's content as a string else -> None ] class Id: Represents one ID element, or . .key: Virtual member. Generic I/F: [ self's sort key in the output, as a string ] class Org(Id): Represents an . Org ( orgNode ): [ if orgNode is a DOM Element representing an -> return a new Org object representing that element ] .key [ self's content ] .name [ self's content ] .contact [ if self has a element -> its content as a string else -> None ] class Person(Id): Represents a . Person ( personNode ): [ if personNode is a DOM Element representing a -> return a new Person object representing that element ] .key: [ concatenation of self.last, self.first, and self.nick, where present, separated by spaces if ] .first: [ first name, or None if missing ] .nick: [ nickname, or None if missing ] .last: [ last name ] .firstLast(): [ return self as: [first+' ']+['"'+nick+'" ']+last ] .lastFirst(): [ return self as: last[', '+first][' "'+nick+'"'] class EMail: Represents an . EMail ( eMailNode ): [ if eMailNode is a DOM Element representing an -> return a new EMail object representing that element ] .text: [ self's content ] .kind: [ if self has a KIND_ATTR attribute -> that attribute's value else -> None ] class Location: Represents a . Location ( locationNode ): [ if locationNode is a DOM Element representing a -> return a new Location object representing that element ] .mail: [ if self has a MAIL_ATTR attribute -> its value as a string else -> None ] .kind: [ if self has a KIND_ATTR attribute -> its value as a string else -> None ] .aList: [ list of contents of A_GI children as strings ] .city: [ if self contains a CITY_GI element -> its content as a string else -> None ] .state: [ if self contains a STATE_GI element -> its content as a string else -> None ] .zip: [ if self contains a ZIP_GI element -> its content as a string else -> None ] .nation: [ if self contains a NATION_GI element -> its content as a string else -> None ] class Phone: Represents a . Phone ( phoneNode ): [ if phoneNode is a DOM Element representing a -> return a new Phone object representing that element ] .kind: [ if self has a kind= attribute -> its value as a string else -> None ] .unlisted: [ if self is unlisted -> 1 else -> 0 ] .text: [ content of self ] class Directions: Represents a . Directions ( directionsNode ): [ if directionsNode is a DOM Element representing a -> return a new Directions object representing that element ] .legList: [ list of Leg objects representing children of self in their original sequence ] class Leg: Represents a . Leg ( legNode ): [ if legNode is a DOM Element representing a -> return a new Leg object representing that element ] .on: [ content of self's child ] .when: [ content of self's child ] .then: [ content of self's child ] ================================================================ CONTENTS ---------------------------------------------------------------- 1. Imports 2. Verification functions 3. Manifest constants 4. Class definitions """ #================================================================ # Imports #---------------------------------------------------------------- # Global modules #-- import sys # Standard system module import xml.dom.minidom # Document Object Model for XML #-- # Local modules #-- from dom_helpers import * # My DOM helper functions #================================================================ # Manifest constants #---------------------------------------------------------------- # ..._GI: Generic identifiers (element types) # ..._ATTR: Attribute names #-- ADDRESS_BOOK_GI = "address-book" # HEADING_GI = "heading" # ENTRY_GI = "entry" # XREF_GI = "xref" # PERSON_GI = "person" # FIRST_GI = "first" # NICK_GI = "nick" # LAST_GI = "last" # ORG_GI = "org" # CONTACT_GI = "contact" # E_MAIL_GI = "e-mail" # LOCATION_GI = "location" # A_GI = "a" # 421 Coleman CITY_GI = "city" # Hobbs STATE_GI = "state" # NM ZIP_GI = "zip" # 88240 NATION_GI = "nation" # PHONE_GI = "phone" # DIRECTIONS_GI = "directions" # LEG_GI = "leg" # ON_GI = "on" # San Mateo N WHEN_GI = "when" # x Academy THEN_GI = "then" # Turn R NOTES_GI = "notes" # #================================================================ # Verification functions #---------------------------------------------------------------- # person-key(person) == # person.last + (" "+person.first, if person.first is present) + # (" "+person.nick, if person.nick is present) #-- # When a person's name is compared against other strings, the # last name is the primary key, followed by the first name and # nickname when present. #---------------------------------------------------------------- # - - - - - c l a s s A d d r e s s B o o k - - - - - class AddressBook: "Represents an entire XML file conforming to address.dtd." # - - - A d d r e s s B o o k . _ _ i n i t _ _ - - - def __init__ ( self, fileName ): "Constructor for AddressBook." #-- 1 -- self.fileName = fileName #-- 2 -- # [ if fileName names a readable, valid XML file conforming # to address.dtd -> # doc := a DOM Document object representing that file # addrBook := an Element object representing the # element # else -> # raise IOError ] try: doc = xml.dom.minidom.parse ( fileName ) rootList = doc.getElementsByTagName ( ADDRESS_BOOK_GI ) if len(rootList) < 1: raise IOError, \ ( "File `%s' has no root <%s> element" % ( fileName, ADDRESS_BOOK_GI ) ) else: addrBook = rootList[0] except Exception, detail: raise IOError, ( "Failure to parse `%s': %s" % ( fileName, str(detail) ) ) #-- 3 -- # [ self.headingText := content of addrBook's HEADING_GI child # self.date := addrBook's DATE_ATTR ] self.__readHeading ( addrBook ) #-- 4 -- # [ self.addrList := a list of addrBook's ENTRY_GI # children, as Entry objects, in document order ] self.__readEntries ( addrBook ) #-- 5 -- del addrBook, doc # Don't keep the input tree around anymore! # - - - A d d r e s s B o o k . _ _ r e a d H e a d i n g - - - def __readHeading ( self, addrBook ): """Process the content of the HEADING_GI child of addrBook [ if (addrBook is an Element representing the root element) -> self.headingText := content of addrBook's HEADING_GI child self.date := addrBook's DATE_ATTR ] """ #-- 1 -- # [ if addrBook has a HEADING_GI child -> # headingNode := that child as an Element ] headingNode = getChildIfAny ( addrBook, HEADING_GI ) if headingNode is None: raise IOError, ( "<%s> has no <%s> child." % ( addrBook.tagName, HEADING_GI ) ) #-- 2 -- # [ if headingNode has a DATE_ATTR attribute -> # self.date := the value of that attribute ] self.date = getAttr ( headingNode, DATE_ATTR ) #-- 3 -- # [ self.headingText := headingNode's content ] self.headingText = textContent ( headingNode ) # - - - A d d r e s s B o o k . _ _ r e a d E n t r i e s - - - def __readEntries ( self, addrBook ): """Process the ENTRY_GI children of addrBook [ if addrBook is an Element representing the root -> self.addrList := a list of addrBook's ENTRY_GI children, as Entry objects, in document order ] """ self.addrList = getChildList ( addrBook, ENTRY_GI, Entry ) # - - - - - c l a s s E n t r y - - - - - class Entry: "Represents one element." # - - - E n t r y . _ _ i n i t _ _ - - - def __init__ ( self, entryNode ): "Constructor for Entry" #-- 1 -- # [ (self.date, self.status, self.hide) := as invariant ] self.date = getAttr ( entryNode, DATE_ATTR ) self.status = getAttr ( entryNode, STATUS_ATTR ) self.hide = getAttr ( entryNode, HIDE_ATTR ) #-- 2 -- # [ self.xrefList := a list of cross-reference strings # from self's XREF_GI children ] self.xrefList = getChildList ( entryNode, XREF_GI, textContent ) #-- 3 -- # [ self.orgList := a list of Org objects representing # self's ORG_GI children ] self.orgList = getChildList ( entryNode, ORG_GI, Org ) #-- 4 -- # [ self.personList := a list of Person objects representing # self's PERSON_GI children ] self.personList = getChildList ( entryNode, PERSON_GI, Person ) #-- 5 -- # [ self.eMailList := a list of EMail objects representing # self's E_MAIL_GI children ] self.eMailList = getChildList ( entryNode, E_MAIL_GI, EMail ) #-- 6 -- # [ self.locationList := a list of Location objects representing # self's LOCATION_GI children ] self.locationList = getChildList ( entryNode, LOCATION_GI, Location ) #-- 7 -- # [ self.phoneList := a list of Phone objects representing # self's PHONE_GI children ] self.phoneList = getChildList ( entryNode, PHONE_GI, Phone ) #-- 8 -- # [ if entryNode has a DIRECTIONS_GI child -> # self.directions := a Directions object representing # that child # else -> # self.directions := None ] dirNode = getChildIfAny ( entryNode, DIRECTIONS_GI ) if dirNode is not None: self.directions = Directions ( dirNode ) else: self.directions = None #-- 9 -- # [ if entryNode has a NOTES_GI child -> # self.notes := that child's content as a string # else -> # self.notes := None ] self.notes = getChildContent ( entryNode, NOTES_GI ) # - - - - - c l a s s I d - - - - - class Id: "Base class for Org and Person classes." pass # - - - - - c l a s s O r g - - - - - class Org(Id): "Represents an element." def __init__ ( self, orgNode ): "Constructor for Org" #-- 1 -- # [ self.name := self's text content # self.key := self's text content ] self.name = self.key = textContent ( orgNode ) #-- 2 -- # [ if orgNode has a CONTACT_GI child with text content -> # self.contact := that text content # else -> # self.contact := None ] self.contact = getChildContent ( orgNode, CONTACT_GI ) # - - - - - c l a s s P e r s o n - - - - - class Person(Id): "Represents a element." # - - - P e r s o n . _ _ i n i t _ _ - - - def __init__ ( self, personNode ): "Constructor for Person" #-- 1 -- # [ self.last := content of LAST_GI child of personNode # keyList := list containing content of LAST_GI child # of personNode ] self.last = getChildContent ( personNode, LAST_GI ) keyList = [ self.last ] #-- 2 -- # [ if personNode has a FIRST_GI child -> # self.first := its content # keyList +:= its content # else -> # self.first := None ] self.first = getChildContent ( personNode, FIRST_GI ) if self.first is not None: keyList.append ( self.first ) #-- 3 -- # [ if personNode has a NICK_GI child -> # self.nick := its content # else -> # self.nick := None ] self.nick = getChildContent ( personNode, NICK_GI ) if self.nick is not None: keyList.append ( self.nick ) #-- 4 -- # [ self.key := elements of keyList, concatenated ] self.key = "".join ( keyList ) # - - - P e r s o n . f i r s t L a s t - - - def firstLast ( self ): """Return a name like 'Don "The Tiger" Knotts'""" result = [self.last] if self.first is not None: result.insert ( 0, '%s ' % self.first ) if self.nick is not None: result.insert ( 0, '"%s" ' % self.nick ) return "".join ( result ) # - - - P e r s o n . l a s t F i r s t - - - def lastFirst ( self ): """Return a name like 'Knotts, Don "The Tiger"'""" result = [ self.last ] if self.first is not None: result.append ( ', %s' % self.first ) if self.nick is not None: result.append ( ' "%s"' % self.nick ) return "".join ( result ) # - - - - - c l a s s E M a i l - - - - - class EMail: "Represents an element." # - - - E M a i l . _ _ i n i t _ _ - - - def __init__ ( self, eMailNode ): "Constructor for EMail." #-- 1 -- # [ self.text := content of eMailNode ] self.text = textContent ( eMailNode ) #-- 2 -- # [ if eMailNode has a KIND_ATTR attribute -> # self.kind := that attribute's value # else -> # self.kind := None ] self.kind = getAttr ( eMailNode, KIND_ATTR ) # - - - - - c l a s s L o c a t i o n - - - - - class Location: "Represents a element." # - - - L o c a t i o n . _ _ a t t r _ _ - - - def __init__ ( self, locationNode ): "Constructor for Location" #-- 1 -- # [ if locationNode has a MAIL_ATTR attribute -> # self.mail := that attribute's value # else -> # self.mail := None ] self.mail = getAttr ( locationNode, MAIL_ATTR ) #-- 2 -- # [ if locationNode has a KIND_ATTR attribute -> # self.kind := that attribute's value # else -> # self.kind := None ] self.kind = getAttr ( locationNode, KIND_ATTR ) #-- 3 -- # [ self.aList := a list of the contents of locationNode's # A_GI children ] self.aList = getChildList ( locationNode, A_GI, textContent ) #-- 4 -- # [ if locationNode has a CITY_GI child -> # self.city := the content of that child # else -> # self.city := None ] self.city = getChildContent ( locationNode, CITY_GI ) #-- 5 -- # [ if locationNode has a STATE_GI child -> # self.state := the content of that child # else -> # self.state := None ] self.state = getChildContent ( locationNode, STATE_GI ) #-- 6 -- # [ if locationNode has a ZIP_GI child -> # self.zip := the content of that child # else -> # self.zip := None ] self.zip = getChildContent ( locationNode, ZIP_GI ) #-- 7 -- # [ if locationNode has a NATION_GI child -> # self.nation := the content of that child # else -> # self.nation := None ] self.nation = getChildContent ( locationNode, NATION_GI ) # - - - - - c l a s s P h o n e - - - - - class Phone: "Represents a element." # - - - P h o n e . _ _ i n i t _ _ - - - def __init__ ( self, phoneNode ): "Constructor for Phone" #-- 1 -- # [ if phoneNode has a KIND_ATTR attribute -> # self.kind := that attribute's value # else -> # self.kind := None ] self.kind = getAttr ( phoneNode, KIND_ATTR ) #-- 2 -- # [ if phoneNode has an UNLISTED_ADDR -> # self.unlisted := 1 # else -> # self.unlisted := 0 ] unAttr = getAttr ( phoneNode, UNLISTED_ATTR ) if unAttr is None: self.unlisted = 0 else: self.unlisted = 1 #-- 3 -- # [ self.text := content of phoneNode ] self.text = textContent ( phoneNode ) # - - - - - c l a s s D i r e c t i o n s - - - - - class Directions: "Represents a element." # - - - D i r e c t i o n s - - - def __init__ ( self, directionsNode ): "Constructor for Directions" #-- 1 -- self.legList = getChildList ( directionsNode, LEG_GI, Leg ) # - - - - - c l a s s L e g - - - - - class Leg: "Represents one , part of a element." # - - - L e g . _ _ i n i t _ _ - - - def __init__ ( self, legNode ): "Constructor for Leg" #-- 1 -- self.on = getChildContent ( legNode, ON_GI ) self.when = getChildContent ( legNode, WHEN_GI ) self.then = getChildContent ( legNode, THEN_GI )