Python-docx: add_picture corruptes file in some case

Created on 12 Jan 2018  路  6Comments  路  Source: python-openxml/python-docx

Dear @scanny ,

First of all, thanks to you and all contributors for python-docx!

I have found a problem when I wanted to add images in an existing file. After the insertion the file resulted corrupted when opening it with MS Word.

I did some debugging I have found out that it was a clash between the id of the wp:docPr of the newly inserted image and the wp:docPr of a shape in the header.

I think that the problem is in next_id(self) of document.py that from my understanding looks only for the image ids in document.xml and not in header1.xml (where the conflicting wp:docPr is).
I have temporarily fixed it for my case by artificially increasing the id number of the newly inserted pictures by 10000 as I haven't find out as to access the header1.xml elements and I'm very short on time.

Again, thanks for this extremely useful package!

shortlist

Most helpful comment

Sure!
The solution I used at first was some kind of a one shot brute force in the code itself to check my hypothesis. Something like this:

from docx import Document
from docx.oxml.shared import qn

my_document = Document('without_images.docx')
#code inserting some images
doc_element = my_document._part._element
docPrs = doc_element.findall('.//' + qn('wp:docPr'))
for docPr in docPrs:
docPr.set('id',str(int(docPr.get('id'))+100000))

my_document.save('with_images.docx')

And the output file was finally ok without having to remove the images in the header. A quick test with opc-diag seemed to show that when adding images with Word later on, it keeps using the lower unused ids without worrying about these high value ones.
This solution is straightforward and simple and may be all that is needed (it was for me) but is of course thought to be done once only at the end of all the insertions (the pool of ids could be considered messy if that step was to be repeated multiple times).

Now however I'm working with a modified property next_id in the document.py file:

@property
def next_id(self):
    """
    The next available positive integer id value in this document. Gaps
    in id sequence are filled. The id attribute value is unique in the
    document, without regard to the element type it appears on.
    """
    id_str_lst = self._element.xpath('//@id')
    used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit() and int(id_str)>100000]
    for n in range(100001, len(used_ids)+100002):
        if n not in used_ids:
            return n

The idea is that all python-docx generated ids from this property live in have their own space at above 100000. It seems to work but unfortunately I haven't had the time yet to test it thoroughly in different scenarios.

All 6 comments

Thanks @Tores76. This is an interesting and useful finding. Your hypothesis makes sense to me; Word has access to the header whenever it has the document and I suppose it's reasonable they would place those ids into the same scope of uniqueness.

We don't have programmatic access to the header XML document(s) yet, so we wouldn't be able to search there yet. The artificial increase in ID for new ones we add makes sense to me though. I'll bet that's a pretty robust approach that would work in all cases we're likely to encounter.

I'll leave this open and put it on the short list for things we can fix next time we're in there.

@Tores76 By the way, can you post the code you used to increase the ID on new images you inserted? That could be very helpful to someone who needs it until we can get this into the build.

Sure!
The solution I used at first was some kind of a one shot brute force in the code itself to check my hypothesis. Something like this:

from docx import Document
from docx.oxml.shared import qn

my_document = Document('without_images.docx')
#code inserting some images
doc_element = my_document._part._element
docPrs = doc_element.findall('.//' + qn('wp:docPr'))
for docPr in docPrs:
docPr.set('id',str(int(docPr.get('id'))+100000))

my_document.save('with_images.docx')

And the output file was finally ok without having to remove the images in the header. A quick test with opc-diag seemed to show that when adding images with Word later on, it keeps using the lower unused ids without worrying about these high value ones.
This solution is straightforward and simple and may be all that is needed (it was for me) but is of course thought to be done once only at the end of all the insertions (the pool of ids could be considered messy if that step was to be repeated multiple times).

Now however I'm working with a modified property next_id in the document.py file:

@property
def next_id(self):
    """
    The next available positive integer id value in this document. Gaps
    in id sequence are filled. The id attribute value is unique in the
    document, without regard to the element type it appears on.
    """
    id_str_lst = self._element.xpath('//@id')
    used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit() and int(id_str)>100000]
    for n in range(100001, len(used_ids)+100002):
        if n not in used_ids:
            return n

The idea is that all python-docx generated ids from this property live in have their own space at above 100000. It seems to work but unfortunately I haven't had the time yet to test it thoroughly in different scenarios.

Fixed in release v0.8.7 circa Aug 18 2018.

Get the same error in 0.8.10 release.
I use custom template with images in the footer and header.

word/header1.xml:wp:docPr id="13" 

word/document.xml:wp:docPr id="1" n
word/document.xml:wp:docPr id="2" n
word/document.xml:wp:docPr id="3" n
word/document.xml:wp:docPr id="4" n
word/document.xml:wp:docPr id="5" n
word/document.xml:wp:docPr id="6" n
word/document.xml:wp:docPr id="7" n
word/document.xml:wp:docPr id="8" n
word/document.xml:wp:docPr id="9" n
word/document.xml:wp:docPr id="10" 
word/document.xml:wp:docPr id="11" 
word/document.xml:wp:docPr id="12" 
word/document.xml:wp:docPr id="13" 

Docx is corrupted if I add more than 12 images..

Sure!
The solution I used at first was some kind of a one shot brute force in the code itself to check my hypothesis. Something like this:

from docx import Document
from docx.oxml.shared import qn

my_document = Document('without_images.docx')

code inserting some images

doc_element = my_document._part._element
docPrs = doc_element.findall('.//' + qn('wp:docPr'))
for docPr in docPrs:
docPr.set('id',str(int(docPr.get('id'))+100000))

my_document.save('with_images.docx')

And the output file was finally ok without having to remove the images in the header. A quick test with opc-diag seemed to show that when adding images with Word later on, it keeps using the lower unused ids without worrying about these high value ones.
This solution is straightforward and simple and may be all that is needed (it was for me) but is of course thought to be done once only at the end of all the insertions (the pool of ids could be considered messy if that step was to be repeated multiple times).

Now however I'm working with a modified property next_id in the document.py file:

@property
def next_id(self):
    """
    The next available positive integer id value in this document. Gaps
    in id sequence are filled. The id attribute value is unique in the
    document, without regard to the element type it appears on.
    """
    id_str_lst = self._element.xpath('//@id')
    used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit() and int(id_str)>100000]
    for n in range(100001, len(used_ids)+100002):
        if n not in used_ids:
            return n

The idea is that all python-docx generated ids from this property live in have their own space at above 100000. It seems to work but unfortunately I haven't had the time yet to test it thoroughly in different scenarios.

Thanks! I am dealing with the same problem, but I am not being succesfull using the next_id fuction. Could you post the whole code of inserting imagens in an existing docx file and then using the fuction above?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

madphysicist picture madphysicist  路  3Comments

carlosmtz2106 picture carlosmtz2106  路  7Comments

aekoch picture aekoch  路  7Comments

sskadit picture sskadit  路  4Comments

bendaojiu picture bendaojiu  路  5Comments