diff --git a/README.md b/README.md
index 16f9786..0e09f21 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Document API
The Document API provides a supported way to programmatically make updates to Tableau workbook and data source files. If you've been making changes to these file types by directly updating the XML--that is, by XML hacking--this SDK is for you :)
Features include:
-- Support for 8.X, 9.X, and 10.X workbook and data source files
+- Support for 9.X, and 10.X workbook and data source files
- Including TDSX and TWBX files
- Getting connection information from data sources and workbooks
- Server Name
diff --git a/tableaudocumentapi/datasource.py b/tableaudocumentapi/datasource.py
index 7e3c2af..3f64145 100644
--- a/tableaudocumentapi/datasource.py
+++ b/tableaudocumentapi/datasource.py
@@ -7,11 +7,11 @@
import itertools
import xml.etree.ElementTree as ET
import xml.sax.saxutils as sax
-import zipfile
from tableaudocumentapi import Connection, xfile
from tableaudocumentapi import Field
from tableaudocumentapi.multilookup_dict import MultiLookupDict
+from tableaudocumentapi.xfile import xml_open
########
# This is needed in order to determine if something is a string or not. It is necessary because
@@ -113,10 +113,7 @@ def __init__(self, dsxml, filename=None):
def from_file(cls, filename):
"""Initialize datasource from file (.tds)"""
- if zipfile.is_zipfile(filename):
- dsxml = xfile.get_xml_from_archive(filename).getroot()
- else:
- dsxml = ET.parse(filename).getroot()
+ dsxml = xml_open(filename).getroot()
return cls(dsxml, filename)
def save(self):
diff --git a/tableaudocumentapi/workbook.py b/tableaudocumentapi/workbook.py
index fd85b3c..28ddd03 100644
--- a/tableaudocumentapi/workbook.py
+++ b/tableaudocumentapi/workbook.py
@@ -10,6 +10,7 @@
import xml.etree.ElementTree as ET
from tableaudocumentapi import Datasource, xfile
+from tableaudocumentapi.xfile import xml_open
class Workbook(object):
@@ -31,12 +32,7 @@ def __init__(self, filename):
self._filename = filename
- # Determine if this is a twb or twbx and get the xml root
- if zipfile.is_zipfile(self._filename):
- self._workbookTree = xfile.get_xml_from_archive(
- self._filename)
- else:
- self._workbookTree = ET.parse(self._filename)
+ self._workbookTree = xml_open(self._filename)
self._workbookRoot = self._workbookTree.getroot()
# prepare our datasource objects
@@ -145,6 +141,7 @@ def _prepare_worksheets(xml_root, ds_index):
datasource = ds_index[datasource_name]
for column in dependency.findall('.//column'):
column_name = column.attrib['name']
- datasource.fields[column_name].add_used_in(worksheet_name)
+ if column_name in datasource.fields:
+ datasource.fields[column_name].add_used_in(worksheet_name)
return worksheets
diff --git a/tableaudocumentapi/xfile.py b/tableaudocumentapi/xfile.py
index 13e08c7..a0cd62e 100644
--- a/tableaudocumentapi/xfile.py
+++ b/tableaudocumentapi/xfile.py
@@ -3,9 +3,31 @@
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
+
+
+def xml_open(filename):
+ # Determine if this is a twb or twbx and get the xml root
+ if zipfile.is_zipfile(filename):
+ tree = get_xml_from_archive(filename)
+ else:
+ tree = ET.parse(filename)
+ file_version = Version(tree.getroot().attrib.get('version', '0.0'))
+ if file_version < MIN_SUPPORTED_VERSION:
+ raise TableauVersionNotSupportedException(file_version)
+ return tree
+
@contextlib.contextmanager
def temporary_directory(*args, **kwargs):
@@ -16,10 +38,10 @@ def temporary_directory(*args, **kwargs):
shutil.rmtree(d)
-def find_file_in_zip(zip):
- for filename in zip.namelist():
+def find_file_in_zip(zip_file):
+ for filename in zip_file.namelist():
try:
- with zip.open(filename) as xml_candidate:
+ with zip_file.open(filename) as xml_candidate:
ET.parse(xml_candidate).getroot().tag in (
'workbook', 'datasource')
return filename
@@ -36,14 +58,14 @@ def get_xml_from_archive(filename):
return xml_tree
-def build_archive_file(archive_contents, zip):
+def build_archive_file(archive_contents, zip_file):
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.write(temp_file_full_path, arcname=zipname)
+ zip_file.write(temp_file_full_path, arcname=zipname)
def save_into_archive(xml_tree, filename, new_filename=None):
diff --git a/test/assets/ephemeral_field.twb b/test/assets/ephemeral_field.twb
new file mode 100644
index 0000000..030c433
--- /dev/null
+++ b/test/assets/ephemeral_field.twb
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: