<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Documents and spreadsheets on errno</title><link>https://errno.jofrey.eu/categories/documents-and-spreadsheets/</link><description>Recent content in Documents and spreadsheets on errno</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 14 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://errno.jofrey.eu/categories/documents-and-spreadsheets/index.xml" rel="self" type="application/rss+xml"/><item><title>LibreOffice converts a PDF form to .docx and python-docx sees no paragraphs at all</title><link>https://errno.jofrey.eu/posts/libreoffice-pdf-to-docx-produces-shapes-not-paragraphs/</link><pubDate>Mon, 14 Sep 2026 00:00:00 +0000</pubDate><guid>https://errno.jofrey.eu/posts/libreoffice-pdf-to-docx-produces-shapes-not-paragraphs/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;&#10;&lt;p&gt;An official form existed only as a PDF. It had to be filled in programmatically — same layout, different&#10;values each time — and delivered as a document. The obvious plan was to convert the PDF once, keep the&#10;result as a template, and write values into it with python-docx.&lt;/p&gt;&#10;&lt;p&gt;The conversion works. LibreOffice has an import filter for PDF, it runs headless, and it exits cleanly:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;soffice --headless --infilter&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;writer_pdf_import&amp;#34;&lt;/span&gt; --convert-to docx form.pdf&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The resulting &lt;code&gt;form.docx&lt;/code&gt; opens in a word processor and looks right. Field labels, boxes, the signature&#10;block at the bottom — all where they belong. Then python-docx opens the same file and finds nothing:&lt;/p&gt;</description></item><item><title>openpyxl: copy_worksheet drops the page setup and insert_rows leaves merged cells behind</title><link>https://errno.jofrey.eu/posts/openpyxl-copy-worksheet-and-insert-rows-lose-layout/</link><pubDate>Mon, 14 Sep 2026 00:00:00 +0000</pubDate><guid>https://errno.jofrey.eu/posts/openpyxl-copy-worksheet-and-insert-rows-lose-layout/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;&#10;&lt;p&gt;The job was the usual bureaucratic one: take an official &lt;code&gt;.xlsx&lt;/code&gt; form, duplicate one sheet per item, and&#10;fill a table on each copy by inserting as many rows as the item needed. The script ran clean. No traceback,&#10;no warning, no stderr at all. Every value landed in the cell I expected, and when I opened the result the&#10;grid looked exactly like the template.&lt;/p&gt;&#10;&lt;p&gt;Then I printed it, and two things were wrong at once:&lt;/p&gt;</description></item><item><title>openpyxl: the second save() of a workbook with images raises I/O operation on closed file</title><link>https://errno.jofrey.eu/posts/openpyxl-second-save-io-operation-on-closed-file/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://errno.jofrey.eu/posts/openpyxl-second-save-io-operation-on-closed-file/</guid><description>&lt;h2 id="symptom"&gt;Symptom&lt;/h2&gt;&#10;&lt;p&gt;A municipal grant application had to be filled into the authority&amp;rsquo;s own &lt;code&gt;.xlsx&lt;/code&gt; form — the kind with a&#10;letterhead image in the first rows. Filling it took several passes: first the identification block, then&#10;the budget table. Same &lt;code&gt;Workbook&lt;/code&gt; object, one &lt;code&gt;save()&lt;/code&gt; after each pass. The second one died:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Traceback (most recent call last):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;fill.py&amp;#34;, line 23, in &amp;lt;module&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wb.save(&amp;#34;out.xlsx&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/workbook/workbook.py&amp;#34;, line 386, in save&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; save_workbook(self, filename)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/writer/excel.py&amp;#34;, line 294, in save_workbook&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; writer.save()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/writer/excel.py&amp;#34;, line 275, in save&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self.write_data()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/writer/excel.py&amp;#34;, line 79, in write_data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self._write_images()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/writer/excel.py&amp;#34;, line 116, in _write_images&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; self._archive.writestr(img.path[1:], img._data())&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ^^^^^^^^^^^&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/drawing/image.py&amp;#34;, line 48, in _data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; img = _import_image(self.ref)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../openpyxl/drawing/image.py&amp;#34;, line 16, in _import_image&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; img = PILImage.open(img)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File &amp;#34;.../PIL/Image.py&amp;#34;, line 3518, in open&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fp.seek(0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ValueError: I/O operation on closed file.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The second, nastier symptom appears when you try to read the result back:&lt;/p&gt;</description></item><item><title>Word tracked changes in a .docx: python-docx cannot do it, raw XML can</title><link>https://errno.jofrey.eu/posts/docx-tracked-changes-without-python-docx/</link><pubDate>Tue, 28 Jul 2026 00:00:00 +0000</pubDate><guid>https://errno.jofrey.eu/posts/docx-tracked-changes-without-python-docx/</guid><description>&lt;h2 id="the-constraint"&gt;The constraint&lt;/h2&gt;&#10;&lt;p&gt;A release-management document had to come back with &lt;strong&gt;native Word tracked changes&lt;/strong&gt; — the reviewer wanted&#10;to accept or reject each edit in Word, not read a changelog. The obvious tool does not do it:&lt;/p&gt;&#10;&lt;p&gt;python-docx exposes runs, paragraphs and styles, but &lt;strong&gt;no API for revisions&lt;/strong&gt;. There is no&#10;&lt;code&gt;run.insert_tracked()&lt;/code&gt;, and writing the text through python-docx produces a document where your edits are&#10;indistinguishable from the original. If tracked changes are a requirement, you are editing OOXML directly.&lt;/p&gt;</description></item></channel></rss>