Skip to content

Commit ef07a43

Browse files
committed
Plumbing hooked up for Import zip with members.
Now we just gotta get ImportString[xxx, "JSON"] working on its own. Also, other small fixes and improvements.
1 parent 0a64e33 commit ef07a43

5 files changed

Lines changed: 138 additions & 74 deletions

File tree

mathics/builtin/import_export/compression.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ class ImportZIP(Builtin):
3030
def eval(self, path: String, evaluation: Evaluation):
3131
"ImportZIP[path_String]"
3232
return eval_ImportZIP(path.value, evaluation)
33+
34+
def eval_with_elements(self, path: String, elements, evaluation: Evaluation):
35+
"ImportZIP[path_String, elements_]"
36+
return eval_ImportZIP(path.value, evaluation, elements)

mathics/builtin/import_export/importexport.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
from itertools import chain
1414
from urllib.error import HTTPError, URLError
1515

16-
from mathics.builtin.import_export.checking import (
17-
check_filename,
18-
import_setup_check,
19-
)
16+
# Use this when accessing IMPORTERS to get changes
17+
# since initializiation.
18+
import mathics.eval.import_export.importexport as importexport
19+
from mathics.builtin.import_export.checking import check_filename, import_setup_check
2020
from mathics.core.atoms import ByteArray
2121
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
2222
from mathics.core.builtin import Builtin, Integer, Predefined, String
@@ -36,7 +36,6 @@
3636
from mathics.eval.files_io.files import eval_Close
3737
from mathics.eval.files_io.filesystem import eval_FindFile
3838
from mathics.eval.import_export.importexport import (
39-
IMPORTERS,
4039
MIMETYPE_TO_SHORTNAME,
4140
eval_FileFormat,
4241
eval_Import_data_only,
@@ -92,7 +91,9 @@ class ImportFormats(Predefined):
9291
summary_text = "list supported import formats"
9392

9493
def evaluate(self, evaluation: Evaluation):
95-
return to_mathics_list(*sorted(IMPORTERS.keys()), elements_conversion_fn=String)
94+
return to_mathics_list(
95+
*sorted(importexport.IMPORTERS.keys()), elements_conversion_fn=String
96+
)
9697

9798

9899
class RegisterImport(Builtin):
@@ -238,7 +239,7 @@ def eval(
238239
# as well.
239240
# By doing this, we accept "text, "Text", "TEXT", and other combinations,
240241
# which what WMA seems to do.
241-
IMPORTERS[formatname.value.upper()] = (
242+
importexport.IMPORTERS[formatname.value.upper()] = (
242243
conditionals,
243244
default,
244245
posts,
@@ -455,15 +456,16 @@ def eval_source_only(self, source, evaluation, options={}):
455456
def eval_with_element_list(self, source, elements, evaluation, options={}):
456457
"Import[source_, elements_List?(AllTrue[#, NotOptionQ]&), OptionsPattern[]]"
457458

458-
findfile, data = import_setup_check(source, evaluation)
459+
findfile, file_format = import_setup_check(source, evaluation)
459460
if findfile is SymbolFailed:
460461
return SymbolFailed
461462

463+
# FIXME remove the need for determine_filetype
462464
def determine_filetype(data: str) -> str:
463-
return data
465+
return file_format
464466

465467
return eval_Import_general(
466-
findfile, determine_filetype, elements, evaluation, options, data
468+
findfile, determine_filetype, elements, evaluation, options
467469
)
468470

469471
# In contrast to Import[source_], we allow an explicit format type
@@ -481,7 +483,7 @@ def eval_with_single_element(self, source, elt: String, evaluation, options={}):
481483
# The code below tests for the first case, and if that fails assumes the
482484
# second case.
483485
file_format = elt.value.upper()
484-
if file_format in IMPORTERS.keys():
486+
if file_format in importexport.IMPORTERS.keys():
485487
# A file format was specified: use the custom routine
486488
return eval_Import_source_only(findfile, file_format, evaluation, options)
487489

@@ -563,7 +565,7 @@ def eval_with_single_element(self, data, elt: String, evaluation, options={}):
563565
# The code below tests for the first case, and if that fails assumes the
564566
# second case.
565567
file_format = elt.value.upper()
566-
if file_format in IMPORTERS.keys():
568+
if file_format in importexport.IMPORTERS.keys():
567569
# A file format was specified: use the custom routine
568570
return eval_Import_data_only(data.value, file_format, evaluation, options)
569571

mathics/eval/import_export/compression.py

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from mathics.core.evaluation import Evaluation
77
from mathics.core.expression import Expression
88
from mathics.core.list import ListExpression
9+
from mathics.core.symbols import SymbolNull
910
from mathics.core.systemsymbols import SymbolFailed, SymbolRule
1011
from mathics.eval.import_export.importexport import (
1112
IMPORTERS,
@@ -17,42 +18,59 @@
1718
def eval_ImportZIP(
1819
zip_path: str, evaluation: Evaluation, members: Optional[list[str]] = None
1920
) -> ListExpression:
20-
"""Takes a ZIP file path and returns a list of file names/paths contained inside."""
21-
with zipfile.ZipFile(zip_path, "r") as archive:
22-
if members is None:
23-
filenames = archive.namelist()
24-
mathics_filenames = to_mathics_list(*filenames)
25-
exprs = [
26-
Expression(
27-
SymbolRule,
28-
String("FileNames"),
29-
mathics_filenames,
30-
),
31-
Expression(
32-
SymbolRule,
33-
String("Summary"),
34-
mathics_filenames,
35-
),
36-
]
37-
38-
if filenames:
39-
for filename in filenames:
40-
exprs.append(
41-
Expression(
42-
SymbolRule,
43-
String(filename),
44-
String(archive.read(filename).decode("utf-8")),
21+
"""If `members` is empty, this function takes a ZIP file path and returns a
22+
list of file names/paths contained inside."""
23+
try:
24+
with zipfile.ZipFile(zip_path, "r") as archive:
25+
if members is None:
26+
filenames = archive.namelist()
27+
mathics_filenames = to_mathics_list(*filenames)
28+
exprs = [
29+
Expression(
30+
SymbolRule,
31+
String("FileNames"),
32+
mathics_filenames,
33+
),
34+
Expression(
35+
SymbolRule,
36+
String("Summary"),
37+
mathics_filenames,
38+
),
39+
]
40+
41+
if filenames:
42+
for filename in filenames:
43+
exprs.append(
44+
Expression(
45+
SymbolRule,
46+
String(filename),
47+
String(archive.read(filename).decode("utf-8")),
48+
)
4549
)
46-
)
4750

48-
return ListExpression(*exprs)
51+
return ListExpression(*exprs)
52+
53+
if members.has_form("List", None):
54+
elements = members.get_elements()
55+
else:
56+
elements = [members]
4957

50-
for member in members:
51-
file_format = infer_file_format(member).upper()
52-
if file_format not in IMPORTERS.keys():
53-
evaluation.message("Import", "fmtnosup", file_format)
54-
return SymbolFailed
58+
for element in elements:
59+
member = element.value
60+
file_format = infer_file_format(member).upper()
61+
if file_format not in IMPORTERS.keys():
62+
evaluation.message("Import", "fmtnosup", file_format)
63+
return SymbolFailed
5564

56-
file_data = archive.read(member)
57-
# FIXME: this handles one member. What do we do if we have more?
58-
return eval_Import_data_only(file_data.value, file_format, evaluation, {})
65+
file_data = archive.read(member).decode("utf-8")
66+
# FIXME: this handles one member. What do we do if we have more?
67+
return eval_Import_data_only(file_data, file_format, evaluation, {})
68+
except FileNotFoundError:
69+
evaluation.message("Import", "nffil", String(zip_path))
70+
return SymbolFailed
71+
except PermissionError:
72+
evaluation.message("Import", "noopen", String(zip_path))
73+
return SymbolFailed
74+
except Exception:
75+
# This seems to be what WMA does.
76+
return SymbolNull

mathics/eval/import_export/importexport.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
SymbolByteArray,
2121
SymbolFailed,
2222
SymbolInputStream,
23+
SymbolNone,
2324
SymbolOpenWrite,
2425
SymbolRule,
2526
SymbolStringToStream,
@@ -37,7 +38,7 @@
3738
IMPORTERS = {}
3839

3940
# TODO: This hard-coded dictionary should be
40-
# accessile from the WL API, and be user modifiable.
41+
# accessible from the WL API, and be user modifiable.
4142
FILE_EXTENSION_MAP: dict[str, str] = {
4243
"bmp": "BMP",
4344
"gif": "GIF",
@@ -219,7 +220,7 @@ def importer_exporter_options(
219220

220221
def eval_FileFormat(path: str) -> String:
221222
"""
222-
Basic implemenation beind FileFormat[filename].
223+
Basic implementation behind FileFormat[filename].
223224
"""
224225
return String(filetype_from_path(path))
225226

@@ -230,10 +231,10 @@ def eval_Import_general(
230231
elements,
231232
evaluation: Evaluation,
232233
options,
233-
data: Optional[str],
234+
data: Optional[str] = None,
234235
):
235236
"""
236-
Basic implementation beind most general kind of Import[source, elements, options].
237+
Basic implementation behind most general kind of Import[source, elements, options].
237238
"""
238239

239240
current_predetermined_out = evaluation.predetermined_out
@@ -252,12 +253,13 @@ def eval_Import_general(
252253
elements = [el.value for el in elements]
253254

254255
# Determine file format
255-
for el in elements:
256+
file_format = None
257+
for el in elements.copy():
256258
if el.upper() in IMPORTERS.keys():
257259
file_format = el.upper()
258260
elements.remove(el)
259-
break
260-
else:
261+
262+
if file_format is None:
261263
filetype = determine_filetype(data)
262264
file_format = MIME_SHORTNAME_TO_WMA.get(filetype, filetype).upper()
263265

@@ -331,7 +333,37 @@ def eval_Import_general(
331333
assert len(elements) >= 1
332334
el = elements[0]
333335
if el == "Elements":
334-
return eval_Import_Elements(file_format, evaluation)
336+
if (
337+
result := eval_Import_Elements(file_format, evaluation)
338+
) is not SymbolNone:
339+
return result
340+
# A list of "Elements" is not obtainable via AvailableElements listed when
341+
# ImportExport`RegisterImport was used. Get a list of the field names via
342+
# the the "defaults" and "conditional" keys.
343+
defaults = get_results(
344+
default_function,
345+
findfile,
346+
function_channels,
347+
stream_options,
348+
custom_options,
349+
evaluation,
350+
options,
351+
data=data,
352+
)
353+
if defaults is None:
354+
evaluation.predetermined_out = current_predetermined_out
355+
return SymbolFailed
356+
# Use set() to remove duplicates
357+
evaluation.predetermined_out = current_predetermined_out
358+
return from_python(
359+
sorted(
360+
set(
361+
list(conditionals.keys())
362+
+ list(defaults.keys())
363+
# + list(posts.keys())
364+
)
365+
)
366+
)
335367
else:
336368
if el in conditionals.keys():
337369
result = get_results(
@@ -342,8 +374,8 @@ def eval_Import_general(
342374
custom_options,
343375
evaluation,
344376
options,
345-
data=data,
346377
elements=elements,
378+
data=data,
347379
)
348380
if result is None:
349381
evaluation.predetermined_out = current_predetermined_out
@@ -378,7 +410,7 @@ def eval_Import_general(
378410
return SymbolFailed
379411

380412

381-
def eval_Import_Elements(file_format: str, evaluation: Evaluation):
413+
def eval_Import_Elements(file_format: str, evaluation):
382414
"""
383415
Basic implementation behind Import[fileformat, Elements].
384416
This returns the element names that can be used for a specific
@@ -484,7 +516,7 @@ def eval_Import_data_only(
484516
options,
485517
):
486518
"""
487-
Basic implementation beind Import_String[data].
519+
Basic implementation behind Import_String[data].
488520
Here, no elements were given, just a import data string.
489521
"""
490522

@@ -560,7 +592,7 @@ def eval_Import_source_only(
560592
options,
561593
):
562594
"""
563-
Basic implementation beind Import[source].
595+
Basic implementation behind Import[source].
564596
Here, no elements were given, just a import source.
565597
"""
566598

@@ -638,7 +670,7 @@ def get_results_for_element_args(
638670
elements: list,
639671
):
640672
"""
641-
Return Import results when elemnet args are given.
673+
Return Import results when element args are given.
642674
For example:
643675
Import["ExampleData/ExampleData.txt", "Lines"]
644676
^^^^^^^

test/builtin/import_export/test_importexport.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -171,33 +171,41 @@ def test_export():
171171
'Import["ExampleData/Testosterone.svg", "xml"] // Head',
172172
None,
173173
"XMLObject[Document]",
174-
"Case use in explicit format name should not be significant",
174+
"format case (xml) is not significant",
175175
),
176176
(
177177
'Import["ExampleData/Testosterone.svg", "Xml"] // Head',
178178
None,
179179
"XMLObject[Document]",
180-
"Case use in explicit format name should not be significant",
180+
"format case (Xml) is not significant",
181181
),
182182
(
183183
'Import["ExampleData/Testosterone.svg", {"XML"}] // Head',
184184
None,
185185
"XMLObject[Document]",
186186
None,
187187
),
188-
(
189-
'Import["ExampleData/Testosterone.svg", {"XML", "XML"}];',
190-
("The Import element XML is not present when importing as XML.",),
191-
"Null",
192-
None,
193-
),
188+
# This test does not match WMA. We are supposed to treat
189+
# {"XML", "XML"} like, "XML" and not give an error.
190+
# (
191+
# 'Import["ExampleData/Testosterone.svg", {"XML", "XML"}];',
192+
# ("The Import element XML is not present when importing as XML.",),
193+
# "Null",
194+
# None,
195+
# ),
194196
# XML
195-
(
196-
'MatchQ[Import["ExampleData/InventionNo1.xml", "Tags"],{__String}]',
197-
None,
198-
"True",
199-
None,
200-
),
197+
# This test does not match WMA. WMA gives:
198+
# XML`Parser`XMLGet::prserr:
199+
# NetAccessorException: Could not open file:
200+
# http://www.musicxml.org/dtds/partwise.dtd at Line: 2 Character: 123 in
201+
# InventionNo1.xml.
202+
# Import::fmterr: Cannot import data as XML format.
203+
# (
204+
# 'MatchQ[Import["ExampleData/InventionNo1.xml", "Tags"],{__String}]',
205+
# None,
206+
# "True",
207+
# None,
208+
# ),
201209
("ImportString[x]", ("First argument x is not a string.",), "$Failed", None),
202210
# CSV
203211
(

0 commit comments

Comments
 (0)