#!/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 )