#!/usr/local/bin/python #-- # eltab.py: Use Python minidom to display `allelements.xml' as a table. # $Revision: 1.5 $ $Date: 2002/05/08 04:02:59 $ #-- # Input: file allelements.xml, which has these tags we care about: # # # Aluminum # 26.98154 # 13 # 2740 # 933.5 # Al # ... # # #-- # Output: A text table, sorted by symbol, with this format: # # 0 1 2 3 4 5 6 # 0123456789012345678901234567890123456789012345678901234567890 # ============================================================= # Atomic Atomic Melting Boiling # Number Symbol Name Weight Point Point # ------ ------ --------------- --------- ---------- ---------- # 89 Ac Actinium 227.00000 3470.00 # 13 Al Aluminum 26.98154 933.50 2740.00 # 95 Am Americium 243.00000 1449.00 2880.00 # ... #-- #================================================================ # Blanket precondition: the `allelements.xml' is formed as shown # above. The node structure looks like this: # Document (root) # Element # Element # Element # #text # Element # #text # and so forth. Note that and # are optional since they are unknown for some elements. #---------------------------------------------------------------- #================================================================ # Imports #---------------------------------------------------------------- import time # Timing routines import sys # Standard system module import xml.dom.minidom # Mini-Document Object Module # - - - t i m e r - - - def timer ( legend ): """Outputs current CPU time with the given legend string. """ sys.stderr.write ( "=== %9.2f sec: %s\n" % ( time.clock(), legend ) ) # - - - g e t C o n t e n t - - - def getContent ( node, childName ): """Extracts the content of #text children of an Element node [ if (node is a minidom Element object) and (childName is a string) -> if (node has a child whose name matches childName) and (that child has at least one #text child) -> return the content from the last #text child as a string else -> return None ] """ #-- 1 -- # [ if node has a child whose name matches childName -> # child := an Element node representing that child # else -> raise ValueError ] childList = node.getElementsByTagName ( childName ) if len ( childList ) < 1: return None else: child = childList[0] #-- 2 -- # [ if child has a child of type #text -> # textNode := a Text object representing the last such child # else -> raise ValueError ] grandkids = child.childNodes textNode = None for n in grandkids: if n.nodeName == "#text": textNode = n if textNode is None: return None #-- 3 -- # NB: The `str()' function is necessary to convert Unicode to # regular strings. return str(textNode.data) # - - - g e t I n t C o n t e n t - - - def getIntContent ( node, childName ): """Like getContent(), but requires that the text be a valid int. [ if (node is a minidom Element object) and (childName is a string) -> if (node has a child whose name matches childName and which has text content that is a valid integer -> return that content as an integer else -> raise ValueError ] """ #-- 1 - # [ if (node has a child whose name matches childName) # and (that child has at least one #text child) -> # raw := the content from the last #text child as a string # else -> raise ValueError ] raw = getContent ( node, childName ) if raw is None: raise ValueError, ( "Node <%s>, missing <%s> or bad content" % ( node.nodeName, childName ) ) #-- 2 -- # [ if raw is a valid integer -> # return raw as a integer # else -> raise ValueError ] return int ( raw ) # - - - g e t F l o a t C o n t e n t - - - def getFloatContent ( node, childName ): """Like getContent(), but requires that the text be a valid float. [ if (node is a minidom Element object) and (childName is a string) -> if (node has a child whose name matches childName and which has text content that is a valid float -> return that content as a float else -> raise ValueError ] """ #-- 1 - # [ if (node has a child whose name matches childName) # and (that child has at least one #text child) -> # raw := the content from the last #text child as a string # else -> raise ValueError ] raw = getContent ( node, childName ) if raw is None: raise ValueError, ( "Node <%s> has no <%s> children" % ( node.nodeName, childName ) ) #-- 2 -- # [ if raw is a valid float -> # return raw as a float # else -> raise ValueError ] return float ( raw ) # - - - - - c l a s s P e r i o d i c T a b l e - - - - - class PeriodicTable: """Represents the entire elements file. Exports: Periodic_Table ( sourceFile ): [ if sourceFile is a string -> if sourceFile names a readable, valid XML elements file -> return a new Periodic_Table object representing that file else -> raise IOError ] .symbolList(): [ return a list of all atomic symbols in self ] .getSymbol ( s ): [ if s is a string -> if s is an atomic symbol in self -> return an Atom object for that atomic symbol else -> raise KeyError ] State/Invariants: .doc: [ a minidom Document object representing self.sourceFile ] .symbolMap: [ a dictionary whose keys are the atomic symbols in self such that self.symbolMap(symbol) == an Atom object representing the element with that symbol ] """ # - - - P e r i o d i c _ T a b l e . _ _ i n i t _ _ - - - def __init__ ( self, sourceFile ): """Constructor for Periodic_Table """ #-- 1 -- # [ if sourceFile is a readable, valid XML file -> # self.doc := a Document object representing that file # else -> # raise IOError ] timer ( "Before parsing document" ) try: self.doc = xml.dom.minidom.parse ( sourceFile ) except Exception, detail: raise IOError, ( "Failure to parse `%s': %s" % ( sourceFile, str(detail) ) ) timer ( "After parsing document" ) #-- 2 -- # [ self.symbolMap := a new, empty dictionary # pt := the element from self.doc ] self.symbolMap = {} pt = self.doc.childNodes[0] #-- 3 -- # [ self.symbolMap := self.symbolMap with all atoms from # pt added as Atom objects ] atomNodeList = pt.childNodes for atomNode in atomNodeList: #-- 3 body -- # [ if kid is an Element -> # self.symbolMap +:= a new entry mapping the symbol # from kid |-> an Atom representing kid # else -> I ] if atomNode.nodeName == "ATOM": self.__addAtom ( atomNode ) timer ( "All data structures built" ) # - - - P e r i o d i c _ T a b l e . _ _ a d d A t o m - - - def __addAtom ( self, atomNode ): """Process one atom's data from the document. [ if atomNode is an node from the document -> self.symbolMap +:= a new entry mapping the symbol from atomNode |-> an Atom representing atomNode ] """ #-- 1 -- # [ atom := a new Atom object representing atomNode and # its descendants ] atom = Atom ( atomNode ) #-- 2 -- self.symbolMap [ atom.symbol ] = atom # - - - P e r i o d i c T a b l e . s y m b o l L i s t - - - def symbolList ( self ): return self.symbolMap.keys() # - - - P e r i o d i c T a b l e . g e t S y m b o l - - - def getSymbol ( self, symbol ): return self.symbolMap[symbol] # - - - - - c l a s s A t o m - - - - - class Atom: """Represents one element in the periodic table. Exports: Atom ( atomNode ): [ if atomNode is a minidom Element object representing the ... element from the input file -> return a new Atom object representing that element ] .atomicNo: [ self's atomic number as an integer ] .symbol: [ self's atomic symbol as a string ] .name: [ self's atomic name as a string ] .weight: [ self's atomic weight as a float ] .melt: [ if self's melting point is known -> self's melting point in Kelvin as a float else -> None ] .boil: [ if self's boiling point is known -> self's boiling point in Kelvin as a float else -> None ] cmp(): [ comparison function, orders elements by .symbol ] """ # - - - A t o m . _ _ i n i t _ _ - - - def __init__ ( self, atomNode ): """Constructor for Atom """ #-- 1 -- # [ if atomNode has a child -> # self.name := that child's text content ] self.name = getContent ( atomNode, "NAME" ) #-- 2 -- # [ if atomNode has an child -> # self.atomicNo := that child's text content as an integer ] self.atomicNo = getIntContent ( atomNode, "ATOMIC_NUMBER" ) #-- 3 -- # [ if atomNode has a child -> # self.symbol := that child's text content ] self.symbol = getContent ( atomNode, "SYMBOL" ) #-- 4 -- # [ if atomNode has an child -> # self.weight := that child's text content as a float ] self.weight = getFloatContent ( atomNode, "ATOMIC_WEIGHT" ) #-- 5 -- # [ if atomNode has an child -> # self.weight := that child's text content as a float ] try: self.melt = getFloatContent ( atomNode, "MELTING_POINT" ) except ValueError: self.melt = None #-- 6 -- # [ if atomNode has an child -> # self.weight := that child's text content as a float ] try: self.boil = getFloatContent ( atomNode, "BOILING_POINT" ) except ValueError: self.boil = None # - - - A t o m . _ _ c m p _ _ - - - def __cmp__ ( self, other ): """Comparison function: sorts by .symbol field. """ return cmp ( self.symbol, other.symbol ) # - - - r e p o r t H e a d e r - - - NUMBER_L = 6 # Length of atomic number field SYMBOL_L = 6 # Length of atomic symbol field NAME_L = 15 # Length of name field WEIGHT_L = 9 # Length of weight field WEIGHT_P = 5 # Precision of weight field MELT_L = 10 # Length of melting point field MELT_P = 2 # Precision of melting point field BOIL_L = 10 # Length of boiling point field BOIL_P = 2 # Precision of boiling point field def reportHeader ( ): """Print heading lines [ if atom is an Atom object -> +:= a report line representing atom ] """ print ( "%s %s %s %s %s %s" % ( "Atomic".center(NUMBER_L), "".center(SYMBOL_L), "".center(NAME_L), "Atomic".center(WEIGHT_L), "Melting".center(MELT_L), "Boiling".center(BOIL_L) ) ) print ( "%s %s %s %s %s %s" % ( "Number".center(NUMBER_L), "Symbol".center(SYMBOL_L), "Name".ljust(NAME_L), "Weight".center(WEIGHT_L), "Point".center(MELT_L), "Point".center(BOIL_L) ) ) print ( "%s %s %s %s %s %s" % ( "-" * NUMBER_L, "-" * SYMBOL_L, "-" * NAME_L, "-" * WEIGHT_L, "-" * MELT_L, "-" * BOIL_L ) ) # - - - r e p o r t D e t a i l - - - def reportDetail ( atom ): """Write one line of the atomic report [ if atom is an Atom object -> +:= a line of our output report representing atom ] """ if atom.melt is None: rawMelt = " " * MELT_L else: rawMelt = "%*.*f" % ( MELT_L, MELT_P, atom.melt ) if atom.boil is None: rawBoil = " " * BOIL_L else: rawBoil = "%*.*f" % ( BOIL_L, BOIL_P, atom.boil ) print ( "%*d %*s %*s %*.*f %s %s" % ( NUMBER_L, atom.atomicNo, - SYMBOL_L, atom.symbol, - NAME_L, atom.name, WEIGHT_L, WEIGHT_P, atom.weight, rawMelt, rawBoil ) ) # - - - - - m a i n - - - - - #-- 1 -- # [ if "allelements.xml" is a readable XML file in the periodic table # format -> # pt := a new PeriodicTable object that represents that file # else -> # +:= traceback # terminate execution ] pt = PeriodicTable ( "allelements.xml" ) #-- 2 -- # [ +:= column headings ] reportHeader ( ) #-- 3 -- # [ +:= lines for pt's atoms, sorted by symbol ] symbolList = pt.symbolList() symbolList.sort() for symbol in symbolList: #-- 3 body -- # [ +:= a line representing the atom from pt # with symbol (symbol) ] #-- 3.1 -- # [ atom := the Atom object from pt with symbol (symbol) ] atom = pt.getSymbol ( symbol ) #-- 3.2 -- # [ if atom is an Atom object -> # +:= a line representing atom ] reportDetail ( atom )