source: orange/Orange/OrangeWidgets/Prototypes/OWCSVFileImport.py @ 10529:35d7c1de22a8

Revision 10529:35d7c1de22a8, 10.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added general '.csv' file import prototype widget (still needs lots of work).

Line 
1"""
2<name>CSV File import</name>
3<description>Import .csv file</description>
4
5"""
6import os
7import csv
8from StringIO import StringIO
9
10from PyQt4 import QtCore, QtGui
11
12from OWWidget import *
13import OWGUI
14import Orange
15
16#from OWFile import FileNameContextHandler as PathContextHandler
17from OWDataTable import ExampleTableModel
18
19DEFAULT_OPTIONS = {"delimiter":",", 
20                   "quote":"'", 
21                   "has_header": True,
22                   "has_orange_definitions": False,
23                   }
24
25Slot = pyqtSlot
26
27class standard_icons(object):
28    def __init__(self, qwidget=None, style=None):
29        self.qwidget = qwidget
30        if qwidget is None:
31            self.style = QApplication.instance().style()
32        else:
33            self.style = qwidget.style()
34   
35    @property
36    def dir_open_icon(self):
37        return self.style.standardIcon(QStyle.SP_DirOpenIcon)
38   
39    @property
40    def reload_icon(self):
41        return self.style.standardIcon(QStyle.SP_BrowserReload)
42   
43   
44           
45           
46class OWCSVFileImport(OWWidget):
47#    contextHandlers = {"": PathContextHandler("")}
48    settingsList = ["recent_files"]
49   
50    DELIMITERS = [("Tab", "\t"),
51                  ("Comma", ","),
52                  ("Semicolon", ";"),
53                  ("Space", " "),
54                  ("Others", None),
55                  ]
56    def __init__(self, parent=None, signalManager=None, 
57                 title="CSV File Import"):
58       
59        OWWidget.__init__(self, parent, signalManager, title, 
60                          wantMainArea=False)
61       
62        self.inputs = []
63        self.outputs = [("Data", Orange.data.Table)]
64       
65        # Settings
66        self.delimiter = ","
67        self.quote = '"'
68       
69        self.has_header = True
70        self.has_orange_header = True
71       
72        self.recent_files = []
73        self.hints = {}
74       
75        self.loadSettings()
76       
77        self.recent_file = filter(os.path.exists, self.recent_files)
78       
79        self.csv_options = dict(DEFAULT_OPTIONS)
80       
81        layout = QHBoxLayout()
82        box = OWGUI.widgetBox(self.controlArea, "File", orientation=layout)
83        self.style().standardIcon(QStyle.SP_DirOpenIcon)
84        icons = standard_icons(self)
85       
86        self.recent_combo = QComboBox(self, objectName="recent_combo",
87                                      toolTip="Recent files.",
88                                      activated=self.on_select_recent)
89        self.recent_combo.addItems([os.path.basename(p) for p in self.recent_files])
90       
91        self.browse_button = QPushButton("...", icon=icons.dir_open_icon,
92                                         toolTip="Browse filesystem",
93                                         clicked=self.on_open_dialog)
94       
95#        self.reload_button = QPushButton("Reload", icon=icons.reload_icon,
96#                                         clicked=self.reload_file)
97       
98        layout.addWidget(self.recent_combo, 2)
99        layout.addWidget(self.browse_button)
100#        layout.addWidget(self.reload_button)
101
102        form_left = QFormLayout()
103        form_right = QFormLayout()
104        h_layout = QHBoxLayout()
105        grid_layout = QGridLayout()
106        grid_layout.setVerticalSpacing(4)
107        grid_layout.setHorizontalSpacing(4)
108        box = OWGUI.widgetBox(self.controlArea, "Cell Separator",
109                              orientation=grid_layout)
110       
111        button_group = QButtonGroup(box)
112        QObject.connect(button_group, 
113                        SIGNAL("buttonPressed(int)"),
114                        self.delimiter_changed)
115#        button_group.buttonPressed.connect(self.delimiter_changed)
116       
117        for i, (name, char) in  enumerate(self.DELIMITERS[:-1]):
118            button = QRadioButton(name, box,
119                                  toolTip="Use %r as cell separator" % char)
120           
121            button_group.addButton(button, i)
122            grid_layout.addWidget(button, i / 3, i % 3)
123           
124        button = QRadioButton("Other", box,
125                              toolTip="Use other character")
126       
127        button_group.addButton(button, i + 1)
128        grid_layout.addWidget(button, i / 3 + 1, 0)
129        self.delimiter_button_group = button_group
130           
131        self.delimiter_edit = QLineEdit(objectName="delimiter_edit",
132                                        text=self.delimiter,
133                                        editingFinished=self.delimiter_changed,
134                                        toolTip="Cell delimiter character.")
135        grid_layout.addWidget(self.delimiter_edit, i / 3 + 1, 1, -1, -1)
136       
137        self.quote_edit = QLineEdit(objectName="quote_edit",
138                                    text=self.quote,
139                                    editingFinished=self.quote_changed,
140                                    toolTip="Text quote character.")
141       
142        form = QFormLayout()
143        box = OWGUI.widgetBox(self.controlArea, "Other Options",
144                              orientation=form)
145       
146#        form_left.addRow("Delimiter", self.delimiter_edit)
147        form.addRow("Quote", self.quote_edit)
148       
149        self.has_header_check = \
150                QCheckBox(objectName="has_header_check",
151                          checked=self.has_header,
152                          text="Header line",
153                          toolTip="Use the first line as a header",
154                          toggled=self.has_header_changed
155                          )
156       
157        form.addRow(self.has_header_check)
158       
159        self.has_orange_header_check = \
160                QCheckBox(objectName="has_orange_header_check",
161                          checked=self.has_orange_header,
162                          text="Has orange variable type definitions",
163                          toolTip="Use second and third line as a orange style '.tab' format feature definitions.",
164                          toggled=self.has_orange_header_changed
165                          )
166               
167        form.addRow(self.has_orange_header_check)
168       
169        box = OWGUI.widgetBox(self.controlArea, "Preview")
170        self.preview_view = QTableView()
171        box.layout().addWidget(self.preview_view)
172       
173        OWGUI.button(self.controlArea, self, "Send", callback=self.send_data)
174       
175        self.selected_file = None
176        self.data = None
177       
178        self.resize(400, 350)
179       
180    def on_select_recent(self, recent):
181        if isinstance(recent, int):
182            recent = self.recent_files[recent]
183       
184        self.set_selected_file(recent)
185   
186    def on_open_dialog(self): 
187        last = os.path.expanduser("~/Documents")
188        path = QFileDialog.getOpenFileName(self, "Open File", last)
189        path = unicode(path)
190        if path:
191            basedir, name = os.path.split(path)
192            self.recent_combo.insertItem(0, name)
193            self.recent_files.insert(0, path)
194            self.set_selected_file(path)
195   
196    @Slot(int)
197    def delimiter_changed(self, index):
198        self.delimiter = self.DELIMITERS[index][1]
199        if self.delimiter is None:
200            self.other_delimiter = str(self.delimiter_edit.text())
201        self.update_preview()
202   
203    def quote_changed(self):
204        if self.quote_edit.text():
205            self.quote = str(self.quote_edit.text())
206            self.update_preview()
207           
208    def has_header_changed(self):
209        self.has_header = self.has_header_check.isChecked()
210        self.update_preview()
211       
212    def has_orange_header_changed(self):
213        self.has_orange_header = self.has_orange_header_check.isChecked()
214        self.update_preview()
215           
216    def set_selected_file(self, filename):
217        if filename in self.hints:
218            hints = self.hints[filename]
219        else:
220            hints = sniff_csv(filename)
221            self.hints[filename] = hints
222       
223        delimiter = hints["delimiter"]
224        preset = [d[1] for d in self.DELIMITERS[:-1]]
225        if delimiter not in preset:
226            self.delimiter = None
227            self.other_delimiter = delimiter
228            self.delimiter_edit.setText(self.other_delimiter)
229            self.delimiter_edit.setEnabled(True)
230        else:
231            index = preset.index(delimiter)
232            button = self.delimiter_button_group.button(index)
233            button.setChecked(True)
234            self.delimiter_edit.setEnabled(False)
235       
236        self.quote = hints["quotechar"]
237        self.quote_edit.setText(self.quote)
238       
239        self.has_header = hints["has_header"]
240        self.has_header_check.setChecked(self.has_header)
241       
242        self.has_orange_header = hints["has_orange_header"]
243        self.has_orange_header_check.setChecked(self.has_orange_header)
244       
245        self.selected_file = filename
246        self.selected_file_head = []
247        with open(self.selected_file, "rb") as f:
248            for i, line in zip(range(30), f):
249                self.selected_file_head.append(line)
250       
251        self.update_preview()
252       
253    def update_preview(self):
254        if self.selected_file:
255            head = StringIO("".join(self.selected_file_head))
256            hints = self.hints[self.selected_file]
257           
258            hints["quote"] = self.quote
259            hints["delimiter"] = self.delimiter or self.other_delimiter
260            hints["has_header"] = self.has_header
261            hints["has_orange_header"] = self.has_orange_header
262           
263            try:
264                data = Orange.data.io.load_csv(head, delimiter=self.delimiter,
265                                               quotechar=self.quote,
266                                               has_header=self.has_header,
267                                               has_types=self.has_orange_header,
268                                               has_annotations=self.has_orange_header,
269                                               )
270            except Exception, ex:
271                self.error(0, "Cannot parse (%r)" % ex)
272                self.data = None
273                return
274            model = ExampleTableModel(data, None, self)
275            self.preview_view.setModel(model)
276            self.data = data
277           
278    def send_data(self):
279        self.send("Data", self.data)
280       
281       
282def sniff_csv(file):
283    snifer = csv.Sniffer()
284    if isinstance(file, basestring):
285        file = open(file, "rb")
286   
287    sample = file.read(2 ** 20) # max 1MB sample
288    dialect = snifer.sniff(sample)
289    has_header = snifer.has_header(sample)
290   
291    return {"delimiter": dialect.delimiter,
292            "doublequote": dialect.doublequote,
293            "escapechar": dialect.escapechar,
294            "quotechar": dialect.quotechar,
295            "quoting": dialect.quoting,
296            "skipinitialspace": dialect.skipinitialspace,
297            "has_header": has_header,
298            "has_orange_header": False}
299   
300if __name__ == "__main__":
301    import sys
302    app = QApplication(sys.argv)
303    w = OWCSVFileImport()
304    w.show()
305    app.exec_()
306    w.saveSettings()
307       
308       
309       
310       
Note: See TracBrowser for help on using the repository browser.