CairoSVG
CairoSVG copied to clipboard
Error converting local file on Windows
Environment:
- OS: Windows
- Python: 3.4
It appears that since version 2.0, I'm unable to convert local files using cairosvg
CLI command on Windows:
$ cairosvg -o test.png input.svg
Traceback (most recent call last):
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 1345, in open_local_file
stats = os.stat(localfile)
FileNotFoundError: [WinError 3] The system cannot find the path specified: ''
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\runpy.py", line 170, in _run_module_as_main
"__main__", mod_spec)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "C:\Users\rasse\Anaconda3\envs\gelutils-py34\Scripts\cairosvg.exe\__main__.py", line 9, in <module>
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\site-packages\cairosvg\__init__.py", line 96, in main
SURFACES[output_format.upper()].convert(**kwargs)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\site-packages\cairosvg\surface.py", line 135, in convert
tree = Tree(**kwargs)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\site-packages\cairosvg\parser.py", line 382, in __init__
parse_url(self.url), 'image/svg+xml')
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\site-packages\cairosvg\parser.py", line 258, in fetch_url
return read_url(url, self.url_fetcher, resource_type)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\site-packages\cairosvg\url.py", line 96, in read_url
return url_fetcher(url, resource_type)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\site-packages\cairosvg\url.py", line 42, in fetch
return urlopen(Request(url, headers=HTTP_HEADERS)).read()
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 161, in urlopen
return opener.open(url, data, timeout)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 466, in open
response = self._open(req, data)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 484, in _open
'_open', req)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 443, in _call_chain
result = func(*args)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 1319, in file_open
return self.open_local_file(req)
File "c:\users\rasse\anaconda3\envs\gelutils-py34\lib\urllib\request.py", line 1363, in open_local_file
raise URLError(exp)
urllib.error.URLError: <urlopen error [WinError 3] The system cannot find the path specified: ''>
The error happens with CairoSVG 2.0.0 and 2.1.3 (and, I assume, versions in between).
It did not happen with CairoSVG 1.0.22.
Adding file:///
in front of the filename does work:
cairosvg -o test.png file:///D:/temp/input.svg
cairosvg -o test.png file:///D:\temp\input.svg
# This does not work:
cairosvg -o test.png "file://D:\temp\input.svg"
Notice the three slashes after file:
.
The reason this works is because urllib interprets the string between file://
(the scheme) and the third slash as the request host.
On Windows, this means that when cairosvg.url.read_url()/fetch()
passes the url argument to urllib.request.Request
the filename is interpreted as the host, leaving the selector
part of the request empty.
When the file is opened by urllib.request.FileHandler.open_local_file()
it uses the empty req.selector
as the local filename. When it then tries to os.stat(localfile)
with an empty filename, it raises an exception.
On Mac/Linux, absolute filepaths starts with /
, which effectively adds the third slash in file:///
. So this, I expect, is only an issue on Windows, where absolute filepaths starts with the drive letter (and we also use backslash as path separator). Relative paths are converted to absolute paths in cairosvg.url.read_url()
.
FWIW, version 1.0.22 had an explicit check to see if the url looked like a local windows filepath, and dealing accordingly in that was case.
https://github.com/Kozea/CairoSVG/blob/1.0.22/cairosvg/parser.py#L299
The following also works:
cairosvg -o test.png "file://localhost/D:/temp/input.svg"
cairosvg -o test.png "file://localhost/D:\temp\input.svg"
cairosvg -o test.png "file://localhost//D:/temp/input.svg"
cairosvg -o test.png "file://localhost//D:\temp\input.svg"
Maybe a fix could be to change https://github.com/Kozea/CairoSVG/blob/2.1.3/cairosvg/url.py#L95, adding the host information explicitly to satisfy the host
part when Request
parses the url?
<<< url = 'file://{}'.format(os.path.abspath(url.geturl()))
>>> url = 'file://localhost/{}'.format(os.path.abspath(url.geturl()))
Would this work on Mac and Linux? Let me know if you would like a PR with this change.
I've run into this issue as well and ended up patching the function read_url referenced by @scholer with:
def read_url(url, url_fetcher, resource_type):
"""Get bytes in a parsed ``url`` using ``url_fetcher``.
If ``url_fetcher`` is None a default (no limitations) URLFetcher is used.
"""
if url.scheme:
url_ = url.geturl()
else:
# urllib fails to properly open local files on Windows if backslashes
# are used in the file URL like: file://C:\directory\file.svg
if sys.platform == 'win32':
path = os.path.abspath(url.geturl()).replace('\\', '/')
# The root slash must be prepended as well.
url_ = 'file:///{}'.format(path)
else:
path = os.path.abspath(url.geturl())
url_ = 'file://{}'.format(path)
return url_fetcher(url_, resource_type)
This works, however, I'm not sure if just swapping the slashes is the safest approach to converting to a valid path format for urllib
.
The above replacement didn't work for me. This still appears to be a problem. Any idea how I can rollback to a previous version without this error?
Additionally, I'm calling CairoSVG as a function in python code.