GUI TKINTER для преобразования фиксированной ширины в файл разграниченного
-
27-10-2019 - |
Вопрос
Я пишу код преобразователя для нашего отдела данных, чтобы преобразовать фиксированные файлы ширины в файлы Delmited. Обычно мы используем импорт файла в Excel, используем мастер импорта текста, чтобы установить длину поля, а затем просто сохранять в качестве CSV. Однако мы столкнулись с ограничением, когда мы начали получать файлы, которые составляют миллионы записей, и, таким образом, нельзя импортировать в Excel. Файлы не всегда имеют пробелы между полями, особенно между полями значения, такими как телефоны или почтовые индексы. Заголовки также часто полностью заполняются без пробелов.
Образец типичного файла фиксированной ширины, с которым мы имеем дело:
SequenSack and PaFull Name****************************]JOB TITLE****************]HOSP NAME******************************]Delivery Address***********************]Alternate 1 Address********************]Calculated Text**********************************]POSTNET Bar
000001T1 P1 Sample A Sample 123 Any Street Anytown 12345-6789 12345678900
000002T1 P1 Sample A Sample Director of Medicine 123 Any Street Po Box 1234 Anytown 12345-6789 12345678900
Программа должна разбить файл в следующие поля разграниченных поля:
Последовательность
Мешок и па
ФИО
Название работы
Название госпиталя
Адресс доставки
Альтернативный адрес 1
Рассчищенный текст
Postnet Bar
Каждый файл в виде немного другой ширины каждого поля в зависимости от остальной части задания. Я ищу разделитель, ориентированный на графический интерфейс, очень похожий на мастер импорта Excel для файлов фиксированной ширины. Я пишу этот инструмент в Python как часть более крупного инструмента, который выполняет множество других операций файлов, таких как разбивание файлов в множественное внимание, обращение с файлом, преобразование из делитированной в фиксированную ширину и проверку цифр. Я использую Tkinter для остальных инструментов, и было бы идеально, если бы решение также его использовало.
Любая помощь оценена
Решение
Если я правильно понимаю проблему (и есть большая вероятность, что у меня нет ...), самое простое решение может заключаться в том, чтобы использовать текстовый виджет.
Сделайте первую строку быть серией пробелов той же длины, что и ряд. Используйте пару чередующихся тегов (например, «даже» и «нечетное»), чтобы дать каждому персонажу альтернативный цвет, чтобы они выделялись друг с другом. Вторая строка будет заголовком, а любые оставшиеся строки будут парой строк образцов данных.
Затем настройте привязки в первой строке, чтобы преобразовать пространство в «x», когда пользователь нажимает на символ. Если они нажимают на «X», преобразуйте его в пространство. Затем они могут пойти и нажать на символ, который является началом каждого столбца. Когда пользователь будет сделан, вы можете получить первую строку текстового виджета, и он будет иметь «X» для каждого столбца. Затем вам просто нужна небольшая функция, которая переводит это в любой формат, который вам нужен.
Это будет выглядеть примерно так (хотя, очевидно, цвета будут отличаться от того, что появится на этом сайте)
x x x ...
SequenSack and PaFull Name****************************]JOB...
000001T1 P1 Sample A Sample ...
Вот быстрый хак, чтобы проиллюстрировать общую идею. Это немного неряшливо, но я думаю, что это иллюстрирует технику. Когда вы запустите его, нажмите на область в первой строке, чтобы установить или очистить маркер. Это приведет к тому, что заголовок будет выделен в альтернативных цветах для каждого маркера.
import sys
import Tkinter as tk
import tkFont
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
header = "SequenSack and PaFull Name****************************]JOB TITLE****************]HOSP NAME******************************]Delivery Address***********************]Alternate 1 Address********************]Calculated Text**********************************]POSTNET Bar"
sample = "000001T1 P1 Sample A Sample 123 Any Street Anytown 12345-6789 12345678900"
widget = DelimiterWidget(self, header, sample)
hsb = tk.Scrollbar(orient="horizontal", command=widget.xview)
widget.configure(xscrollcommand=hsb.set)
hsb.pack(side="bottom", fill="x")
widget.pack(side="top", fill="x")
class DelimiterWidget(tk.Text):
def __init__(self, parent, header, samplerow):
fixedFont = tkFont.nametofont("TkFixedFont")
tk.Text.__init__(self, parent, wrap="none", height=3, font=fixedFont)
self.configure(cursor="left_ptr")
self.tag_configure("header", background="gray")
self.tag_configure("even", background="#ffffff")
self.tag_configure("header_even", background="bisque")
self.tag_configure("header_odd", background="lightblue")
self.tag_configure("odd", background="#eeeeee")
markers = " "*len(header)
for i in range(len(header)):
tag = "even" if i%2==0 else "odd"
self.insert("end", " ", (tag,))
self.insert("end", "\n")
self.insert("end", header+"\n", "header")
self.insert("end", samplerow, "sample")
self.configure(state="disabled")
self.bind("<1>", self.on_click)
self.bind("<Double-1>", self.on_click)
self.bind("<Triple-1>", self.on_click)
def on_click(self, event):
'''Handle a click on a marker'''
index = self.index("@%s,%s" % (event.x, event.y))
current = self.get(index)
self.configure(state="normal")
self.delete(index)
(line, column) = index.split(".")
tag = "even" if int(column)%2 == 0 else "odd"
char = " " if current == "x" else "x"
self.insert(index, char, tag)
self.configure(state="disabled")
self.highlight_header()
return "break"
def highlight_header(self):
'''Highlight the header based on marker positions'''
self.tag_remove("header_even", 1.0, "end")
self.tag_remove("header_odd", 1.0, "end")
markers = self.get(1.0, "1.0 lineend")
i = 0
start = "2.0"
tag = "header_even"
while True:
try:
i = markers.index("x", i+1)
end = "2.%s" % i
self.tag_add(tag, start, end)
start = self.index(end)
tag = "header_even" if tag == "header_odd" else "header_odd"
except ValueError:
break
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Другие советы
редактировать: Теперь я вижу, что вы ищете графический интерфейс. Я оставлю этот неправильный ответ для потомства.
import csv
def fixedwidth2csv(fw_name, csv_name, field_info, headings=None):
with open(fw_name, 'r') as fw_in:
with open(csv_name, 'rb') as csv_out: # 'rb' => 'r' for python 3
wtr = csv.writer(csv_out)
if headings:
wtr.writerow(headings)
for line in fw_in:
wtr.writerow(line[pos:pos+width].strip() for pos, width in field_info)