11import functools
22import xml .etree .ElementTree as ET
33
4+ from tableaudocumentapi .property_decorators import argument_is_one_of
45
56_ATTRIBUTES = [
67 'id' , # Name of the field as specified in the file, usually surrounded by [ ]
@@ -45,12 +46,14 @@ def __init__(self, column_xml=None, metadata_xml=None):
4546
4647 if column_xml is not None :
4748 self ._initialize_from_column_xml (column_xml )
49+ self ._xml = column_xml
4850 # This isn't currently never called because of the way we get the data from the xml,
4951 # but during the refactor, we might need it. This is commented out as a reminder
5052 # if metadata_xml is not None:
5153 # self.apply_metadata(metadata_xml)
5254
5355 elif metadata_xml is not None :
56+ self ._xml = metadata_xml
5457 self ._initialize_from_metadata_xml (metadata_xml )
5558
5659 else :
@@ -66,6 +69,16 @@ def _initialize_from_metadata_xml(self, xmldata):
6669 read_name = metadata_name )
6770 self .apply_metadata (xmldata )
6871
72+ @classmethod
73+ def create_field_xml (cls , caption , datatype , role , field_type , name ):
74+ column = ET .Element ('column' )
75+ column .set ('caption' , caption )
76+ column .set ('datatype' , datatype )
77+ column .set ('role' , role )
78+ column .set ('type' , field_type )
79+ column .set ('name' , name )
80+ return column
81+
6982 ########################################
7083 # Special Case methods for construction fields from various sources
7184 # not intended for client use
@@ -116,52 +129,200 @@ def id(self):
116129 """ Name of the field as specified in the file, usually surrounded by [ ] """
117130 return self ._id
118131
132+ @property
133+ def xml (self ):
134+ """ XML representation of the field. """
135+ return self ._xml
136+
137+ ########################################
138+ # Attribute getters and setters
139+ ########################################
140+
119141 @property
120142 def caption (self ):
121143 """ Name of the field as displayed in Tableau unless an aliases is defined """
122144 return self ._caption
123145
146+ @caption .setter
147+ def caption (self , caption ):
148+ """ Set the caption of a field
149+
150+ Args:
151+ caption: New caption. String.
152+
153+ Returns:
154+ Nothing.
155+ """
156+ self ._caption = caption
157+ self ._xml .set ('caption' , caption )
158+
124159 @property
125160 def alias (self ):
126161 """ Name of the field as displayed in Tableau if the default name isn't wanted """
127162 return self ._alias
128163
164+ @alias .setter
165+ def alias (self , alias ):
166+ """ Set the alias of a field
167+
168+ Args:
169+ alias: New alias. String.
170+
171+ Returns:
172+ Nothing.
173+ """
174+ self ._alias = alias
175+ self ._xml .set ('alias' , alias )
176+
129177 @property
130178 def datatype (self ):
131179 """ Type of the field within Tableau (string, integer, etc) """
132180 return self ._datatype
133181
182+ @datatype .setter
183+ @argument_is_one_of ('string' , 'integer' , 'date' , 'boolean' )
184+ def datatype (self , datatype ):
185+ """ Set the datatype of a field
186+
187+ Args:
188+ datatype: New datatype. String.
189+
190+ Returns:
191+ Nothing.
192+ """
193+ self ._datatype = datatype
194+ self ._xml .set ('datatype' , datatype )
195+
134196 @property
135197 def role (self ):
136198 """ Dimension or Measure """
137199 return self ._role
138200
201+ @role .setter
202+ @argument_is_one_of ('dimension' , 'measure' )
203+ def role (self , role ):
204+ """ Set the role of a field
205+
206+ Args:
207+ role: New role. String.
208+
209+ Returns:
210+ Nothing.
211+ """
212+ self ._role = role
213+ self ._xml .set ('role' , role )
214+
215+ @property
216+ def type (self ):
217+ """ Dimension or Measure """
218+ return self ._type
219+
220+ @type .setter
221+ @argument_is_one_of ('quantitative' , 'ordinal' , 'nominal' )
222+ def type (self , field_type ):
223+ """ Set the type of a field
224+
225+ Args:
226+ field_type: New type. String.
227+
228+ Returns:
229+ Nothing.
230+ """
231+ self ._type = field_type
232+ self ._xml .set ('type' , field_type )
233+
234+ ########################################
235+ # Aliases getter and setter
236+ # Those are NOT the 'alias' field of the column,
237+ # but instead the key-value aliases in its child elements
238+ ########################################
239+
240+ def add_alias (self , key , value ):
241+ """ Add an alias for a given display value.
242+
243+ Args:
244+ key: The data value to map. Example: "1". String.
245+ value: The display value for the key. Example: "True". String.
246+ Returns:
247+ Nothing.
248+ """
249+
250+ # determine whether there already is an aliases-tag
251+ aliases = self ._xml .find ('aliases' )
252+ # and create it if there isn't
253+ if not aliases :
254+ aliases = ET .Element ('aliases' )
255+ self ._xml .append (aliases )
256+
257+ # find out if an alias with this key already exists and use it
258+ existing_alias = [tag for tag in aliases .findall ('alias' ) if tag .get ('key' ) == key ]
259+ # if not, create a new ET.Element
260+ alias = existing_alias [0 ] if existing_alias else ET .Element ('alias' )
261+
262+ alias .set ('key' , key )
263+ alias .set ('value' , value )
264+ if not existing_alias :
265+ aliases .append (alias )
266+
267+ @property
268+ def aliases (self ):
269+ """ Returns all aliases that are registered under this field.
270+
271+ Returns:
272+ Key-value mappings of all registered aliases. Dict.
273+ """
274+ aliases_tag = self ._xml .find ('aliases' ) or []
275+ return {a .get ('key' , 'None' ): a .get ('value' , 'None' ) for a in list (aliases_tag )}
276+
277+ ########################################
278+ # Attribute getters
279+ ########################################
280+
139281 @property
140282 def is_quantitative (self ):
141283 """ A dependent value, usually a measure of something
142284
143285 e.g. Profit, Gross Sales """
144- return self ._type == 'quantitative'
286+ return self .type == 'quantitative'
145287
146288 @property
147289 def is_ordinal (self ):
148290 """ Is this field a categorical field that has a specific order
149291
150292 e.g. How do you feel? 1 - awful, 2 - ok, 3 - fantastic """
151- return self ._type == 'ordinal'
293+ return self .type == 'ordinal'
152294
153295 @property
154296 def is_nominal (self ):
155297 """ Is this field a categorical field that does not have a specific order
156298
157299 e.g. What color is your hair? """
158- return self ._type == 'nominal'
300+ return self .type == 'nominal'
159301
160302 @property
161303 def calculation (self ):
162304 """ If this field is a calculated field, this will be the formula """
163305 return self ._calculation
164306
307+ @calculation .setter
308+ def calculation (self , new_calculation ):
309+ """ Set the calculation of a calculated field.
310+
311+ Args:
312+ new_calculation: The new calculation/formula of the field. String.
313+ """
314+ if self .calculation is None :
315+ calculation = ET .Element ('calculation' )
316+ calculation .set ('class' , 'tableau' )
317+ calculation .set ('formula' , new_calculation )
318+ # Append the elements to the respective structure
319+ self ._xml .append (calculation )
320+
321+ else :
322+ self ._xml .find ('calculation' ).set ('formula' , new_calculation )
323+
324+ self ._calculation = new_calculation
325+
165326 @property
166327 def default_aggregation (self ):
167328 """ The default type of aggregation on the field (e.g Sum, Avg)"""
0 commit comments