使用Python xml.sax解析XML:如何“跟踪”您在树中的位置?

2 投票
4 回答
5847 浏览
提问于 2025-04-16 14:19

我需要定期从我们的管理软件中导出XML文件。

这是我第一次在Python中使用XML解析。用xml.sax处理XML并不是特别难,但我想知道,怎么才能“跟踪”我在XML树中的位置呢?

举个例子,我有一个客户列表。我想提取电话号码,但在XML中有多个地方出现了电话号码:

eExact -> Accounts -> Account -> Contacts -> Contact -> Addresses -> Address  -> Phone
eExact -> Accounts -> Account -> Contacts -> Contact -> Phone
eExact -> Accounts -> Account -> Phone

所以我需要知道自己在XML树的哪个位置,才能获取到正确的电话号码。

根据我从Python网站上查到的xml.sax文档,似乎没有什么“简单”的方法或变量可以用来记录这个位置。

所以,我做了以下的事情:

import xml.sax

class Exact(xml.sax.handler.ContentHandler):
  def __init__(self):
    self.curpath = []

  def startElement(self, name, attrs):
    self.curpath.append(name)

    if name == 'Phone':
      print self.curpath, name

  def endElement(self, name):
    self.curpath.pop()

if __name__ == '__main__':
  parser = xml.sax.make_parser()
  handler = Exact()
  parser.setContentHandler(handler)
  parser.parse(open('/home/cronuser/xml/mount/daily/debtors.xml'))

这并不算太难,但因为我对XML的经验不多,我在想这样做是否是“普遍认可”或“最佳的”方式呢?

谢谢 :)

4 个回答

2

你为什么一定要用SAX呢?

因为如果把整个XML文件加载到内存中的对象模型里是可以接受的,那你可能会发现使用ElementTree DOM API要简单得多。

(如果你不需要通过子节点来获取父节点,Python标准库里的cElementTree就能很好地满足你的需求。如果你需要这个功能,LXML库提供的ElementTree实现可以让你获取父节点的引用。这两个都使用了编译的C模块来提高速度。)

4

我之前也用过sax,但后来发现了一个更好的工具:ElementTree里的iterparse

这个工具和sax有点像,但你可以直接获取带内容的元素。为了释放内存,你只需要在获取元素后清除它就可以了。

2

感谢大家的评论。

我看了看ElementTree的iterparse,但那时候我已经在用xml.sax写了不少代码。因为iterparse带来的直接好处不大,我决定继续使用xml.sax。这已经比我之前的解决方案好很多了。

好了,最后我做了这些。

class Exact(xml.sax.handler.ContentHandler):
    def __init__(self, stdpath):
        self.stdpath = stdpath

        self.thisrow = {}
        self.curpath = []
        self.getvalue = None

        self.conn = MySQLConnect()
        self.table = None
        self.numrows = 0

    def __del__(self):
        self.conn.close()

        print '%s rows affected' % self.numrows

    def startElement(self, name, att):
        self.curpath.append(name)

    def characters(self, data):
        if self.getValue is not None:
            self.thisrow[self.getValue.strip()] = data.strip()
            self.getValue = None

    def endElement(self, name):
        self.curpath.pop()

        if name == self.stdpath[len(self.stdpath) - 1]:
            self.EndRow()
            self.thisrow = { }

    def EndRow(self):
        self.numrows += MySQLInsert(self.conn, self.thisrow, True, self.table)
        #for k, v in self.thisrow.iteritems():
        #   print '%s: %s,' % (k, v),
        #print ''

    def curPath(self, full=False):
        if full:
            return ' > '.join(self.curpath)
        else:
            return ' > '.join(self.curpath).replace(' > '.join(self.stdpath) + ' > ', '')

然后我为不同的XML文件多次对这个进行子类化:

class Debtors(sqlimport.Exact):
    def startDocument(self):
        self.table = 'debiteuren'
        self.address = None

    def startElement(self, name, att):
        sqlimport.Exact.startElement(self, name, att)

        if self.curPath(True) == ' > '.join(self.stdpath):
            self.thisrow = {}
            self.thisrow['debiteur'] = att.get('code').strip()
        elif self.curPath() == 'Name':
            self.getValue = 'naam'
        elif self.curPath() == 'Phone':
            self.getValue = 'telefoon1'
        elif self.curPath() == 'ExtPhone':
            self.getValue = 'telefoon2'
        elif self.curPath() == 'Contacts > Contact > Addresses > Address':
            if att.get('type') == 'V':
                self.address = 'Contacts > Contact > Addresses > Address '
        elif self.address is not None:
            if self.curPath() == self.address + '> AddressLine1':
                self.getValue = 'adres1'
            elif self.curPath() == self.address + '> AddressLine2':
                self.getValue = 'adres2'
        else:
            self.getValue = None

if __name__ == '__main__':
    handler = Debtors(['Debtors', 'Accounts', 'Account'])
    parser = xml.sax.make_parser()
    parser.setContentHandler(handler)

    parser.parse(open('myfile.xml', 'rb'))

... 以此类推 ...

撰写回答