Diff for /ZSQLExtend/importFMPXML.py between versions 1.1 and 1.28

version 1.1, 2006/12/20 18:45:58 version 1.28, 2008/10/06 16:02:31
Line 2 Line 2
 #  #
   
 import string  import string
 from xml.dom.pulldom import parse  
 import psycopg2 as psycopg  
 import logging  import logging
 import sys  import sys
   import types
   import time
   
   from xml import sax
   from amara import saxtools
   
   try:
       import psycopg2 as psycopg
       import psycopg2.extensions
       # switch to unicode
       psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
       psyco = 2
   except:
       import psycopg
       psyco = 1
   
   fm_ns = 'http://www.filemaker.com/fmpxmlresult'
   
   version_string = "V0.6.2 ROC 6.10.2008"
   
   def unicodify(text, withNone=False):
       """decode str (utf-8 or latin-1 representation) into unicode object"""
       if withNone and text is None:
           return None
       if not text:
           return u""
       if isinstance(text, str):
           try:
               return text.decode('utf-8')
           except:
               return text.decode('latin-1')
       else:
           return text
   
   def utf8ify(text, withNone=False):
       """encode unicode object or string into byte string in utf-8 representation"""
       if withNone and text is None:
           return None
       if not text:
           return ""
       if isinstance(text, unicode):
           return text.encode('utf-8')
       else:
           return text
   
 def getTextFromNode(nodename):  def getTextFromNode(nodename):
     """get the cdata content of a node"""      """get the cdata content of a node"""
