Это пример файла XML для чтения данным скриптом: Read Sample XML file

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# -*- 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) # отправили синтаксис на исполнение