# -*- coding: cp1251 -*- # Источники: Hetland p. 427 (Beginning Python From Novice to Professional) # Глава 24 (Python in a Nutshel), Alex Martelli # Автор: Raynald Levesque, август 2008. # Перевод: А. Балабанов, 31.07.2009. # Проверено: Python 2.4.3 (IDLE 1.1.3), SPSS 15.0.1.1. # Размещение: http://www.spsstools.ru/Python/ReadSampleXMLfile.txt (.py) # Пример данных - .../sample.xml. # Цель решения - продемонстрировать импорт из .xml-файлов в SPSS посредством Python. # Задачи программы - извлечь данные из .xml-файла, поместить их в текстовые файлы с табуляцией в качестве # разделителей, затем - прочесть эти файлы синтаксисом SPSS. Все действия, в том числе - заключительная отсылка синтаксиса # в SPSS на исполнение производится в Питоне (например, из IDE-редактора, в который будет загружен данные .py-файл). При этом # SPSS должен быть запущен - примеч. перев. # Особенности примера. Данные, по-видимому, относятся к протоколированию футбольных матчей. Файл содержит 2 вида записей: action_log #(тип 1) - данные, характеризующие игру (команды, лига, время начала и конца матча) и вложенные записи action (либо Deletedaction) (типы 2, 3) - данные, # характеризующие ход конкретной игры: тип действия, взаимодействующие игроки, место (координаты поля) и т.д. # Т.е. данные представлены в иерархическом виде. В записях типа 2 состав элементов, содержащих данные, непостоянен: время от времени # могут появляться элементы subtype, c1, c2, c3, которые нет в большинстве записей action. Соответственно, разборщик, запрограммированный # в классе DataHandler должен учесть все эти особенности. Записи типа 1 помещаются в файл с именем samleLog.txt (2 записи), # записи типов 2, 3 - в файл с именем sampleAction.txt (23 записи). Вложенность записей action в записи action_log фиксируется в переменных # logID/id в обоих файлах. После импорта в SPSS файлы сохраняются в формате .sav - примеч. перев. from xml.sax.handler import ContentHandler from xml.sax import parse import spss # Укажите свои параметры: путь и имя файла fpath=r'C:/temp' fname=r'sample.xml' # прилагающийся .xml-файл примера xmlFile = r'%(fpath)s/%(fname)s' % vars() class DataHandler(ContentHandler): """Управляет процессом извлечения данных и организует их хранение в массиве """ in_extract = False # инициализация экземпляра класса def __init__(self, extract, wantList): ContentHandler.__init__(self) self.extract = extract self.data = [] self.nome="no" self.wantList = wantList # следующие 2 строки были скопированы из XML-файла и очищены от лишних символов - получили списки переменных self.action_logAtt = 'id team1 team1_name team2 team2_name league league_id \\ date matchday season season_code start1 start2'.split() self.actionAtt = 'aid action_code activitytype result id minute second \\ field_position receiver team_id x y z pace last_modified'.split() self.DeletedactionAtt = self.actionAtt # в записях типа action (тип 2) время от времени встречаются также и такие переменные: self.actionOpt ='subtype c1 c2 c3'.split() self.first_Action = True self.first_Log = True # Определяем действия, которые должны выполняться при возникновении событий startElement, endElement, characters. # Указанные события генерируются классом ContentHandler подключенного выше модуля xml.sax.handler. # Такая возможность позволяет нам "вмешиваться" в процесс разбора XML-структуры и решать, что делать с той или иной порцией данных. def startElement(self, name, attrs): self.in_extract = name in self.wantList # print name, attrs.keys() # если имя очередного атрибута находится в списке wantList (в перечне элементов, которые хотим извлечь)... if self.in_extract: self.nome = name if name== 'action_log': self.data=["1\\t"] #тип записи = 1 (характеристика игры) и табулятор как разделитель # через табулятор в цикле соединяем значения всех переменных, относящихся к этой записи self.data.extend(["%s\\t" % attrs.getValue(att) for att in self.action_logAtt]) self.action_logID=attrs.getValue('id') # будет добавлена в начале каждой записи-наследника (см. типы 2, 3) elif name == 'action': self.data=["2\\t", self.action_logID + '\\t'] #тип записи = 2 (характеристика событий в игре) # особая обработка записей с дополнительными ключами/переменными subtype, c1, c2, c3 # делаем заготовку массива dataOpt c количеством значков табуляции по числу ключей # (т.к. наперед еще неизвестно, будет ли запись содержать тот или иной ключ) # это позволит в следующем цикле при нахождении ключа поместить его в "свою" колонку self.dataOpt=['\\t']*len(self.actionOpt) for idx,attrib in enumerate(self.actionOpt): if attrs.has_key(attrib): self.dataOpt[idx] = attrs.getValue(attrib) + '\\t' # формируем основной массив, как и для типа 1 self.data.extend(["%s\\t" % attrs.getValue(att) for att in self.actionAtt]) # соединяем основной и дополнительный массивы self.data.extend(self.dataOpt ) elif name == 'Deletedaction': self.data=["3\\t", self.action_logID + '\\t'] #тип записи = 3 (характеристика событий в игре с пометкой Deleted - определяем как тип 3) self.data.extend(["%s\\t" % attrs.getValue(att) for att in self.DeletedactionAtt]) # Заготовка имен переменных для последующей записи в табулированные файлы if self.first_Log: # extract[0] будет содержать имена переменных и данные из 1-й XML-записи первого типа vnames=['recType'] vnames.extend(self.action_logAtt) vnames=["%s\\t" % v for v in vnames] vnames.append('\\n') # перевод строки/возврат каретки vnames.extend(self.data) # добавляем извлеченные данные во вторую строку self.data=vnames # обратный обмен содержимым (т.к. ниже мы помещаем # результат в массив extract именно из массива data). self.first_Log = False # это чтобы не возвращаться сюда ещё раз, и не формировать заголовочную строку с именами # переменных снова и снова # extract[1] будет содержать имена переменных и данные из 1-й XML-записи второго/третьего типа elif self.first_Action and name in ['action','Deletedaction']: vnames=['recType','logID'] vnames.extend(self.actionAtt) vnames.extend(self.actionOpt) vnames=["%s\\t" % v for v in vnames] vnames.append('\\n') vnames.extend(self.data) self.data = vnames self.first_Action = False text = ''.join(self.data) + '\\n' # добавляем перевод строки self.extract.append(text) # формируем очередной элемент массива extract def endElement(self,name): # встретили закрывающий элемент if name in self.wantList: self.data = [] # если это тот элемент, который разбирали, то очищаем массив данных и ставим признак того, # что мы находимся вне желаемого ключа self.in_extract = False def characters(self,string): # обработка строковых данных, встречающихся вне ключей if self.in_extract: # если находимся внутри желаемого ключа, self.data.append(string) # В данном случае обработка данного события, вероятно, излишняя. # В файле с примером это событие вызывают лишь символы перевода строки. Но в этих случаях # все нужные данные в self.data уже сформированы и выгружены в self.extract. # для создания табулированных файлов используем свой класс # Примеч.: если бы мы имели дело с разбором очень большого XML-файла, было бы предпочтительнее организовывать запись # в табулированные текстовые файлы непосредственно по ходу разбора (парсинга) XML-файла, т.е. прописать это в классе DataHandler. extract = [] # инициализируем массив, где будут храниться извлеченные данные wantList=['action_log','action','Deletedaction'] # перечисляем элементы, которые хотим извлечь parse(xmlFile, DataHandler(extract, wantList)) # извлекаем данные из XML-файла (работает парсинг и класс DataHandler) nameroot = fname[:fname.find('.')] # sample.xml --> sample (обрезаем расширение) fLogName = r'%(fpath)s/%(nameroot)sLog' % vars() # --> путь/sampleLog fActionName = r'%(fpath)s/%(nameroot)sAction' % vars() # --> путь/sampleAction fLog = open(fLogName+'.txt','w') # файл с содержимым action_log +'.txt' fAction = open(fActionName+'.txt','w') # файл с содержимым action/Deletedaction +'.txt' # print extract try: for (i,s) in enumerate(extract): if len(s)> 0: s2 = s.encode('iso8859-1') # перед записью в файл перекодируем строку, если в данных есть символы ASCII c кодами вне 0-128 # кодировка совпадает с той, что находится в заголовке .xml-файла. if s[0] in ['2','3'] or i==1: # вторая запись содержит имена переменных для файла action fAction.write(s2) elif s[0] == '1' or i == 0: # первая запись содержит имена переменных для файла action_log fLog.write(s2) finally: fAction.close() fLog.close() # Полученные табулированные файлы далее будут прочитаны в SPSS. # Ниже приводится синтаксис, созданный при помощи мастера импорта текстовых файлов, # и вставленный сюда с небольшими правками, чтобы сделать процесс импорта # полностью автоматизированным. Последняя команда spss.Submit посылает в SPSS # команды на импорт текстовых табулированных файлов и сохранение их в формате .sav. cmd=r""" SET PRINTBACK=YES /MPRINT=YES. DATASET CLOSE ALL. GET DATA /TYPE = TXT /FILE = "%(fLogName)s.txt" /DELCASE = LINE /DELIMITERS = "\\t" /ARRANGEMENT = DELIMITED /FIRSTCASE = 2 /IMPORTCASE = ALL /VARIABLES = recType F1.0 id F6.0 team1 F3.0 team1_name A19 team2 F3.0 team2_name A16 league A14 league_id F1.0 date A22 matchday F2.0 season A9 season_code F2.0 start1 A22 start2 A22. CACHE. SAVE OUTFILE= "%(fLogName)s.sav". GET DATA /TYPE = TXT /FILE = "%(fActionName)s.txt" /DELCASE = LINE /DELIMITERS = "\\t" /ARRANGEMENT = DELIMITED /FIRSTCASE = 2 /IMPORTCASE = ALL /VARIABLES = recType F1.0 logID F6.0 aid F7.0 action_code A4 activitytype F2.0 result F1.0 id F2.0 minute F2.0 second F2.0 field_position F2.0 receiver F5.0 team_id F3.0 x F5.3 y F5.3 z F5.3 pace F5.3 last_modified A22 subtype F2.0 c1 F1.0 c2 F1.0 c3 F1.0 . CACHE. SAVE OUTFILE="%(fActionName)s.sav". """ % vars() # сформировали синтаксис SPSS для считки и сохранения в .sav табулированных файлов. spss.Submit(cmd) # отправили синтаксис на исполнение