Line 27  def sql_quote(v): Line 68  def sql_quote(v):
             v=string.join(string.split(v,dkey),quote_dict[dkey])              v=string.join(string.split(v,dkey),quote_dict[dkey])
     return "'%s'" % v      return "'%s'" % v
   
 def SimpleSearch(curs,query, args=None):  def SimpleSearch(curs,query, args=None, ascii=False):
     """execute sql query and return data"""      """execute sql query and return data"""
     logging.debug("executing: "+query)      #logger.debug("executing: "+query)
       if ascii:
           # encode all in UTF-8
           query = utf8ify(query)
           if args is not None:
               encargs = []
               for a in args:
                   encargs.append(utf8ify(a, withNone=True))
               
               args = encargs
   
     curs.execute(query, args)      curs.execute(query, args)
     logging.debug("sql done")      #logger.debug("sql done")
       try:
     return curs.fetchall()      return curs.fetchall()
       except:
           return None
   
   
   class TableColumn:
       """simple type for storing sql column name and type"""
   
 def importXMLFileFMP(dsn,table,filename,update_fields=None,id_field=None,sync_mode=False):      def __init__(self, name, type=None):
     '''          #print "new tablecolumn(%s,%s)"%(name, type)
     Import FileMaker XML file (FMPXMLRESULT format) into the table.          self.name = name
     @param table: name of the table the xml shall be imported into          self.type = type
     @param filename: xmlfile filename          
     @param update_fields: (optional) list of fields to update; default is to create all fields      def getName(self):
     @param id_field: (optional) field which uniquely identifies an entry for updating purposes.          return self.name
     @param sync_mode: (optional) really synchronise, i.e. delete entries not in XML file      
     '''      def getType(self):
           if self.type is not None:
               return self.type
           else:
               return "text"
   
       def __str__(self):
           return self.name
       
       
   class xml_handler:
       def __init__(self,options):
           """SAX handler to import FileMaker XML file (FMPXMLRESULT format) into the table.
           @param options: dict of options
           @param options.dsn: database connection string
           @param options.table: name of the table the xml shall be imported into
           @param options.filename: xmlfile filename
           @param options.update_fields: (optional) list of fields to update; default is to create all fields
           @param options.id_field: (optional) field which uniquely identifies an entry for updating purposes.
           @param options.sync_mode: (optional) really synchronise, i.e. delete entries not in XML file
           @param options.lc_names: (optional) lower case and clean up field names from XML
           @param options.keep_fields: (optional) don't add fields to SQL database
           @param options.ascii_db: (optional) assume ascii encoding in db
           @param options.replace_table: (optional) delete and re-insert data
           @param options.backup_table: (optional) create backup of old table (breaks indices)
           @param options.use_logger_instance: (optional) use this instance of a logger
           """
           
           # set up logger
           if hasattr(options, 'use_logger_instance'):
               self.logger = options.use_logger_instance
           else:
               self.logger = logging.getLogger('db.import.fmpxml')
   
           
           # set up parser
           self.event = None
           self.top_dispatcher = { 
               (saxtools.START_ELEMENT, fm_ns, u'METADATA'): 
               self.handle_meta_fields,
               (saxtools.START_ELEMENT, fm_ns, u'RESULTSET'): 
               self.handle_data_fields,
               }
   
     # connect database      # connect database
     dbCon = psycopg.connect(dsn)          self.dbCon = psycopg.connect(options.dsn)
     db = dbCon.cursor()          logging.debug("DB encoding: %s"%getattr(self.dbCon, 'encoding', 'UNKNOWN'))
     assert db, "AIIEE no db cursor for %s!!"%dsn          self.db = self.dbCon.cursor()
           assert self.db, "AIIEE no db cursor for %s!!"%options.dsn
     # read XML file      
     fh=file(filename)          self.table = getattr(options,"table",None)
     logging.info("reading file "+filename)          self.update_fields = getattr(options,"update_fields",None)
     doc=parse(fh)          self.id_field = getattr(options,"id_field",None)
     logging.info("file read")          self.sync_mode = getattr(options,"sync_mode",None)
           self.lc_names = getattr(options,"lc_names",None)
     dbIDs = {}          self.keep_fields = getattr(options,"keep_fields",None)
     rowcnt = 0          self.ascii_db = getattr(options,"ascii_db",None)
     ret = ""          self.replace_table = getattr(options,"replace_table",None)
               self.backup_table = getattr(options,"backup_table",None)
     logging.debug("dsn: "+dsn)          self.read_before_update = getattr(options,"read_before_update",None)
     logging.debug("table: "+table)          self.debug_data = getattr(options,"debug_data",None)
     logging.debug("update_fields: "+repr(update_fields))  
     logging.debug("id_field: "+id_field)          self.logger.debug("dsn: "+repr(getattr(options,"dsn",None)))
     logging.debug("sync_mode: "+repr(sync_mode))          self.logger.debug("table: "+repr(self.table))
           self.logger.debug("update_fields: "+repr(self.update_fields))
           self.logger.debug("id_field: "+repr(self.id_field))
           self.logger.debug("sync_mode: "+repr(self.sync_mode))
           self.logger.debug("lc_names: "+repr(self.lc_names))
           self.logger.debug("keep_fields: "+repr(self.keep_fields))
           self.logger.debug("ascii_db: "+repr(self.ascii_db))
           self.logger.debug("replace_table: "+repr(self.replace_table))
           self.logger.debug("backup_table: "+repr(self.backup_table))
           self.logger.debug("read_before_update: "+repr(self.read_before_update))
           self.logger.debug("debug_data: "+repr(self.debug_data))
           
     if id_field is not None:          self.dbIDs = {}
           self.rowcnt = 0
           
           if self.id_field is not None:
         # prepare a list of ids for sync mode          # prepare a list of ids for sync mode
         qstr="select %s from %s"%(id_field,table)              qstr="select %s from %s"%(self.id_field,self.table)
         for id in SimpleSearch(db, qstr):              for id in SimpleSearch(self.db, qstr):
             # value 0: not updated              # value 0: not updated
             dbIDs[id[0]] = 0;                  self.dbIDs[id[0]] = 0;
             rowcnt += 1                  self.rowcnt += 1
               
         logging.info("%d entries in DB to sync"%rowcnt)  
           
     fieldNames = []              self.logger.info("%d entries in DB to sync"%self.rowcnt)
     rowcnt = 0  
     id_val = ''  
           
     while 1:          # names of fields in XML file
         node=doc.getEvent()          self.xml_field_names = []
           # map XML field names to SQL field names
           self.xml_field_map = {}
           # and vice versa
           self.sql_field_map = {}
           
           return
   
       def handle_meta_fields(self, end_condition):
           dispatcher = {
               (saxtools.START_ELEMENT, fm_ns, u'FIELD'):
               self.handle_meta_field,
               }
           #First round through the generator corresponds to the
           #start element event
           self.logger.info("reading metadata...")
           self.logger.debug("START METADATA")
           yield None
       
           #delegate is a generator that handles all the events "within"
           #this element
           delegate = None
           while not self.event == end_condition:
               delegate = saxtools.tenorsax.event_loop_body(
                   dispatcher, delegate, self.event)
               yield None
           
           #Element closed. Wrap up
           self.logger.debug("END METADATA")
           
           # rename table for backup
           if self.backup_table:
               self.orig_table = self.table
               self.tmp_table = self.table + "_tmp"
               backup_name = "%s_%s"%(self.table,time.strftime('%Y_%m_%d_%H_%M_%S'))
               
               # remove old temp table
               qstr = "DROP TABLE %s"%(self.tmp_table)
               try:
                   self.db.execute(qstr)
               except:
                   pass
               
               self.dbCon.commit()
              
               if self.id_field:
                   # sync mode -- copy backup table, update current table 
                   self.logger.info("copy table %s to %s"%(self.table,backup_name))
                   qstr = "CREATE TABLE %s AS (SELECT * FROM %s)"%(backup_name,self.table)
           
         if node is None:              else:
             break;                  # replace mode -- create empty tmp table, insert into tmp table
                   self.table = self.tmp_table
                   self.logger.info("create empty table %s"%(self.table))
                   qstr = "CREATE TABLE %s AS (SELECT * FROM %s WHERE 1=0)"%(self.table,self.orig_table)
               
               self.db.execute(qstr)
               self.dbCon.commit()
           
           # delete data from table for replace
           if self.replace_table:
               self.logger.info("delete data from table %s"%(self.table))
               qstr = "TRUNCATE TABLE %s"%(self.table)
               self.db.execute(qstr)
               self.dbCon.commit()
              
           # try to match date style with XML
           self.db.execute("set datestyle to 'german'")
           
           #self.logger.debug("xml-fieldnames:"+repr(self.xml_field_names))
           # get list of fields and types of db table
           qstr="select attname, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) from pg_attribute, pg_class where attrelid = pg_class.oid and pg_attribute.attnum > 0 and relname = '%s'"
           self.sql_fields={}
           for f in SimpleSearch(self.db, qstr%self.table):
               fn = f[0]
               ft = f[1]
               #print "SQL fields: %s (%s)"%(n,t)
               self.sql_fields[fn] = TableColumn(fn,ft)
           
           # translate id_field (SQL-name) to XML-name
           self.xml_id = self.sql_field_map.get(self.id_field, None)
           # get type of id_field
           if self.id_field:
               self.id_type = self.sql_fields[self.id_field].getType()
           else:
               self.id_type = None
                   
         # METADATA tag defines number and names of fields in FMPXMLRESULT          # check fields to update
         if node[1].nodeName == 'METADATA':          if self.update_fields is None:
             doc.expandNode(node[1])              if self.keep_fields:
                   # update all existing fields from sql (when they are in the xml file)
                   self.update_fields = {}
                   for f in self.sql_fields.keys():
                       if self.sql_field_map.has_key(f):
                           xf = self.sql_field_map[f]
                           self.update_fields[f] = self.xml_field_map[xf]
                   
             names=node[1].getElementsByTagName('FIELD')              else:
                   # update all fields
                   if self.lc_names:
                       # create dict with sql names
                       self.update_fields = {}
                       for f in self.xml_field_map.values():
                           self.update_fields[f.getName()] = f
   
             for name in names:                  else:
                 fn = name.getAttribute('NAME')                      self.update_fields = self.xml_field_map
                 fieldNames.append(fn)  
                           
             if update_fields is None:          # and translate to list of xml fields
                 # update all fields          if self.lc_names:
                 update_fields = fieldNames              self.xml_update_list = [self.sql_field_map[x] for x in self.update_fields]
           else:
               self.xml_update_list = self.update_fields.keys()
                           
             logging.debug("fieldnames:"+repr(fieldNames))          if not self.keep_fields:
             # get list of fields in db table              # adjust db table to fields in XML and update_fields
             qstr="""select attname from pg_attribute, pg_class where attrelid = pg_class.oid and relname = '%s'"""              for f in self.xml_field_map.values():
             columns=[x[0] for x in SimpleSearch(db, qstr%table)]                  self.logger.debug("sync-fieldname: %s"%f.getName())
                               sf = self.sql_fields.get(f.getName(), None)
             # adjust db table to fields in XML and fieldlist                  uf = self.update_fields.get(f.getName(), None)
             for fieldName in fieldNames:                  if sf is not None:
                 logging.debug("db-fieldname:"+repr(fieldName))                                           # name in db -- check type
                 if (fieldName not in columns) and (fieldName in update_fields):                      if f.getType() != sf.getType():
                     qstr="alter table %s add %%s %%s"%table                          self.logger.debug("field %s has different type (%s vs %s)"%(f,f.getType(),sf.getType()))
                     logging.info("db add field:"+qstr%(fieldName,'text'))                  elif uf is not None:
                     db.execute(qstr, (fieldName,'text'))                      # add field to table
                     db.commit()                      fn = uf.getName()
                                           ft = uf.getType()
                               qstr="alter table %s add \"%s\" %s"%(self.table,fn,ft)
         # ROW tags (in RESULTSET tag) hold data                      self.logger.info("db add field:"+qstr)
         elif node[1].nodeName == 'ROW':                      
             rowcnt += 1                      if self.ascii_db and type(qstr)==types.UnicodeType:
                                       qstr=qstr.encode('utf-8')
             doc.expandNode(node[1])                          
             cols=node[1].getElementsByTagName('COL')                      self.db.execute(qstr)
             dataSet={}                      self.dbCon.commit()
             i = 0                      # add field to field list
             # populate with data                      self.sql_fields[fn] = TableColumn(fn, ft)
             for col in cols:                  
                 data=col.getElementsByTagName('DATA')          # prepare sql statements for update (do not update id_field)
                 dataSet[fieldNames[i]] = getTextFromNode(data[0])          setStr=string.join(["\"%s\" = %%s"%self.xml_field_map[f] for f in self.xml_update_list if f != self.xml_id], ', ')
                 i += 1          self.updQuery="UPDATE %s SET %s WHERE \"%s\" = %%s"%(self.table,setStr,self.id_field)
           # and select (for update check)
           selStr=string.join([self.xml_field_map[f].getName() for f in self.xml_update_list if f != self.xml_id], ', ')
           self.selQuery="SELECT %s FROM %s WHERE \"%s\" = %%s"%(selStr,self.table,self.id_field)
           # and insert
           fields=string.join(["\"%s\""%self.xml_field_map[x].getName() for x in self.xml_update_list], ',')
           values=string.join(['%s' for f in self.xml_update_list], ',')
           self.addQuery="INSERT INTO %s (%s) VALUES (%s)"%(self.table,fields,values)
           self.logger.debug("update-query: "+self.updQuery)
           self.logger.debug("sel-query: "+self.selQuery)
           self.logger.debug("add-query: "+self.addQuery)
           return
   
       def handle_meta_field(self, end_condition):
           name = self.params.get((None, u'NAME'))
           yield None
           #Element closed.  Wrap up
           if self.lc_names:
               # clean name
               sqlname = name.replace(" ","_").lower() 
           else:
               sqlname = name
           self.xml_field_names.append(name)
           # map to sql name and default text type
           self.xml_field_map[name] = TableColumn(sqlname, 'text')
           self.sql_field_map[sqlname] = name
           self.logger.debug("FIELD name: "+name)
           return
   
       def handle_data_fields(self, end_condition):
           dispatcher = {
               (saxtools.START_ELEMENT, fm_ns, u'ROW'):
               self.handle_row,
               }
           #First round through the generator corresponds to the
           #start element event
           self.logger.info("reading data...")
           self.logger.debug("START RESULTSET")
           self.rowcnt = 0
           yield None
       
           #delegate is a generator that handles all the events "within"
           #this element
           delegate = None
           while not self.event == end_condition:
               delegate = saxtools.tenorsax.event_loop_body(
                   dispatcher, delegate, self.event)
               yield None
           
           #Element closed.  Wrap up
           self.logger.debug("END RESULTSET")
           self.dbCon.commit()
                                   
           if self.sync_mode:
               # delete unmatched entries in db
               self.logger.info("deleting unmatched rows from db")
               delQuery = "DELETE FROM %s WHERE \"%s\" = %%s"%(self.table,self.id_field)
               for id in self.dbIDs.keys():
                   # find all not-updated fields
                   if self.dbIDs[id] == 0:
                       self.logger.info(" delete: %s"%id)
                       SimpleSearch(self.db, delQuery, [id], ascii=self.ascii_db)
                       
                   elif self.dbIDs[id] > 1:
                       self.logger.info(" sync: ID %s used more than once?"%id)
               
               self.dbCon.commit()
               
           # reinstate backup tables
           if self.backup_table and not self.id_field:
               backup_name = "%s_%s"%(self.orig_table,time.strftime('%Y_%m_%d_%H_%M_%S'))
               self.logger.info("rename backup table %s to %s"%(self.orig_table,backup_name))
               qstr = "ALTER TABLE %s RENAME TO %s"%(self.orig_table,backup_name)
               self.db.execute(qstr)
               self.logger.info("rename working table %s to %s"%(self.table,self.orig_table))
               qstr = "ALTER TABLE %s RENAME TO %s"%(self.table,self.orig_table)
               self.db.execute(qstr)
               self.dbCon.commit()
           
           return
   
       def handle_row(self, end_condition):
           dispatcher = {
               (saxtools.START_ELEMENT, fm_ns, u'COL'):
               self.handle_col,
               }
           self.logger.debug("START ROW")
           self.xml_data = {}
           self.colIdx = 0
           yield None
       
           #delegate is a generator that handles all the events "within"
           #this element
           delegate = None
           while not self.event == end_condition:
               delegate = saxtools.tenorsax.event_loop_body(
                   dispatcher, delegate, self.event)
               yield None
           
           #Element closed.  Wrap up
           self.logger.debug("END ROW")
           self.rowcnt += 1
           # process collected row data
             update=False              update=False
                       id_val=''
             # synchronize by id_field              # synchronize by id_field
             if id_field:          if self.id_field:
                 id_val=dataSet[id_field]              if self.id_type == 'integer':
                 if id_val in dbIDs:                  id_val = int(self.xml_data[self.xml_id])
                     dbIDs[id_val] += 1              else:
                   id_val = self.xml_data[self.xml_id]
                   
               if id_val in self.dbIDs:
                   self.dbIDs[id_val] += 1
                     update=True                      update=True
                           
             if update:          # collect all values
                 # update existing row (by id_field)          args = []
                 setvals=[]          for fn in self.xml_update_list:
                 for fieldName in update_fields:              # do not update id_field
                     setvals.append("%s = %s"%(fieldName,sql_quote(dataSet[fieldName])))              if update and fn == self.xml_id:
                 setStr=string.join(setvals, ',')                  continue
                 id_val=dataSet[id_field]              
                 qstr="UPDATE %s SET %s WHERE %s = '%s' "%(table,setStr,id_field,id_val)              f = self.xml_field_map[fn]
                 db.execute(qstr)              val = self.xml_data[fn]
                 ret += "up: %s"%id_val              type = self.sql_fields[f.getName()].getType()
             else:              if type == "date" and len(val) == 0: 
                 # create new row                  # empty date field
                 fields=string.join(update_fields, ',')                  val = None
                 values=string.join([" %s "%sql_quote(dataSet[x]) for x in update_fields], ',')                  
                 qstr="INSERT INTO %s (%s) VALUES (%s)"%(table,fields,values)              elif type == "integer" and len(val) == 0: 
                 db.execute(qstr)                  # empty int field
                 ret += "ad: %s"%dataSet.get(id_field, rowcnt)                  val = None
   
             #logging.info(" row:"+"%d (%s)"%(rowcnt,id_val))  
             if (rowcnt % 10) == 0:  
                 logging.info(" row:"+"%d (%s)"%(rowcnt,id_val))  
                 dbCon.commit()  
   
     dbCon.commit()              args.append(val)
     if sync_mode:  
         # delete unmatched entries in db  
         for id in dbIDs.keys():  
             # find all not-updated fields  
             if dbIDs[id] == 0:  
                 logging.info(" delete:"+id)  
                 qstr = "DELETE FROM %s WHERE %%s = '%%s'"%table  
                 db.execute(qstr, (id_field,id))  
                                   
             elif dbIDs[id] > 1:          if update:
                 logging.info(" sync:"+"id used more than once?"+id)              # update existing row (by id_field)
               if self.read_before_update:
                   # read data
                   if self.debug_data:
                       self.logger.debug("update check: %s = %s"%(id_val, args))
                   oldrow = SimpleSearch(self.db, self.selQuery, [id_val], ascii=self.ascii_db)
                   #i = 0
                   #for v in oldrow[0]:
                   #    logging.debug("v: %s = %s (%s)"%(v,args[i],v==args[i]))
                   #    i += 1
                   if tuple(oldrow[0]) != tuple(args):
                       # data has changed -- update
                       if self.debug_data:
                           self.logger.debug("really update: %s = %s"%(id_val, args))
                       args.append(id_val) # last arg is id
                       SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db)
                   
         dbCon.commit()              else:
                   # always update
                   if self.debug_data:
                       self.logger.debug("update: %s = %s"%(id_val, args))
                   args.append(id_val) # last arg is id
                   SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db)
           
     return ret          else:
               # create new row
               if self.debug_data:
                   self.logger.debug("insert: %s"%args)
               SimpleSearch(self.db, self.addQuery, args, ascii=self.ascii_db)
   
           #self.logger.info(" row:"+"%d (%s)"%(self.rowcnt,id_val))
           if (self.rowcnt % 100) == 0:
               self.logger.info(" row:"+"%d (id:%s)"%(self.rowcnt,id_val))
               self.dbCon.commit()
               
           return
   
       def handle_col(self, end_condition):
           dispatcher = {
               (saxtools.START_ELEMENT, fm_ns, u'DATA'):
               self.handle_data_tag,
               }
           #print "START COL"
           yield None
           #delegate is a generator that handles all the events "within"
           #this element
           delegate = None
           while not self.event == end_condition:
               delegate = saxtools.tenorsax.event_loop_body(
                   dispatcher, delegate, self.event)
               yield None
           #Element closed.  Wrap up
           #print "END COL"
           self.colIdx += 1
           return
   
       def handle_data_tag(self, end_condition):
           #print "START DATA"
           content = u''
           yield None
           # gather child elements
           while not self.event == end_condition:
               if self.event[0] == saxtools.CHARACTER_DATA:
                   content += self.params
               yield None
           #Element closed.  Wrap up
           fn = self.xml_field_names[self.colIdx]
           self.xml_data[fn] = content
           return
   
   
   def importFMPXML(options):
       """import FileMaker XML file (FMPXMLRESULT format) into the table.     
           @param options: dict of options
           @param options.dsn: database connection string
           @param options.table: name of the table the xml shall be imported into
           @param options.filename: xmlfile filename
           @param options.update_fields: (optional) list of fields to update; default is to create all fields
           @param options.id_field: (optional) field which uniquely identifies an entry for updating purposes.
           @param options.sync_mode: (optional) really synchronise, i.e. delete entries not in XML file
           @param options.lc_names: (optional) lower case and clean up field names from XML
           @param options.keep_fields: (optional) don't add fields to SQL database
           @param options.ascii_db: (optional) assume ascii encoding in db
           @param options.replace_table: (optional) delete and re-insert data
           @param options.backup_table: (optional) create backup of old table
           """
           
       if getattr(options,'update_fields',None):
           uf = {}
           for f in options.update_fields.split(','):
               if f.find(':') > 0:
                   (n,t) = f.split(':')
               else:
                   n = f
                   t = None
               uf[n] = TableColumn(n,t)
               
           options.update_fields = uf
       
       if getattr(options,'id_field',None) and getattr(options,'replace_table',None):
           logging.error("ABORT: sorry, you can't do both sync (id_field) and replace")
           return
           
       parser = sax.make_parser()
       #The "consumer" is our own handler
       consumer = xml_handler(options)
       #Initialize Tenorsax with handler
       handler = saxtools.tenorsax(consumer)
       #Resulting tenorsax instance is the SAX handler 
       parser.setContentHandler(handler)
       parser.setFeature(sax.handler.feature_namespaces, 1)
       parser.parse(options.filename)  
   
 ##  
 ## public static int main()  
 ##  
   
   if __name__ == "__main__":
 from optparse import OptionParser  from optparse import OptionParser
   
 opars = OptionParser()  opars = OptionParser()
