|
| 1 | +import functools |
| 2 | + |
| 3 | +_ATTRIBUTES = [ |
| 4 | + 'id', # Name of the field as specified in the file, usually surrounded by [ ] |
| 5 | + 'caption', # Name of the field as displayed in Tableau unless an aliases is defined |
| 6 | + 'datatype', # Type of the field within Tableau (string, integer, etc) |
| 7 | + 'role', # Dimension or Measure |
| 8 | + 'type', # three possible values: quantitative, ordinal, or nominal |
| 9 | + 'alias', # Name of the field as displayed in Tableau if the default name isn't wanted |
| 10 | + 'calculation', # If this field is a calculated field, this will be the formula |
| 11 | +] |
| 12 | + |
| 13 | +_METADATA_ATTRIBUTES = [ |
| 14 | + 'aggregation', # The type of aggregation on the field (e.g Sum, Avg) |
| 15 | +] |
| 16 | + |
| 17 | + |
| 18 | +def _find_metadata_record(record, attrib): |
| 19 | + element = record.find('.//{}'.format(attrib)) |
| 20 | + if element is None: |
| 21 | + return None |
| 22 | + return element.text |
| 23 | + |
| 24 | + |
| 25 | +class Field(object): |
| 26 | + """ Represents a field in a datasource """ |
| 27 | + |
| 28 | + def __init__(self, xmldata): |
| 29 | + for attrib in _ATTRIBUTES: |
| 30 | + self._apply_attribute(xmldata, attrib, lambda x: xmldata.attrib.get(x, None)) |
| 31 | + |
| 32 | + # All metadata attributes begin at None |
| 33 | + for attrib in _METADATA_ATTRIBUTES: |
| 34 | + setattr(self, '_{}'.format(attrib), None) |
| 35 | + |
| 36 | + def apply_metadata(self, metadata_record): |
| 37 | + for attrib in _METADATA_ATTRIBUTES: |
| 38 | + self._apply_attribute(metadata_record, attrib, functools.partial(_find_metadata_record, metadata_record)) |
| 39 | + |
| 40 | + @classmethod |
| 41 | + def from_xml(cls, xmldata): |
| 42 | + return cls(xmldata) |
| 43 | + |
| 44 | + def __getattr__(self, item): |
| 45 | + private_name = '_{}'.format(item) |
| 46 | + if item in _ATTRIBUTES or item in _METADATA_ATTRIBUTES: |
| 47 | + return getattr(self, private_name) |
| 48 | + raise AttributeError(item) |
| 49 | + |
| 50 | + def _apply_attribute(self, xmldata, attrib, default_func): |
| 51 | + if hasattr(self, '_read_{}'.format(attrib)): |
| 52 | + value = getattr(self, '_read_{}'.format(attrib))(xmldata) |
| 53 | + else: |
| 54 | + value = default_func(attrib) |
| 55 | + |
| 56 | + setattr(self, '_{}'.format(attrib), value) |
| 57 | + |
| 58 | + @property |
| 59 | + def name(self): |
| 60 | + """ Provides a nice name for the field which is derived from the alias, caption, or the id. |
| 61 | +
|
| 62 | + The name resolves as either the alias if it's defined, or the caption if alias is not defined, |
| 63 | + and finally the id which is the underlying name if neither of the fields exist. """ |
| 64 | + alias = getattr(self, 'alias', None) |
| 65 | + if alias: |
| 66 | + return alias |
| 67 | + |
| 68 | + caption = getattr(self, 'caption', None) |
| 69 | + if caption: |
| 70 | + return caption |
| 71 | + |
| 72 | + return self.id |
| 73 | + |
| 74 | + ###################################### |
| 75 | + # Special Case handling methods for reading the values from the XML |
| 76 | + ###################################### |
| 77 | + @staticmethod |
| 78 | + def _read_id(xmldata): |
| 79 | + # ID is actually the name of the field, but to provide a nice name, we call this ID |
| 80 | + return xmldata.attrib.get('name', None) |
| 81 | + |
| 82 | + @staticmethod |
| 83 | + def _read_calculation(xmldata): |
| 84 | + # The formula for a calculation is stored in a child element, so we need to pull it out separately. |
| 85 | + calc = xmldata.find('.//calculation') |
| 86 | + if calc is None: |
| 87 | + return None |
| 88 | + |
| 89 | + return calc.attrib.get('formula', None) |
0 commit comments