FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
document-api-python/tableaudocumentapi/xfile.py at master · tailsdotcom/document-api-python · GitHub
Uh oh!
There was an error while loading.
Please reload this page
.
tailsdotcom
/
document-api-python
Public
forked from
tableau/document-api-python
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
document-api-python
/
tableaudocumentapi
/
xfile.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
131 lines (97 loc) · 4.25 KB
Breadcrumbs
document-api-python
/
tableaudocumentapi
/
xfile.py
Copy path
File metadata and controls
131 lines (97 loc) · 4.25 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import
contextlib
import
os
import
shutil
import
tempfile
import
zipfile
import
xml
.
etree
.
ElementTree
as
ET
try
:
from
distutils2
.
version
import
NormalizedVersion
as
Version
except
ImportError
:
from
distutils
.
version
import
LooseVersion
as
Version
MIN_SUPPORTED_VERSION
=
Version
(
"9.0"
)
class
TableauVersionNotSupportedException
(
Exception
):
pass
class
TableauInvalidFileException
(
Exception
):
pass
def
xml_open
(
filename
,
expected_root
=
None
):
"""Opens the provided 'filename'. Handles detecting if the file is an archive,
detecting the document version, and validating the root tag."""
# Is the file a zip (.twbx or .tdsx)
if
zipfile
.
is_zipfile
(
filename
):
tree
=
get_xml_from_archive
(
filename
)
else
:
tree
=
ET
.
parse
(
filename
)
# Is the file a supported version
tree_root
=
tree
.
getroot
()
file_version
=
Version
(
tree_root
.
attrib
.
get
(
'version'
,
'0.0'
))
if
file_version
<
MIN_SUPPORTED_VERSION
:
raise
TableauVersionNotSupportedException
(
file_version
)
# Does the root tag match the object type (workbook or data source)
if
expected_root
and
(
expected_root
!=
tree_root
.
tag
):
raise
TableauInvalidFileException
(
"'{}'' is not a valid '{}' file"
.
format
(
filename
,
expected_root
))
return
tree
@
contextlib
.
contextmanager
def
temporary_directory
(
*
args
,
**
kwargs
):
d
=
tempfile
.
mkdtemp
(
*
args
,
**
kwargs
)
try
:
yield
d
finally
:
shutil
.
rmtree
(
d
)
def
find_file_in_zip
(
zip_file
):
'''Returns the twb/tds file from a Tableau packaged file format. Packaged
files can contain cache entries which are also valid XML, so only look for
files with a .tds or .twb extension.
'''
candidate_files
=
filter
(
lambda
x
:
x
.
split
(
'.'
)[
-
1
]
in
(
'twb'
,
'tds'
),
zip_file
.
namelist
())
for
filename
in
candidate_files
:
with
zip_file
.
open
(
filename
)
as
xml_candidate
:
try
:
ET
.
parse
(
xml_candidate
)
return
filename
except
ET
.
ParseError
:
# That's not an XML file by gosh
pass
def
get_xml_from_archive
(
filename
):
with
zipfile
.
ZipFile
(
filename
)
as
zf
:
with
zf
.
open
(
find_file_in_zip
(
zf
))
as
xml_file
:
xml_tree
=
ET
.
parse
(
xml_file
)
return
xml_tree
def
build_archive_file
(
archive_contents
,
zip_file
):
"""Build a Tableau-compatible archive file."""
# This is tested against Desktop and Server, and reverse engineered by lots
# of trial and error. Do not change this logic.
for
root_dir
,
_
,
files
in
os
.
walk
(
archive_contents
):
relative_dir
=
os
.
path
.
relpath
(
root_dir
,
archive_contents
)
for
f
in
files
:
temp_file_full_path
=
os
.
path
.
join
(
archive_contents
,
relative_dir
,
f
)
zipname
=
os
.
path
.
join
(
relative_dir
,
f
)
zip_file
.
write
(
temp_file_full_path
,
arcname
=
zipname
)
def
save_into_archive
(
xml_tree
,
filename
,
new_filename
=
None
):
# Saving an archive means extracting the contents into a temp folder,
# saving the changes over the twb/tds in that folder, and then
# packaging it back up into a zip with a very specific format
# e.g. no empty files for directories, which Windows and Mac do by default
if
new_filename
is
None
:
new_filename
=
filename
# Extract to temp directory
with
temporary_directory
()
as
temp_path
:
with
zipfile
.
ZipFile
(
filename
)
as
zf
:
xml_file
=
find_file_in_zip
(
zf
)
zf
.
extractall
(
temp_path
)
# Write the new version of the file to the temp directory
xml_tree
.
write
(
os
.
path
.
join
(
temp_path
,
xml_file
),
encoding
=
"utf-8"
,
xml_declaration
=
True
)
# Write the new archive with the contents of the temp folder
with
zipfile
.
ZipFile
(
new_filename
,
"w"
,
compression
=
zipfile
.
ZIP_DEFLATED
)
as
new_archive
:
build_archive_file
(
temp_path
,
new_archive
)
def
_save_file
(
container_file
,
xml_tree
,
new_filename
=
None
):
ET
.
register_namespace
(
"user"
,
"http://www.tableausoftware.com/xml/user"
)
if
new_filename
is
None
:
new_filename
=
container_file
if
zipfile
.
is_zipfile
(
container_file
):
save_into_archive
(
xml_tree
,
container_file
,
new_filename
)
else
:
xml_tree
.
write
(
new_filename
,
encoding
=
"utf-8"
,
xml_declaration
=
True
)
Back
|
FazBrowse Home
|
New Git URL