Line 196  opars.add_option("-t", "--table", Line 596  opars.add_option("-t", "--table",
                  help="database table name")                   help="database table name")
 opars.add_option("--fields", default=None,   opars.add_option("--fields", default=None, 
                  dest="update_fields",                    dest="update_fields", 
                  help="list of fields to update (comma separated)", metavar="LIST")                       help="list of fields to update (comma separated, sql-names)", metavar="LIST")
 opars.add_option("--id-field", default=None,   opars.add_option("--id-field", default=None, 
                  dest="id_field",                    dest="id_field", 
                  help="name of id field for synchronisation", metavar="NAME")                       help="name of id field for synchronisation (only appends data otherwise, sql-name)", metavar="NAME")
 opars.add_option("--sync-mode", default=False, action="store_true",       opars.add_option("--sync", "--sync-mode", default=False, action="store_true", 
                  dest="sync_mode",                    dest="sync_mode", 
                  help="do full sync (remove unmatched fields from db)")                       help="do full sync based on id field (remove unmatched fields from db)")
       opars.add_option("--lc-names", default=False, action="store_true", 
                        dest="lc_names", 
                        help="clean and lower case field names from XML")
       opars.add_option("--keep-fields", default=False, action="store_true", 
                        dest="keep_fields", 
                        help="don't add fields from XML to SQL table")
       opars.add_option("--ascii-db", default=False, action="store_true", 
                        dest="ascii_db", 
                        help="the SQL database stores ASCII instead of unicode")
       opars.add_option("--replace", default=False, action="store_true", 
                        dest="replace_table", 
                        help="replace table i.e. delete and re-insert data")
       opars.add_option("--backup", default=False, action="store_true", 
                        dest="backup_table", 
                        help="create backup of old table")
       opars.add_option("--read-before-update", default=False, action="store_true", 
                        dest="read_before_update", 
                        help="read all data to check if it really changed")
 opars.add_option("-d", "--debug", default=False, action="store_true",   opars.add_option("-d", "--debug", default=False, action="store_true", 
                  dest="debug",                    dest="debug", 
                  help="debug mode (more output)")                   help="debug mode (more output)")
       opars.add_option("--debug-data", default=False, action="store_true", 
                        dest="debug_data", 
                        help="debug mode for data (even more output)")
   
 (options, args) = opars.parse_args()  (options, args) = opars.parse_args()
   
 if len(sys.argv) < 2 or options.filename is None or options.dsn is None:  if len(sys.argv) < 2 or options.filename is None or options.dsn is None:
           print "importFMPXML "+version_string
     opars.print_help()      opars.print_help()
     sys.exit(1)      sys.exit(1)
   
Line 222  logging.basicConfig(level=loglevel, Line 644  logging.basicConfig(level=loglevel,
                     format='%(asctime)s %(levelname)s %(message)s',                      format='%(asctime)s %(levelname)s %(message)s',
                     datefmt='%H:%M:%S')                      datefmt='%H:%M:%S')
   
 update_fields = None      importFMPXML(options)
   
   
 if options.update_fields:  
     update_fields = [string.strip(s) for s in options.update_fields.split(',')]  
             
 importXMLFileFMP(dsn=options.dsn,table=options.table,filename=options.filename,  
                  update_fields=update_fields,id_field=options.id_field,  
                  sync_mode=options.sync_mode)  
   
 print "DONE!"  

Removed from v.1.1  
changed lines
  Added in v.1.28


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>