scanny / python-pptx

Create Open XML PowerPoint documents in Python
MIT License
2.44k stars 528 forks source link

Example `replace_data` no longer works #745

Closed smcpherson closed 3 years ago

smcpherson commented 3 years ago

I'm seeing the following error when using replace_data() using the latest version (0.6.21):

*** ValueError: `.target_part` property on _Relationship is undefined when target-mode is external

This appears to be reproducable by running the example code from https://python-pptx.readthedocs.io/en/latest/dev/analysis/cht-chart-data.html:

>>> chart_data = ChartData()
>>> chart_data.categories = 'Foobar', 'Barbaz', 'Bazfoo'
>>> chart_data.add_series('New Series 1', (5.6, 6.7, 7.8))
>>> chart_data.add_series('New Series 2', (2.3, 3.4, 4.5))
>>> chart_data.add_series('New Series 3', (8.9, 9.1, 1.2))
>>> chart.replace_data(chart_data)

*** ValueError: `.target_part` property on _Relationship is undefined when target-mode is external

Please advise. Thank you!

scanny commented 3 years ago

What's the full stack trace?

smcpherson commented 3 years ago

@scanny

  File "foo.py", line 406, in merge_chart
    chart.replace_data(chart_data)
  File "/foo/.venv/lib/python3.7/site-packages/pptx/chart/chart.py", line 171, in replace_data
    self._workbook.update_from_xlsx_blob(chart_data.xlsx_blob)
  File "/foo/.venv/lib/python3.7/site-packages/pptx/parts/chart.py", line 63, in update_from_xlsx_blob
    xlsx_part = self.xlsx_part
  File "/foo/.venv/lib/python3.7/site-packages/pptx/parts/chart.py", line 80, in xlsx_part
    else self._chart_part.related_part(xlsx_part_rId)
  File "/foo/.venv/lib/python3.7/site-packages/pptx/opc/package.py", line 46, in related_part
    return self._rels[rId].target_part
  File "/foo/.venv/lib/python3.7/site-packages/pptx/util.py", line 215, in __get__
    value = self._fget(obj)
  File "/foo/.venv/lib/python3.7/site-packages/pptx/opc/package.py", line 705, in target_part
    "`.target_part` property on _Relationship is undefined when "
ValueError: `.target_part` property on _Relationship is undefined when target-mode is external

thank you!

scanny commented 3 years ago

Hmm, very interesting. I'll see if I can reproduce.

The one place I would expect this error is if the Excel worksheet behind the chart was linked rather than embedded. I know that is theoretically possible although I'm not sure how you would do it from the UI.

Where does chart come from in your example? All I'm seeing is from chart_data = ChartData() onward.

smcpherson commented 3 years ago

Thanks for your help thus far! In my case the chart object comes from shape.chart as follows:

# Find shape by ID (only works with non-placeholder shapes.)
def shapeById(slide, id):
  for shape in slide.shapes:
    if shape.shape_id == id:
      return shape

shape = shapeById(slide, element['id'])
if shape is not None:
  if shape.has_chart:
    merge_chart(shape.chart, element)

Re:

The one place I would expect this error is if the Excel worksheet behind the chart was linked rather than embedded.

Any suggestions on how to work around this if thats the case?

scanny commented 3 years ago

Yeah, so if the chart data Excel file is linked I'm afraid you're stuck. python-pptx has never supported that case. The only way to work around that I can think of is to embed the Excel file rather than link it, or at least enough of the Excel file to provide the chart data.

The other thing you could do I guess is to update the Excel file instead and then "refresh links" when loading the PPTX file.

There is kind of a lot happening when a chart-data Excel is linked. The "true" data is held in the Excel file, but a copy of it is cached (on a point-by-point basis) in the PowerPoint chart part/object such that PowerPoint doesn't need to load the Excel file to display the chart. When you do "update links", this cached version is what is getting updated.

So this would be a significant amount of development work and a non-trivial complication of the API to do things like create a chart from a linked source, update the link, update the source (involving the security weirdness of updating another file on the client's filesystem), etc.

scanny commented 3 years ago

@smcpherson can you confirm that this exception is only raised for a linked (non-embedded) Excel chart-data file?

I'll close if that's the case since that would be the expected behavior.

smcpherson commented 3 years ago

@scanny it appears the excel chart-data file is in fact linked (and a broken link at that). Looks like thats always been the case in the .pptx template I'm using. It never mattered because I replace the chart data in the template with data I want merged with the chart.

The interesting thing is this had been working in previous versions - only when I updated from 0.6.18 to 0.6.21 did this start erroring out.

In any event I can probably update my template to not include linked chart-data and that should likely resolve. Thanks!

scanny commented 3 years ago

Hmm, yes, in the last release I gave the load/save package module a good going over and I expect this is a consequence of that "tightening up".

Theoretically the code could be elaborated to first check whether the chart-data Excel was linked and if so drop that link and then write the embedded Excel, which is effectively what I expect it did in prior versions. It's kind of an edge case though. Your idea to update your template sounds like the right idea to me :)