diff --git a/pptx/chart/chart.py b/pptx/chart/chart.py
index 9070dd316..3094e615d 100644
--- a/pptx/chart/chart.py
+++ b/pptx/chart/chart.py
@@ -14,6 +14,7 @@
from .plot import PlotFactory, PlotTypeInspector
from .series import SeriesCollection
from ..util import lazyproperty
+from .data import ChartDataMoreDetails
class Chart(object):
@@ -147,6 +148,28 @@ def _workbook(self):
"""
return self._chart_part.chart_workbook
+ def get_data_more_details(self):
+ """
+ return |ChartDataMoreDetails| object contains categories and series
+ in the xml of this chart
+ """
+ chart_data = ChartDataMoreDetails()
+ if len(self.series) > 0:
+ categories = self.series[0].categories_tuples
+ else:
+ categories = None
+ if not categories is None:
+ chart_data.categories = categories
+ chart_data.categories_len = self.series[0].categories_len
+ for item_series in self.series:
+ chart_data.add_series(
+ item_series.name,
+ item_series.values_tuple,
+ values_len=item_series.values_len,
+ format_code=item_series.format_code,
+ )
+ return chart_data
+
class Legend(object):
"""
@@ -277,6 +300,7 @@ def _adjust_ser_count(cls, chartSpace, new_ser_count):
increasing order of the c:ser/c:idx value, starting with 0 and with
any gaps in numbering collapsed.
"""
+ chartSpace.reindex_sers()
ser_count_diff = new_ser_count - len(chartSpace.sers)
if ser_count_diff > 0:
cls._add_cloned_sers(chartSpace, ser_count_diff)
@@ -294,7 +318,8 @@ def _rewrite_ser_data(cls, ser, series_data):
ser._remove_cat()
ser._remove_val()
ser._insert_tx(series_data.tx)
- ser._insert_cat(series_data.cat)
+ if not series_data.cat is None:
+ ser._insert_cat(series_data.cat)
ser._insert_val(series_data.val)
@classmethod
diff --git a/pptx/chart/data.py b/pptx/chart/data.py
index 95cf3e4cd..f2f52a3ee 100644
--- a/pptx/chart/data.py
+++ b/pptx/chart/data.py
@@ -6,10 +6,14 @@
from __future__ import absolute_import, print_function, unicode_literals
+import warnings
+
from ..oxml import parse_xml
from ..oxml.ns import nsdecls
from .xlsx import WorkbookWriter
from .xmlwriter import ChartXmlWriter
+from xlsxwriter.utility import xl_rowcol_to_cell, xl_col_to_name
+from xml.sax.saxutils import escape
class ChartData(object):
@@ -144,7 +148,7 @@ def tx(self):
series name.
"""
xml = self._tx_tmpl.format(
- wksht_ref=self._series_name_ref, series_name=self.name,
+ wksht_ref=self._series_name_ref, series_name=escape(self.name),
nsdecls=' %s' % nsdecls('c')
)
return parse_xml(xml)
@@ -156,7 +160,7 @@ def tx_xml(self):
element contains the series name.
"""
return self._tx_tmpl.format(
- wksht_ref=self._series_name_ref, series_name=self.name,
+ wksht_ref=self._series_name_ref, series_name=escape(self.name),
nsdecls=''
)
@@ -209,7 +213,8 @@ def _cat_pt_xml(self):
' \n'
' %s\n'
' \n'
- ) % (idx, name)
+ ) % (idx, (escape(name) if
+ isinstance(name,(str, unicode)) else name))
return xml
@property
@@ -308,3 +313,352 @@ def _values_ref(self):
return "Sheet1!$%s$2:$%s$%d" % (
self._col_letter, self._col_letter, len(self._values)+1
)
+
+
+class ChartDataMoreDetails(ChartData):
+ """
+ Subclass of ChartData, support categories and vals with more details:
+ categories with multiple levels, categories and vals with blanks.
+
+ See also :class: `ChartData`.
+ """
+ def __init__(self):
+ super(ChartDataMoreDetails, self).__init__()
+ self._categories_len = None
+ self._values_len = None
+
+ @property
+ def categories_len(self):
+ """
+ Read-write. The length of categories. Assigned value
+ will be applied to all sers
+ """
+ return self._categories_len
+
+ @categories_len.setter
+ def categories_len(self, value):
+ self._categories_len = value
+ #make sure all sers have this categories_len
+ for series in self._series_lst:
+ series.categories_len = value
+
+ @property
+ def values_len(self):
+ """
+ Read-write. The length of values.Assigned value
+ will be applied to all sers
+ """
+ return self._values_len
+
+ @values_len.setter
+ def values_len(self, value):
+ self._values_len = value
+ if self._values_len < self._categories_len:
+ warnings.warn(
+ '''Length of values is less than that of categories.
+Over bound categories will not be displayed.''')
+ #make sure all sers have this values_len
+ for series in self._series_lst:
+ series.values_len = value
+
+
+ def add_series(self, name, values, values_len=None, format_code=None):
+ """
+ Add a series to this data set entitled *name* and the data points
+ specified by *values*, an iterable of numeric values.
+ """
+ series_idx = len(self._series_lst)
+ series = _SeriesDataMoreDetails(
+ series_idx, name, values,
+ self.categories,
+ values_len = values_len or self._values_len,
+ categories_len=self._categories_len,
+ format_code=format_code,
+ )
+ self._series_lst.append(series)
+
+
+class _SeriesDataMoreDetails(_SeriesData):
+ """
+ Subclass of _SeriesData, support categories and vals with more details:
+
+ * categories with multiple levels
+ * categories and vals with blanks
+ * column letters exceeding 'Z'
+ * formatCode
+
+ Arguments : values & categories must be 2D sequence of (idx, value)
+
+ See also : :class: `_SeriesData`.
+ """
+ def __init__(self, series_idx, name, values, categories,
+ values_len=None, categories_len=None, format_code=None):
+ super(_SeriesDataMoreDetails, self).__init__(series_idx, name,
+ values, categories)
+ self._values_len = values_len or max(i[0] for i in values) + 1
+ self._auto_values_len = True if values_len else False
+ if categories_len is None and (not self._categories is None) and (
+ len(self._categories) != 0):
+ self._categories_len = max(max(j[0] for j in i)
+ for i in self._categories) + 1
+ else:
+ self._categories_len = categories_len
+ if self._values_len != self._categories_len:
+ warnings.warn('''Categories and Values have different lengths.
+ Will break data range adjustment by dragging in MS PowerPoint.''')
+ self._format_code = format_code or 'General'
+
+ @property
+ def categories_len(self):
+ """
+ Read-write. The length of categories.
+ """
+ return self._categories_len
+
+ @categories_len.setter
+ def categories_len(self, value):
+ self._categories_len = value
+
+ @property
+ def values_len(self):
+ """
+ Read-write. The length of values.
+ """
+ return self._values_len
+
+ @values_len.setter
+ def values_len(self, value):
+ self._values_len = value
+
+ @property
+ def format_code(self):
+ """
+ format code string in ```` element
+ """
+ return self._format_code
+
+ @property
+ def is_cat_multilvl(self):
+ """
+ whether ```` element has multiple levels
+ """
+ if len(self._categories) > 1:
+ return True
+ else:
+ return False
+
+ @property
+ def prefix(self):
+ """
+ prefix for ```` and ```` element
+ """
+ if self.is_cat_multilvl:
+ return 'multiLvlStr'
+ else:
+ return 'str'
+
+ @property
+ def cat(self):
+ """
+ The ```` element XML for this series, as an oxml element.
+ """
+ if self._categories_len > 0:
+ xml = self._cat_tmpl.format(
+ prefix=self.prefix,
+ wksht_ref=self._categories_ref, cat_count=self._categories_len,
+ cat_pt_xml=self._cat_pt_xml, nsdecls=' %s' % nsdecls('c')
+ )
+ return parse_xml(xml)
+ else:
+ return None
+
+
+ @property
+ def cat_xml(self):
+ """
+ The unicode XML snippet for the ```` element for this series,
+ containing the category labels and spreadsheet reference.
+ """
+ if self._categories_len > 0:
+ return self._cat_tmpl.format(
+ prefix=self.prefix,
+ wksht_ref=self._categories_ref, cat_count=self._categories_len,
+ cat_pt_xml=self._cat_pt_xml, nsdecls=''
+ )
+ else:
+ return ''
+
+ @property
+ def val(self):
+ """
+ The ```` XML for this series, as an oxml element.
+ """
+ xml = self._val_tmpl.format(
+ wksht_ref=self._values_ref, val_count=self._values_len,
+ format_code=self._format_code,
+ val_pt_xml=self._val_pt_xml, nsdecls=' %s' % nsdecls('c')
+ )
+ return parse_xml(xml)
+
+ @property
+ def val_xml(self):
+ """
+ Return the unicode XML snippet for the ```` element describing
+ this series.
+ """
+ return self._val_tmpl.format(
+ wksht_ref=self._values_ref, val_count=self._values_len,
+ format_code=self._format_code,
+ val_pt_xml=self._val_pt_xml, nsdecls=''
+ )
+
+ @property
+ def values(self):
+ """
+ The values in this series as a tuple of a sequence of float.
+ """
+ return self._values
+
+ @property
+ def _categories_ref(self):
+ """
+ The Excel worksheet reference to the categories for this series.
+ """
+ end_col_number = len(self._categories) - 1
+ end_row_number = self._categories_len
+ return "Sheet1!$A$2:%s" % xl_rowcol_to_cell(
+ end_row_number, end_col_number,
+ row_abs=True, col_abs=True)
+
+ @property
+ def _cat_pt_xml(self):
+ """
+ The unicode XML snippet for the ```` elements containing the
+ category names for this series.
+ """
+ xml = ''
+ if self.is_cat_multilvl:
+ lvl_start_tag = ' \n'
+ lvl_end_tag = ' \n'
+ pt_indent_spaces = ' '
+ else:
+ lvl_start_tag = ''
+ lvl_end_tag = ''
+ pt_indent_spaces = ''
+ pt_xml = pt_indent_spaces.join(
+ ('',
+ ' \n',
+ ' %s\n',
+ ' \n',))
+ #ref lvl is in reverse sequence in xml
+ loop_range = range(len(self._categories))
+ loop_range.reverse()
+ for ilvl in loop_range:
+ lvl = self._categories[ilvl]
+ xml += lvl_start_tag
+ for idx, name in lvl:
+ #ignore idx out bound
+ if idx < self.categories_len:
+ xml += pt_xml % (idx, (escape(name) if
+ isinstance(name,(str, unicode)) else name))
+ xml += lvl_end_tag
+ return xml
+
+ @property
+ def _cat_tmpl(self):
+ """
+ The template for the ```` element for this series, containing
+ the category labels and spreadsheet reference.
+ """
+ return (
+ ' \n'
+ ' \n'
+ ' {wksht_ref}\n'
+ ' \n'
+ ' \n'
+ '{cat_pt_xml}'
+ ' \n'
+ ' \n'
+ ' \n'
+ )
+
+ @property
+ def _col_letter(self):
+ """
+ The letter of the Excel worksheet column in which the data for this
+ series appears.
+ """
+ return xl_col_to_name(max(1, len(self._categories)) + self._series_idx)
+
+ @property
+ def _val_pt_xml(self):
+ """
+ The unicode XML snippet containing the ```` elements for this
+ series.
+ """
+ xml = ''
+ for idx, value in self._values:
+ if idx < self.values_len:
+ xml += (
+ ' \n'
+ ' %s\n'
+ ' \n'
+ ) % (idx, value)
+ return xml
+
+ @property
+ def _val_tmpl(self):
+ """
+ The template for the ```` element for this series, containing
+ the series values and their spreadsheet range reference.
+ """
+ return (
+ ' \n'
+ ' \n'
+ ' {wksht_ref}\n'
+ ' \n'
+ ' {format_code}\n'
+ ' \n'
+ '{val_pt_xml}'
+ ' \n'
+ ' \n'
+ ' \n'
+ )
+
+ @property
+ def _values_ref(self):
+ """
+ The Excel worksheet reference to the values for this series (not
+ including the series name).
+ """
+ return "Sheet1!${col_letter}$2:${col_letter}${end_row_number}".format(
+ col_letter = self._col_letter,
+ end_row_number = self._values_len + 1,
+ )
+
+ @property
+ def name(self):
+ """
+ The name of this series.
+ """
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ #name setter
+ self._name = value
+
+ @property
+ def values(self):
+ """
+ The values in this series as a sequence of float.
+ """
+ return self._values
+
+ @values.setter
+ def values(self, _values):
+ #values setter
+ self._values = _values
+ #update values len if auto
+ if self._auto_values_len:
+ self._values_len = max(i[0] for i in _values) + 1
diff --git a/pptx/chart/plot.py b/pptx/chart/plot.py
index 48906d26a..55a602e6d 100644
--- a/pptx/chart/plot.py
+++ b/pptx/chart/plot.py
@@ -37,6 +37,21 @@ def categories(self):
category_pt_elms = xChart.cat_pts
return tuple(pt.v.text for pt in category_pt_elms)
+ @property
+ def categories_tuples(self):
+ """
+ Tuples containing the category indexes and strings for this plot.
+ If len > 1, the plot has multilvl categories.
+ """
+ return self._element.iter_sers().next().cat.tuples_pts
+
+ @property
+ def categories_len(self):
+ """
+ Return length of categories, equal to ticks in categories axis
+ """
+ return self._element.iter_sers().next().cat.val_ptCount
+
@property
def chart(self):
"""
diff --git a/pptx/chart/series.py b/pptx/chart/series.py
index 6f5f7f71c..1ce8ae4cd 100644
--- a/pptx/chart/series.py
+++ b/pptx/chart/series.py
@@ -51,6 +51,44 @@ def values(self):
value_pt_elms = ser.val_pts
return tuple(pt.value for pt in value_pt_elms)
+ @property
+ def categories_tuples(self):
+ """
+ Tuples containing the category indexes and strings for this series.
+ If len > 1, the series has multilvl categories.
+ """
+ if not self._element.cat is None:
+ return self._element.cat.tuples_pts
+
+ @property
+ def categories_len(self):
+ """
+ Return length of categories, equal to ticks in categories axis
+ """
+ if not self._element.cat is None:
+ return self._element.cat.val_ptCount
+
+ @property
+ def values_tuple(self):
+ """
+ Tuple containing the value indexes and value for this series.
+ """
+ return self._element.val.tuples_pts
+
+ @property
+ def values_len(self):
+ """
+ Return length of values, equal to ticks in categories axis
+ """
+ return self._element.val.val_ptCount
+
+ @property
+ def format_code(self):
+ """
+ Return formatCode
+ """
+ return self._element.val.ref.cache.text_fomatCode
+
class BarSeries(_BaseSeries):
"""
diff --git a/pptx/chart/xlsx.py b/pptx/chart/xlsx.py
index 681aeb4a7..5365e501d 100644
--- a/pptx/chart/xlsx.py
+++ b/pptx/chart/xlsx.py
@@ -46,11 +46,23 @@ def _populate_worksheet(cls, worksheet, categories, series):
"""
Write *categories* and *series* to *worksheet* in the standard
layout, categories in first column starting in second row, and series
- as columns starting in second column, series title in first cell.
- Make the whole range an Excel List.
+ as columns starting in column next to categories, series title in first
+ cell. Make the whole range an Excel List.
"""
- worksheet.write_column(1, 0, categories)
- for series in series:
- series_col = series.index + 1
- worksheet.write(0, series_col, series.name)
- worksheet.write_column(1, series_col, series.values)
+ if len(categories) != 0 and isinstance(categories[0], (list, tuple)):
+ for ilvl in xrange(len(categories)):
+ for idx, token in categories[ilvl]:
+ worksheet.write(1+idx, ilvl, token)
+ value_start_col = len(categories)
+ else:
+ worksheet.write_column(1, 0, categories)
+ value_start_col = 1
+ for item_series in series:
+ series_col = item_series.index + value_start_col
+ worksheet.write(0, series_col, item_series.name)
+ if len(item_series.values) != 0 and isinstance(
+ item_series.values[0], tuple):
+ for idx, token in item_series.values:
+ worksheet.write(1+idx, series_col, token)
+ else:
+ worksheet.write_column(1, series_col, item_series.values)
diff --git a/pptx/oxml/__init__.py b/pptx/oxml/__init__.py
index 6ec67cfdf..a60da615e 100644
--- a/pptx/oxml/__init__.py
+++ b/pptx/oxml/__init__.py
@@ -84,9 +84,24 @@ def register_element_cls(nsptagname, cls):
register_element_cls('c:pieChart', CT_PieChart)
-from .chart.series import CT_SeriesComposite, CT_StrVal_NumVal_Composite
+from .chart.series import (
+ CT_SeriesComposite, CT_StrVal_NumVal_Composite,
+ CT_Val, CT_Cat,
+ CT_MultiLvlStrRef, CT_StrRef, CT_NumRef,
+ CT_MultiLvlStrCache, CT_Lvl, CT_StrCache, CT_NumCache
+)
register_element_cls('c:pt', CT_StrVal_NumVal_Composite)
register_element_cls('c:ser', CT_SeriesComposite)
+register_element_cls('c:val', CT_Val)
+register_element_cls('c:cat', CT_Cat)
+register_element_cls('c:multiLvlStrRef', CT_MultiLvlStrRef)
+register_element_cls('c:strRef', CT_StrRef)
+register_element_cls('c:numRef', CT_NumRef)
+register_element_cls('c:multiLvlStrCache', CT_MultiLvlStrCache)
+register_element_cls('c:lvl', CT_Lvl)
+register_element_cls('c:strCache', CT_StrCache)
+register_element_cls('c:numCache', CT_NumCache)
+
from .chart.shared import (
@@ -108,7 +123,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls('c:varyColors', CT_Boolean)
register_element_cls('c:x', CT_Double)
register_element_cls('c:xMode', CT_LayoutMode)
-
+register_element_cls('c:ptCount', CT_UnsignedInt)
from .dml.color import (
CT_HslColor, CT_Percentage, CT_PresetColor, CT_SchemeColor,
diff --git a/pptx/oxml/chart/chart.py b/pptx/oxml/chart/chart.py
index 2b4ffb5d6..824eb2db8 100644
--- a/pptx/oxml/chart/chart.py
+++ b/pptx/oxml/chart/chart.py
@@ -105,18 +105,29 @@ def last_doc_order_ser(self):
def sers(self):
"""
An immutable sequence of the `c:ser` elements under this chartSpace
- element, sorted in order of their `c:ser/c:idx/@val` value and with
- any gaps in numbering collapsed.
+ element in order of `c:ser/c:idx/@val` (same as Official VBA API :
+ Chart.SeriesCollection(Index))
"""
- def ser_idx(ser):
- return ser.idx.val
+ return sorted(self.xpath('.//c:ser'), key=lambda ser: ser.idx.val)
- sers = sorted(self.xpath('.//c:ser'), key=ser_idx)
- for idx, ser in enumerate(sers):
+ def reindex_sers(self):
+ """
+ Reindex `c:ser` elements in order of their `c:ser/c:idx/@val` value
+ and with any gaps in numbering collapsed. (method for backwards
+ compatible) Beware : Altering `c:ser/c:idx/@val` value frequently
+ change auto style of the series
+ """
+ for idx, ser in enumerate(self.sers):
if ser.idx.val != idx:
ser.idx.val = idx
- ser.order.val = idx
- return sers
+ ser.order.val = idx
+
+ def reset_order_sers(self):
+ """
+ Duplicated `c:ser/c:order/@val` is not allowed in MS PowerPoint, reset
+ order starting from zero"""
+ for order, ser in enumerate(self.sers):
+ ser.order.val = order
@property
def valAx(self):
diff --git a/pptx/oxml/chart/series.py b/pptx/oxml/chart/series.py
index af77b55a9..710dd5152 100644
--- a/pptx/oxml/chart/series.py
+++ b/pptx/oxml/chart/series.py
@@ -8,7 +8,7 @@
from ..simpletypes import XsdUnsignedInt
from ..xmlchemy import (
- BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrOne
+ BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrOne, OneOrMore
)
@@ -85,3 +85,206 @@ def value(self):
The float value of the text in the required ```` child.
"""
return float(self.v.text)
+
+class _Base_Seq(BaseOxmlElement):
+ """
+ base class for sequence element
+ provides similar properties and methods for element with ref and cache
+ """
+ #ref element must be implemented in subclass
+
+ @property
+ def ref(self):
+ """
+ ref element, must be implemented in subclass
+ """
+ raise NotImplementedError(
+ 'property ref must be implemented in subclass')
+
+ @property
+ def text_ref(self):
+ """
+ reference text
+ """
+ return self.ref.text_cf
+
+ @property
+ def val_ptCount(self):
+ """
+ val of ptCount
+ """
+ return self.ref.cache.ptCount.val
+
+ @property
+ def tuples_pts(self):
+ """
+ tuples of pts
+ """
+ return self.ref.cache.tuple_pts
+
+
+class CT_Val(_Base_Seq):
+ """
+ ```` element, contains values
+ """
+ _ref = ZeroOrOne('c:numRef')
+
+ @property
+ def ref(self):
+ """
+ ref element of ```` child
+ """
+ return self._ref
+
+
+class CT_Cat(_Base_Seq):
+ """
+ ```` element, contains categories
+ """
+ _multilvlstrref = ZeroOrOne('c:multiLvlStrRef')
+ _strref = ZeroOrOne('c:strRef')
+ _numref = ZeroOrOne('c:numRef')
+
+ @property
+ def is_multilvl(self):
+ """
+ True if with ``c:multiLvlStrRef>`` child
+ """
+ if self._multilvlstrref is None:
+ return False
+ else:
+ return True
+
+ @property
+ def ref(self):
+ """
+ ref element of ```` or ```` child
+ """
+ if self.is_multilvl:
+ return self._multilvlstrref
+ elif not self._numref is None:
+ return self._numref
+ else:
+ return self._strref
+
+
+ @property
+ def tuples_pts(self):
+ """
+ tuples of pts
+ """
+ if self.is_multilvl:
+ return tuple(
+ (lvl.tuple_pts for lvl in self.ref.cache.lvl_lst)
+ )
+ else:
+ return (self.ref.cache.tuple_pts,)
+
+
+class _Base_Ref(BaseOxmlElement):
+ """
+ base class for ````, ````, and ````
+ element
+ """
+ cf = ZeroOrOne('c:f', successors=('c:multiLvlStrCache', 'c:strCache',
+ 'c:numCache'))
+
+ @property
+ def text_cf(self):
+ """
+ reference text of ```` child.
+ """
+ if not self.cf is None:
+ return self.cf.text
+
+
+class CT_MultiLvlStrRef(_Base_Ref):
+ """
+ ```` element
+ """
+ cache = OneAndOnlyOne('c:multiLvlStrCache')
+
+
+class CT_StrRef(_Base_Ref):
+ """
+ ```` element
+ """
+ cache = OneAndOnlyOne('c:strCache')
+
+class CT_NumRef(_Base_Ref):
+ """
+ ```` element
+ """
+ cache = OneAndOnlyOne('c:numCache')
+
+
+class _Base_Cache(BaseOxmlElement):
+ """
+ base class for ````, ````, and ```` element
+ """
+ formatCode = ZeroOrOne('c:formatCode', successors=('c:ptCount',))
+ ptCount = OneAndOnlyOne('c:ptCount')
+
+
+class CT_MultiLvlStrCache(_Base_Cache):
+ """
+ ```` element
+ """
+ lvl = OneOrMore('c:lvl')
+
+
+class _PtMixin(BaseOxmlElement):
+ """
+ mixin class for ```` children, provides pt_tuples
+ """
+ pt = OneOrMore('c:pt')
+
+ @property
+ def tuple_pts(self):
+ """
+ return pt tuples as : (idx, v.text)
+ """
+ return tuple(
+ ((pt.idx, pt.v.text) for pt in self.pt_lst)
+ )
+
+
+class CT_Lvl(_PtMixin):
+ """
+ ```` element
+ """
+ pass
+
+
+class CT_StrCache(_Base_Cache, _PtMixin):
+ """
+ ```` element
+ """
+ pass
+
+
+class CT_NumCache(_Base_Cache, _PtMixin):
+ """
+ ```` element
+ """
+ @property
+ def text_fomatCode(self):
+ """
+ return text of formatCode
+ """
+ return self.formatCode.text
+
+ @property
+ def tuple_pts(self):
+ """
+ try return pt tuple as : (idx, float(v.text))
+ and return (idx, v.text) if failed
+ """
+ pt_tuple_lst = list()
+ for pt in self.pt_lst:
+ try:
+ value = float(pt.v.text)
+ except ValueError:
+ value = pt.v.text
+ pt_tuple_lst.append((pt.idx, value))
+ return tuple(pt_tuple_lst)