jdum / odfdo

python library for OpenDocument format (ODF)
Apache License 2.0
55 stars 12 forks source link

Manipulating Chart Data with odfdo #40

Closed micah-quinn closed 5 months ago

micah-quinn commented 5 months ago

I'm attempting to modify the underlying data in a chart within an existing text document. The chart is placed inside a table for formatting. In the main content.xml for the document, I can see the following:

 <table:table table:name="Table1" table:style-name="Table1">
                <table:table-column table:style-name="Table1.A" table:number-columns-repeated="3" />
                <table:table-row>
                    <table:table-cell table:style-name="Table1.A1" office:value-type="string">
                        <text:p text:style-name="Table_20_Contents">
                            <draw:frame draw:style-name="fr3" draw:name="Performance_20_Chart"
                                text:anchor-type="paragraph" svg:x="0.0598in" svg:y="0.1402in"
                                svg:width="2.1283in" svg:height="1.6075in" draw:z-index="1">
                                <draw:object xlink:href="./Object 1" xlink:type="simple"
                                    xlink:show="embed" xlink:actuate="onLoad" />
                                <draw:image xlink:href="./ObjectReplacements/Object 1"
                                    xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" />
                                <svg:desc>chart</svg:desc>
                            </draw:frame>
                        </text:p>
                    </table:table-cell>

And within the content.xml file (./Object 1/content.xml) for the chart I see a chart node and table node that describe the chart and it's data.

  <office:body>
        <office:chart>
            <chart:chart svg:width="5.406cm" svg:height="4.083cm" xlink:href="." xlink:type="simple"
                chart:class="chart:ring" chart:style-name="ch1">
                <chart:title svg:x="1.35cm" svg:y="0.217cm" chart:style-name="ch2">
                    <text:p>Performance</text:p>
                </chart:title>
                <chart:plot-area chart:style-name="ch3" chart:data-source-has-labels="both"
                    svg:x="0.108cm" svg:y="1.077cm" svg:width="5.19cm" svg:height="2.925cm">
                    <chart:coordinate-region svg:x="1.241cm" svg:y="1.078cm" svg:width="2.924cm"
                        svg:height="2.924cm" />
                    <chart:axis chart:dimension="x" chart:name="primary-x" chart:style-name="ch4"
                        chartooo:axis-type="auto">
                        <chartooo:date-scale />
                        <chart:categories table:cell-range-address="local-table.$A$2:.$A$3" />
                    </chart:axis>
                    <chart:axis chart:dimension="y" chart:name="primary-y" chart:style-name="ch5">
                        <chart:grid chart:style-name="ch6" chart:class="major" />
                    </chart:axis>
                    <chart:series chart:style-name="ch7"
                        chart:values-cell-range-address="local-table.$B$2:.$B$3"
                        chart:label-cell-address="local-table.$B$1" chart:class="chart:circle">
                        <chart:data-point chart:style-name="ch8" />
                        <chart:data-point chart:style-name="ch9" />
                    </chart:series>
                    <chart:wall chart:style-name="ch10" />
                    <chart:floor chart:style-name="ch11" />
                </chart:plot-area>
                <table:table table:name="local-table">
                    <table:table-header-columns>
                        <table:table-column />
                    </table:table-header-columns>
                    <table:table-columns>
                        <table:table-column />
                    </table:table-columns>
                    <table:table-header-rows>
                        <table:table-row>
                            <table:table-cell office:value="">
                                <text:p />
                            </table:table-cell>
                            <table:table-cell office:value-type="string" office:value="Column1">
                                <text:p>Column 1</text:p>
                            </table:table-cell>
                        </table:table-row>
                    </table:table-header-rows>
                    <table:table-rows>
                        <table:table-row>
                            <table:table-cell office:value-type="string" office:value="Space">
                                <text:p>Space</text:p>
                            </table:table-cell>
                            <table:table-cell office:value-type="float" office:value="16">
                                <text:p>10</text:p>
                            </table:table-cell>
                        </table:table-row>
                        <table:table-row>
                            <table:table-cell office:value-type="string" office:value="Score">
                                <text:p>Score</text:p>
                            </table:table-cell>
                            <table:table-cell office:value-type="float" office:value="84">
                                <text:p>90</text:p>
                            </table:table-cell>
                        </table:table-row>
                    </table:table-rows>
                </table:table>
            </chart:chart>
        </office:chart>

However, when I attempt to get all tables using "doc.body.get_tables()", the table for the chart is not returned. How does one manipulate the data of a chart within a text document?

LibreOffice 7.3.7.2 odfdo 3.7.7

jdum commented 5 months ago

Hi, at the moment there is no simple way to manipulate the data of a chart. I just made an attempt by using odfdo internals, but table is seen empty? (should not of course), I need to investigate.

from odfdo import *

doc = Document("my_document.odt")
# list the parts if needed
d.get_parts()
# -> ['mimetype', 'ObjectReplacements/Object 1', 'Object 1/meta.xml', 'Object 1/styles.xml', 'Object 1/content.xml', ...

# this should have done the trick : 
obj = XmlPart('Object 1/content.xml', doc.container)
body = obj.root.document_body
table = body.get_table(0)

# however strange bug for now:
table.serialize()
# -> some apparently valid xml content, but :
table.size
# -> (0, 0)
table.is_empty()
# -> True
micah-quinn commented 5 months ago

Hey Jerome. Thanks for the quick response. I've spent an hour or so investigating this and I'm not sure what's going on. I think it has something to do with the cache map for Table. " _compute_table_cache" seems to count on having a repeated element (cols or rows), but charts from LibreOffice do not have either.

Let me know if you need me to test anything. I appreciate your help on this.

micah-quinn commented 5 months ago

This is a crude work-around. More complex graphs I'm sure will be even less intuitive:

        obj = XmlPart('Object 1/content.xml', self.doc.container)
        body = obj.root.document_body

        t0 = body.get_table(0)

        table_rows = t0.children[3]

        row0 = table_rows.children[0]
        row1 = table_rows.children[1]

        row0.set_value(1, 50)
        row1.set_value(1, 50)

        self.doc.container.set_part('Object 1/content.xml',obj.serialize())
jdum commented 5 months ago

The last v3.7.8 version fix the table problem (and improve somehow the XmlPart management). See recipe "change_values_of_a_chart_inside_a_document.py"