Вдохновившись этой статейкой от sqltd1, решил написать на питончике скрипт для чтения данных из ячейки XLSX-файла.
Но не просто написать скрипт, а сделать это без внешних библиотек и всего прочего.
Формат XLSX
XLSX представляет собой zip-архив. Для извлечения значений нужны два файла из него:
/xl/worksheets/<лист>.xml - собственно лист
/xl/sharedStrings.xml - строки
Извлечение значений
Для распаковки использую модуль zipfile, а в качестве простенького XML-парсера подойдут регулярные выражения.
Код класса (42 строки, поэтому под спойлером):
Кликните здесь для просмотра всего текста
Python | 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
| import zipfile, re
class XlsxReader:
__worksheet = None
__strings = []
def __init__(self, file, worksheet):
# Открытие XLSX-файла как ZIP-архива
with zipfile.ZipFile(file) as zip:
# Чтение файла листа из архива
self.__worksheet = zip.read(f'xl/worksheets/{worksheet}.xml').decode('utf-8')
# Чтение файла хранилища строк из архива
strings_file = zip.read('xl/sharedStrings.xml').decode('utf-8')
# Добавление строк в список
for match in re.finditer('\\<si\\>\\<t\\>([^\\<]+)\\</t\\>\\</si\\>', strings_file):
self.__strings.append(match.group(1))
def read(self, cell):
# Если в документе есть строковые значения
if len(self.__strings) > 0:
# Поиск элемента <c r="ячейка" t="s"><v>индекс</v></c>
s_match = re.search(f'\\<c r="{cell}" t="s"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
# Если элемент найден
if(s_match != None and len(s_match.groups()) > 0):
# Найти строку в списке по индексу и вернуть
return self.__strings[int(s_match.group(1))]
# В документе нет строковых значений
# Поиск элемента <c r="ячейка"><v>значение</v></c>
n_match = re.search(f'\\<c r="{cell}"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
# Если элемент найден
if(n_match != None and len(n_match.groups()) > 0):
# Вернуть его
return n_match.group(1)
# Если ничего не найдено, вернуть None
return None |
|
Пример использования:
Python | 1
2
3
4
5
6
7
| from xlsx_reader import XlsxReader
reader = XlsxReader('test.xlsx', 'sheet1')
print('A1', reader.read('A1'))
print('B2', reader.read('B2'))
print('B8', reader.read('B8'))
print('D6', reader.read('D6')) |
|
Тестировал я на этом файле:
test.xlsx
Результат:
Код:
A1 5
B2 92
B8 String2
D6 TestString
Версия без комментариев (для пущей экономии места и использования в качестве библиотеки):
Кликните здесь для просмотра всего текста
Python | 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
| import zipfile, re
class XlsxReader:
__worksheet = None
__strings = []
def __init__(self, file, worksheet):
with zipfile.ZipFile(file) as zip:
self.__worksheet = zip.read(f'xl/worksheets/{worksheet}.xml').decode('utf-8')
strings_file = zip.read('xl/sharedStrings.xml').decode('utf-8')
for match in re.finditer('\\<si\\>\\<t\\>([^\\<]+)\\</t\\>\\</si\\>', strings_file):
self.__strings.append(match.group(1))
def read(self, cell):
if len(self.__strings) > 0:
s_match = re.search(f'\\<c r="{cell}" t="s"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
if(s_match != None and len(s_match.groups()) > 0):
return self.__strings[int(s_match.group(1))]
n_match = re.search(f'\\<c r="{cell}"\\>\\<v\\>([^\\<]+)\\</v\\>\\</c\\>', self.__worksheet)
if(n_match != None and len(n_match.groups()) > 0):
return n_match.group(1)
return None |
|
Внимание! Даже если тип значения в XLSX-файле - не строка, возвращено будет значение типа str. Это сделано специально, чтобы вызывающий мог сам конвертировать его в нужный тип числа.
P.S. Обратите внимание, что, к примеру, "Лист 1" называется "sheet1" внутри XLSX-файла. Использовать нужно именно "внутреннее" название. |