Tkinter GUI per convertire il file di larghezza fisso in file delimitato
-
27-10-2019 - |
Domanda
Sto scrivendo un codice di convertitore per il nostro reparto dati per convertire i file di larghezza fissa in file delmitati. Normalmente utilizziamo l'importazione del file in Excel, utilizziamo la procedura guidata di importazione di testo per impostare le lunghezze del campo, quindi salvo come CSV. Tuttavia, abbiamo incontrato la limitazione in cui abbiamo iniziato a ottenere file che sono lunghi milioni di record e quindi non possiamo essere importati in Excel. I file non hanno sempre spazi tra i campi, in particolare tra campi di valore come numeri di telefono o codici postali. Anche le intestazioni sono spesso riempite completamente senza spazi.
Un campione di un tipico file di larghezza fissa con cui abbiamo a che fare:
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
Il programma deve interrompere il file nei seguenti campi delimitati:
Sequen
Sack e PA
Nome e cognome
Titolo di lavoro
Nome hosp
Indirizzo di consegna
Indirizzo alternativo 1
Testo calcolato
Postnet Bar
Ogni file come larghezza leggermente diversa di ciascun campo a seconda del resto del lavoro. Quello che sto cercando è un delimitatore orientato alla GUI molto simile alla procedura guidata di importazione Excel per i file di larghezza fissa. Sto scrivendo questo strumento in Python come parte di uno strumento più ampio che esegue molte altre operazioni di file come la rottura dei file in più UP, invertendo un file, convertendo da una larghezza delimitata in larghezza fissa e controlla il controllo della cifra. Sto usando Tkinter per il resto degli strumenti e sarebbe l'ideale se anche la soluzione lo usasse.
Qualsiasi aiuto apprezzato
Soluzione
Se capisco correttamente il problema (e ci sono buone probabilità che non ...), la soluzione più semplice potrebbe essere quella di utilizzare un widget di testo.
Fai in modo che la prima riga sia una serie di spazi la stessa lunghezza della riga. Usa un paio di tag alternati (ad esempio: "anche" e "dispari") per dare a ogni personaggio un colore alternativo in modo che si distinguano l'uno dall'altro. La seconda riga sarebbe l'intestazione e tutte le linee rimanenti sarebbero un paio di linee di dati di esempio.
Quindi, impostare i binding nella prima riga per convertire uno spazio in una "X" quando l'utente fa clic su un carattere. Se fanno clic su una "X", convertilo in uno spazio. Possono quindi andare a fare clic sul personaggio che è l'inizio di ogni colonna. Quando l'utente è finito, è possibile ottenere la prima riga del widget di testo e avrà una "X" per ogni colonna. Quindi hai solo bisogno di una piccola funzione che lo traduce in qualsiasi formato di cui hai bisogno.
Sarebbe approssimativamente così (anche se ovviamente i colori sarebbero diversi da quelli che appare su questo sito)
x x x ...
SequenSack and PaFull Name****************************]JOB...
000001T1 P1 Sample A Sample ...
Ecco un rapido hack per illustrare l'idea generale. È un po 'sciatto ma penso che illustri la tecnica. Quando lo esegui, fai clic su un'area nella prima riga per impostare o cancellare un marcatore. Ciò farà evidenziare l'intestazione in colori alternativi per ciascun marker.
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()
Altri suggerimenti
modificare: Ora vedo che stai cercando una GUI. Lascerò questa risposta errata per i posteri.
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)