PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/spyderlib/widgets/importwizard.py

https://code.google.com/p/spyderlib/
Python | 635 lines | 595 code | 20 blank | 20 comment | 11 complexity | 6ad7b5264f160f8625d91a67cd9f7a5f MD5 | raw file
Possible License(s): CC-BY-3.0
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright Š 2009-2010 Pierre Raybaut
  4. # Licensed under the terms of the MIT License
  5. # (see spyderlib/__init__.py for details)
  6. """
  7. Text data Importing Wizard based on Qt
  8. """
  9. from __future__ import print_function
  10. from spyderlib.qt.QtGui import (QTableView, QVBoxLayout, QHBoxLayout,
  11. QGridLayout, QWidget, QDialog, QTextEdit,
  12. QTabWidget, QPushButton, QLabel, QSpacerItem,
  13. QSizePolicy, QCheckBox, QColor, QRadioButton,
  14. QLineEdit, QFrame, QMenu, QIntValidator,
  15. QGroupBox, QMessageBox)
  16. from spyderlib.qt.QtCore import (Qt, QModelIndex, QAbstractTableModel, Signal,
  17. Slot)
  18. from spyderlib.qt.compat import to_qvariant
  19. from functools import partial as ft_partial
  20. try:
  21. import pandas as pd
  22. except ImportError:
  23. pd = None
  24. # Local import
  25. from spyderlib.baseconfig import _
  26. from spyderlib.utils import programs
  27. from spyderlib.utils.qthelpers import get_icon, add_actions, create_action
  28. from spyderlib.py3compat import (TEXT_TYPES, INT_TYPES, to_text_string, u,
  29. zip_longest, io)
  30. def try_to_parse(value):
  31. _types = ('int', 'float')
  32. for _t in _types:
  33. try:
  34. _val = eval("%s('%s')" % (_t, value))
  35. return _val
  36. except (ValueError, SyntaxError):
  37. pass
  38. return value
  39. def try_to_eval(value):
  40. try:
  41. return eval(value)
  42. except (NameError, SyntaxError, ImportError):
  43. return value
  44. #----Numpy arrays support
  45. class FakeObject(object):
  46. """Fake class used in replacement of missing modules"""
  47. pass
  48. try:
  49. from numpy import ndarray, array
  50. except ImportError:
  51. class ndarray(FakeObject): # analysis:ignore
  52. """Fake ndarray"""
  53. pass
  54. #----date and datetime objects support
  55. import datetime
  56. try:
  57. from dateutil.parser import parse as dateparse
  58. except ImportError:
  59. def dateparse(datestr, dayfirst=True): # analysis:ignore
  60. """Just for 'day/month/year' strings"""
  61. _a, _b, _c = list(map(int, datestr.split('/')))
  62. if dayfirst:
  63. return datetime.datetime(_c, _b, _a)
  64. return datetime.datetime(_c, _a, _b)
  65. def datestr_to_datetime(value, dayfirst=True):
  66. return dateparse(value, dayfirst=dayfirst)
  67. #----Background colors for supported types
  68. COLORS = {
  69. bool: Qt.magenta,
  70. tuple([float] + list(INT_TYPES)): Qt.blue,
  71. list: Qt.yellow,
  72. dict: Qt.cyan,
  73. tuple: Qt.lightGray,
  74. TEXT_TYPES: Qt.darkRed,
  75. ndarray: Qt.green,
  76. datetime.date: Qt.darkYellow,
  77. }
  78. def get_color(value, alpha):
  79. """Return color depending on value type"""
  80. color = QColor()
  81. for typ in COLORS:
  82. if isinstance(value, typ):
  83. color = QColor(COLORS[typ])
  84. color.setAlphaF(alpha)
  85. return color
  86. class ContentsWidget(QWidget):
  87. """Import wizard contents widget"""
  88. asDataChanged = Signal(bool)
  89. def __init__(self, parent, text):
  90. QWidget.__init__(self, parent)
  91. self.text_editor = QTextEdit(self)
  92. self.text_editor.setText(text)
  93. self.text_editor.setReadOnly(True)
  94. # Type frame
  95. type_layout = QHBoxLayout()
  96. type_label = QLabel(_("Import as"))
  97. type_layout.addWidget(type_label)
  98. data_btn = QRadioButton(_("data"))
  99. data_btn.setChecked(True)
  100. self._as_data= True
  101. type_layout.addWidget(data_btn)
  102. code_btn = QRadioButton(_("code"))
  103. self._as_code = False
  104. type_layout.addWidget(code_btn)
  105. txt_btn = QRadioButton(_("text"))
  106. type_layout.addWidget(txt_btn)
  107. h_spacer = QSpacerItem(40, 20,
  108. QSizePolicy.Expanding, QSizePolicy.Minimum)
  109. type_layout.addItem(h_spacer)
  110. type_frame = QFrame()
  111. type_frame.setLayout(type_layout)
  112. # Opts frame
  113. grid_layout = QGridLayout()
  114. grid_layout.setSpacing(0)
  115. col_label = QLabel(_("Column separator:"))
  116. grid_layout.addWidget(col_label, 0, 0)
  117. col_w = QWidget()
  118. col_btn_layout = QHBoxLayout()
  119. self.tab_btn = QRadioButton(_("Tab"))
  120. self.tab_btn.setChecked(False)
  121. col_btn_layout.addWidget(self.tab_btn)
  122. other_btn_col = QRadioButton(_("other"))
  123. other_btn_col.setChecked(True)
  124. col_btn_layout.addWidget(other_btn_col)
  125. col_w.setLayout(col_btn_layout)
  126. grid_layout.addWidget(col_w, 0, 1)
  127. self.line_edt = QLineEdit(",")
  128. self.line_edt.setMaximumWidth(30)
  129. self.line_edt.setEnabled(True)
  130. other_btn_col.toggled.connect(self.line_edt.setEnabled)
  131. grid_layout.addWidget(self.line_edt, 0, 2)
  132. row_label = QLabel(_("Row separator:"))
  133. grid_layout.addWidget(row_label, 1, 0)
  134. row_w = QWidget()
  135. row_btn_layout = QHBoxLayout()
  136. self.eol_btn = QRadioButton(_("EOL"))
  137. self.eol_btn.setChecked(True)
  138. row_btn_layout.addWidget(self.eol_btn)
  139. other_btn_row = QRadioButton(_("other"))
  140. row_btn_layout.addWidget(other_btn_row)
  141. row_w.setLayout(row_btn_layout)
  142. grid_layout.addWidget(row_w, 1, 1)
  143. self.line_edt_row = QLineEdit(";")
  144. self.line_edt_row.setMaximumWidth(30)
  145. self.line_edt_row.setEnabled(False)
  146. other_btn_row.toggled.connect(self.line_edt_row.setEnabled)
  147. grid_layout.addWidget(self.line_edt_row, 1, 2)
  148. grid_layout.setRowMinimumHeight(2, 15)
  149. other_group = QGroupBox(_("Additional options"))
  150. other_layout = QGridLayout()
  151. other_group.setLayout(other_layout)
  152. skiprows_label = QLabel(_("Skip rows:"))
  153. other_layout.addWidget(skiprows_label, 0, 0)
  154. self.skiprows_edt = QLineEdit('0')
  155. self.skiprows_edt.setMaximumWidth(30)
  156. intvalid = QIntValidator(0, len(to_text_string(text).splitlines()),
  157. self.skiprows_edt)
  158. self.skiprows_edt.setValidator(intvalid)
  159. other_layout.addWidget(self.skiprows_edt, 0, 1)
  160. other_layout.setColumnMinimumWidth(2, 5)
  161. comments_label = QLabel(_("Comments:"))
  162. other_layout.addWidget(comments_label, 0, 3)
  163. self.comments_edt = QLineEdit('#')
  164. self.comments_edt.setMaximumWidth(30)
  165. other_layout.addWidget(self.comments_edt, 0, 4)
  166. self.trnsp_box = QCheckBox(_("Transpose"))
  167. #self.trnsp_box.setEnabled(False)
  168. other_layout.addWidget(self.trnsp_box, 1, 0, 2, 0)
  169. grid_layout.addWidget(other_group, 3, 0, 2, 0)
  170. opts_frame = QFrame()
  171. opts_frame.setLayout(grid_layout)
  172. data_btn.toggled.connect(opts_frame.setEnabled)
  173. data_btn.toggled.connect(self.set_as_data)
  174. code_btn.toggled.connect(self.set_as_code)
  175. # self.connect(txt_btn, SIGNAL("toggled(bool)"),
  176. # self, SLOT("is_text(bool)"))
  177. # Final layout
  178. layout = QVBoxLayout()
  179. layout.addWidget(type_frame)
  180. layout.addWidget(self.text_editor)
  181. layout.addWidget(opts_frame)
  182. self.setLayout(layout)
  183. def get_as_data(self):
  184. """Return if data type conversion"""
  185. return self._as_data
  186. def get_as_code(self):
  187. """Return if code type conversion"""
  188. return self._as_code
  189. def get_as_num(self):
  190. """Return if numeric type conversion"""
  191. return self._as_num
  192. def get_col_sep(self):
  193. """Return the column separator"""
  194. if self.tab_btn.isChecked():
  195. return u("\t")
  196. return to_text_string(self.line_edt.text())
  197. def get_row_sep(self):
  198. """Return the row separator"""
  199. if self.eol_btn.isChecked():
  200. return u("\n")
  201. return to_text_string(self.line_edt_row.text())
  202. def get_skiprows(self):
  203. """Return number of lines to be skipped"""
  204. return int(to_text_string(self.skiprows_edt.text()))
  205. def get_comments(self):
  206. """Return comment string"""
  207. return to_text_string(self.comments_edt.text())
  208. @Slot(bool)
  209. def set_as_data(self, as_data):
  210. """Set if data type conversion"""
  211. self._as_data = as_data
  212. self.asDataChanged.emit(as_data)
  213. @Slot(bool)
  214. def set_as_code(self, as_code):
  215. """Set if code type conversion"""
  216. self._as_code = as_code
  217. class PreviewTableModel(QAbstractTableModel):
  218. """Import wizard preview table model"""
  219. def __init__(self, data=[], parent=None):
  220. QAbstractTableModel.__init__(self, parent)
  221. self._data = data
  222. def rowCount(self, parent=QModelIndex()):
  223. """Return row count"""
  224. return len(self._data)
  225. def columnCount(self, parent=QModelIndex()):
  226. """Return column count"""
  227. return len(self._data[0])
  228. def _display_data(self, index):
  229. """Return a data element"""
  230. return to_qvariant(self._data[index.row()][index.column()])
  231. def data(self, index, role=Qt.DisplayRole):
  232. """Return a model data element"""
  233. if not index.isValid():
  234. return to_qvariant()
  235. if role == Qt.DisplayRole:
  236. return self._display_data(index)
  237. elif role == Qt.BackgroundColorRole:
  238. return to_qvariant(get_color(self._data[index.row()][index.column()], .2))
  239. elif role == Qt.TextAlignmentRole:
  240. return to_qvariant(int(Qt.AlignRight|Qt.AlignVCenter))
  241. return to_qvariant()
  242. def setData(self, index, value, role=Qt.EditRole):
  243. """Set model data"""
  244. return False
  245. def get_data(self):
  246. """Return a copy of model data"""
  247. return self._data[:][:]
  248. def parse_data_type(self, index, **kwargs):
  249. """Parse a type to an other type"""
  250. if not index.isValid():
  251. return False
  252. try:
  253. if kwargs['atype'] == "date":
  254. self._data[index.row()][index.column()] = \
  255. datestr_to_datetime(self._data[index.row()][index.column()],
  256. kwargs['dayfirst']).date()
  257. elif kwargs['atype'] == "perc":
  258. _tmp = self._data[index.row()][index.column()].replace("%", "")
  259. self._data[index.row()][index.column()] = eval(_tmp)/100.
  260. elif kwargs['atype'] == "account":
  261. _tmp = self._data[index.row()][index.column()].replace(",", "")
  262. self._data[index.row()][index.column()] = eval(_tmp)
  263. elif kwargs['atype'] == "unicode":
  264. self._data[index.row()][index.column()] = to_text_string(
  265. self._data[index.row()][index.column()])
  266. elif kwargs['atype'] == "int":
  267. self._data[index.row()][index.column()] = int(
  268. self._data[index.row()][index.column()])
  269. elif kwargs['atype'] == "float":
  270. self._data[index.row()][index.column()] = float(
  271. self._data[index.row()][index.column()])
  272. self.dataChanged.emit(index, index)
  273. except Exception as instance:
  274. print(instance)
  275. class PreviewTable(QTableView):
  276. """Import wizard preview widget"""
  277. def __init__(self, parent):
  278. QTableView.__init__(self, parent)
  279. self._model = None
  280. # Setting up actions
  281. self.date_dayfirst_action = create_action(self, "dayfirst",
  282. triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=True))
  283. self.date_monthfirst_action = create_action(self, "monthfirst",
  284. triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=False))
  285. self.perc_action = create_action(self, "perc",
  286. triggered=ft_partial(self.parse_to_type, atype="perc"))
  287. self.acc_action = create_action(self, "account",
  288. triggered=ft_partial(self.parse_to_type, atype="account"))
  289. self.str_action = create_action(self, "unicode",
  290. triggered=ft_partial(self.parse_to_type, atype="unicode"))
  291. self.int_action = create_action(self, "int",
  292. triggered=ft_partial(self.parse_to_type, atype="int"))
  293. self.float_action = create_action(self, "float",
  294. triggered=ft_partial(self.parse_to_type, atype="float"))
  295. # Setting up menus
  296. self.date_menu = QMenu()
  297. self.date_menu.setTitle("Date")
  298. add_actions( self.date_menu, (self.date_dayfirst_action,
  299. self.date_monthfirst_action))
  300. self.parse_menu = QMenu(self)
  301. self.parse_menu.addMenu(self.date_menu)
  302. add_actions( self.parse_menu, (self.perc_action, self.acc_action))
  303. self.parse_menu.setTitle("String to")
  304. self.opt_menu = QMenu(self)
  305. self.opt_menu.addMenu(self.parse_menu)
  306. add_actions( self.opt_menu, (self.str_action, self.int_action,
  307. self.float_action))
  308. def _shape_text(self, text, colsep=u("\t"), rowsep=u("\n"),
  309. transpose=False, skiprows=0, comments='#'):
  310. """Decode the shape of the given text"""
  311. assert colsep != rowsep
  312. out = []
  313. text_rows = text.split(rowsep)[skiprows:]
  314. for row in text_rows:
  315. stripped = to_text_string(row).strip()
  316. if len(stripped) == 0 or stripped.startswith(comments):
  317. continue
  318. line = to_text_string(row).split(colsep)
  319. line = [try_to_parse(to_text_string(x)) for x in line]
  320. out.append(line)
  321. # Replace missing elements with np.nan's or None's
  322. if programs.is_module_installed('numpy'):
  323. from numpy import nan
  324. out = list(zip_longest(*out, fillvalue=nan))
  325. else:
  326. out = list(zip_longest(*out, fillvalue=None))
  327. # Tranpose the last result to get the expected one
  328. out = [[r[col] for r in out] for col in range(len(out[0]))]
  329. if transpose:
  330. return [[r[col] for r in out] for col in range(len(out[0]))]
  331. return out
  332. def get_data(self):
  333. """Return model data"""
  334. if self._model is None:
  335. return None
  336. return self._model.get_data()
  337. def process_data(self, text, colsep=u("\t"), rowsep=u("\n"),
  338. transpose=False, skiprows=0, comments='#'):
  339. """Put data into table model"""
  340. data = self._shape_text(text, colsep, rowsep, transpose, skiprows,
  341. comments)
  342. self._model = PreviewTableModel(data)
  343. self.setModel(self._model)
  344. @Slot()
  345. def parse_to_type(self,**kwargs):
  346. """Parse to a given type"""
  347. indexes = self.selectedIndexes()
  348. if not indexes: return
  349. for index in indexes:
  350. self.model().parse_data_type(index, **kwargs)
  351. def contextMenuEvent(self, event):
  352. """Reimplement Qt method"""
  353. self.opt_menu.popup(event.globalPos())
  354. event.accept()
  355. class PreviewWidget(QWidget):
  356. """Import wizard preview widget"""
  357. def __init__(self, parent):
  358. QWidget.__init__(self, parent)
  359. vert_layout = QVBoxLayout()
  360. # Type frame
  361. type_layout = QHBoxLayout()
  362. type_label = QLabel(_("Import as"))
  363. type_layout.addWidget(type_label)
  364. self.array_btn = array_btn = QRadioButton(_("array"))
  365. array_btn.setEnabled(ndarray is not FakeObject)
  366. array_btn.setChecked(ndarray is not FakeObject)
  367. type_layout.addWidget(array_btn)
  368. list_btn = QRadioButton(_("list"))
  369. list_btn.setChecked(not array_btn.isChecked())
  370. type_layout.addWidget(list_btn)
  371. if pd:
  372. self.df_btn = df_btn = QRadioButton(_("DataFrame"))
  373. df_btn.setChecked(False)
  374. type_layout.addWidget(df_btn)
  375. h_spacer = QSpacerItem(40, 20,
  376. QSizePolicy.Expanding, QSizePolicy.Minimum)
  377. type_layout.addItem(h_spacer)
  378. type_frame = QFrame()
  379. type_frame.setLayout(type_layout)
  380. self._table_view = PreviewTable(self)
  381. vert_layout.addWidget(type_frame)
  382. vert_layout.addWidget(self._table_view)
  383. self.setLayout(vert_layout)
  384. def open_data(self, text, colsep=u("\t"), rowsep=u("\n"),
  385. transpose=False, skiprows=0, comments='#'):
  386. """Open clipboard text as table"""
  387. if pd:
  388. self.pd_text = text
  389. self.pd_info = dict(sep=colsep, lineterminator=rowsep,
  390. skiprows=skiprows,comment=comments)
  391. self._table_view.process_data(text, colsep, rowsep, transpose,
  392. skiprows, comments)
  393. def get_data(self):
  394. """Return table data"""
  395. return self._table_view.get_data()
  396. class ImportWizard(QDialog):
  397. """Text data import wizard"""
  398. def __init__(self, parent, text,
  399. title=None, icon=None, contents_title=None, varname=None):
  400. QDialog.__init__(self, parent)
  401. # Destroying the C++ object right after closing the dialog box,
  402. # otherwise it may be garbage-collected in another QThread
  403. # (e.g. the editor's analysis thread in Spyder), thus leading to
  404. # a segmentation fault on UNIX or an application crash on Windows
  405. self.setAttribute(Qt.WA_DeleteOnClose)
  406. if title is None:
  407. title = _("Import wizard")
  408. self.setWindowTitle(title)
  409. if icon is None:
  410. self.setWindowIcon(get_icon("fileimport.png"))
  411. if contents_title is None:
  412. contents_title = _("Raw text")
  413. if varname is None:
  414. varname = _("variable_name")
  415. self.var_name, self.clip_data = None, None
  416. # Setting GUI
  417. self.tab_widget = QTabWidget(self)
  418. self.text_widget = ContentsWidget(self, text)
  419. self.table_widget = PreviewWidget(self)
  420. self.tab_widget.addTab(self.text_widget, _("text"))
  421. self.tab_widget.setTabText(0, contents_title)
  422. self.tab_widget.addTab(self.table_widget, _("table"))
  423. self.tab_widget.setTabText(1, _("Preview"))
  424. self.tab_widget.setTabEnabled(1, False)
  425. name_layout = QHBoxLayout()
  426. name_label = QLabel(_("Variable Name"))
  427. name_layout.addWidget(name_label)
  428. self.name_edt = QLineEdit()
  429. self.name_edt.setText(varname)
  430. name_layout.addWidget(self.name_edt)
  431. btns_layout = QHBoxLayout()
  432. cancel_btn = QPushButton(_("Cancel"))
  433. btns_layout.addWidget(cancel_btn)
  434. cancel_btn.clicked.connect(self.reject)
  435. h_spacer = QSpacerItem(40, 20,
  436. QSizePolicy.Expanding, QSizePolicy.Minimum)
  437. btns_layout.addItem(h_spacer)
  438. self.back_btn = QPushButton(_("Previous"))
  439. self.back_btn.setEnabled(False)
  440. btns_layout.addWidget(self.back_btn)
  441. self.back_btn.clicked.connect(ft_partial(self._set_step, step=-1))
  442. self.fwd_btn = QPushButton(_("Next"))
  443. btns_layout.addWidget(self.fwd_btn)
  444. self.fwd_btn.clicked.connect(ft_partial(self._set_step, step=1))
  445. self.done_btn = QPushButton(_("Done"))
  446. self.done_btn.setEnabled(False)
  447. btns_layout.addWidget(self.done_btn)
  448. self.done_btn.clicked.connect(self.process)
  449. self.text_widget.asDataChanged.connect(self.fwd_btn.setEnabled)
  450. self.text_widget.asDataChanged.connect(self.done_btn.setDisabled)
  451. layout = QVBoxLayout()
  452. layout.addLayout(name_layout)
  453. layout.addWidget(self.tab_widget)
  454. layout.addLayout(btns_layout)
  455. self.setLayout(layout)
  456. def _focus_tab(self, tab_idx):
  457. """Change tab focus"""
  458. for i in range(self.tab_widget.count()):
  459. self.tab_widget.setTabEnabled(i, False)
  460. self.tab_widget.setTabEnabled(tab_idx, True)
  461. self.tab_widget.setCurrentIndex(tab_idx)
  462. def _set_step(self, step):
  463. """Proceed to a given step"""
  464. new_tab = self.tab_widget.currentIndex() + step
  465. assert new_tab < self.tab_widget.count() and new_tab >= 0
  466. if new_tab == self.tab_widget.count()-1:
  467. try:
  468. self.table_widget.open_data(self._get_plain_text(),
  469. self.text_widget.get_col_sep(),
  470. self.text_widget.get_row_sep(),
  471. self.text_widget.trnsp_box.isChecked(),
  472. self.text_widget.get_skiprows(),
  473. self.text_widget.get_comments())
  474. self.done_btn.setEnabled(True)
  475. self.done_btn.setDefault(True)
  476. self.fwd_btn.setEnabled(False)
  477. self.back_btn.setEnabled(True)
  478. except (SyntaxError, AssertionError) as error:
  479. QMessageBox.critical(self, _("Import wizard"),
  480. _("<b>Unable to proceed to next step</b>"
  481. "<br><br>Please check your entries."
  482. "<br><br>Error message:<br>%s") % str(error))
  483. return
  484. elif new_tab == 0:
  485. self.done_btn.setEnabled(False)
  486. self.fwd_btn.setEnabled(True)
  487. self.back_btn.setEnabled(False)
  488. self._focus_tab(new_tab)
  489. def get_data(self):
  490. """Return processed data"""
  491. # It is import to avoid accessing Qt C++ object as it has probably
  492. # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
  493. return self.var_name, self.clip_data
  494. def _simplify_shape(self, alist, rec=0):
  495. """Reduce the alist dimension if needed"""
  496. if rec != 0:
  497. if len(alist) == 1:
  498. return alist[-1]
  499. return alist
  500. if len(alist) == 1:
  501. return self._simplify_shape(alist[-1], 1)
  502. return [self._simplify_shape(al, 1) for al in alist]
  503. def _get_table_data(self):
  504. """Return clipboard processed as data"""
  505. data = self._simplify_shape(
  506. self.table_widget.get_data())
  507. if self.table_widget.array_btn.isChecked():
  508. return array(data)
  509. elif pd and self.table_widget.df_btn.isChecked():
  510. info = self.table_widget.pd_info
  511. buf = io.StringIO(self.table_widget.pd_text)
  512. return pd.read_csv(buf, **info)
  513. return data
  514. def _get_plain_text(self):
  515. """Return clipboard as text"""
  516. return self.text_widget.text_editor.toPlainText()
  517. @Slot()
  518. def process(self):
  519. """Process the data from clipboard"""
  520. var_name = self.name_edt.text()
  521. try:
  522. self.var_name = str(var_name)
  523. except UnicodeEncodeError:
  524. self.var_name = to_text_string(var_name)
  525. if self.text_widget.get_as_data():
  526. self.clip_data = self._get_table_data()
  527. elif self.text_widget.get_as_code():
  528. self.clip_data = try_to_eval(
  529. to_text_string(self._get_plain_text()))
  530. else:
  531. self.clip_data = to_text_string(self._get_plain_text())
  532. self.accept()
  533. def test(text):
  534. """Test"""
  535. from spyderlib.utils.qthelpers import qapplication
  536. _app = qapplication() # analysis:ignore
  537. dialog = ImportWizard(None, text)
  538. if dialog.exec_():
  539. print(dialog.get_data())
  540. if __name__ == "__main__":
  541. test(u("17/11/1976\t1.34\n14/05/09\t3.14"))