jsreport / jsreport-docx

jsreport recipe rendering word docx
MIT License
10 stars 3 forks source link

Shapes with "<v:textbox/>" in "{{#if}}/{{/if}}" block are broken if no newline #26

Closed pdecat closed 3 years ago

pdecat commented 3 years ago

Hi, I'm facing an issue with shapes (<w:drawing/>/<a:graphic/>) when used in conjunction with "if" blocks, e.g. :

image

The docx template: shape-in-if.docx

When rendered, the following xmldom errors are produced:

[xmldom warning]        unclosed xml attribute
@#[line:1,col:163]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:184]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:210]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:221]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:422]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:497]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:584]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:1133]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:1143]

The generated output is then rejected by LibreOffice, Word 365 and xmllint:

-:2: namespace error : Namespace prefix a on graphicData is not defined
phicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
                                                                               ^
-:2: namespace error : Namespace prefix a on xfrm is not defined
d/2010/wordprocessingShape"/><wps:wsp/><wps:cNvSpPr/><wps:spPr><a:xfrm flipH="1"
                                                                               ^
-:2: namespace error : Namespace prefix a on off is not defined
ngShape"/><wps:wsp/><wps:cNvSpPr/><wps:spPr><a:xfrm flipH="1"><a:off x="0" y="0"
                                                                               ^
-:2: namespace error : Namespace prefix a on ext is not defined
><wps:spPr><a:xfrm flipH="1"><a:off x="0" y="0"/><a:ext cx="1534320" cy="219600"
                                                                               ^
-:2: namespace error : Namespace prefix a on prstGeom is not defined
0" y="0"/><a:ext cx="1534320" cy="219600"/></a:xfrm><a:prstGeom prst="homePlate"
                                                                               ^
-:2: namespace error : Namespace prefix a on avLst is not defined
><a:ext cx="1534320" cy="219600"/></a:xfrm><a:prstGeom prst="homePlate"><a:avLst
                                                                               ^
-:2: namespace error : Namespace prefix a on gd is not defined
</a:xfrm><a:prstGeom prst="homePlate"><a:avLst><a:gd name="adj" fmla="val 50000"
                                                                               ^
-:2: namespace error : Namespace prefix a on solidFill is not defined
><a:avLst><a:gd name="adj" fmla="val 50000"/></a:avLst></a:prstGeom><a:solidFill
                                                                               ^
-:2: namespace error : Namespace prefix a on srgbClr is not defined
j" fmla="val 50000"/></a:avLst></a:prstGeom><a:solidFill><a:srgbClr val="25b52c"
                                                                               ^
-:2: namespace error : Namespace prefix a on ln is not defined
Lst></a:prstGeom><a:solidFill><a:srgbClr val="25b52c"/></a:solidFill><a:ln w="0"
                                                                               ^
-:2: namespace error : Namespace prefix a on noFill is not defined
stGeom><a:solidFill><a:srgbClr val="25b52c"/></a:solidFill><a:ln w="0"><a:noFill
                                                                               ^
-:2: namespace error : Namespace prefix a on lnRef is not defined
a:solidFill><a:ln w="0"><a:noFill/></a:ln></wps:spPr><wps:style><a:lnRef idx="2"
                                                                               ^
-:2: namespace error : Namespace prefix a on schemeClr is not defined
><a:noFill/></a:ln></wps:spPr><wps:style><a:lnRef idx="2"><a:schemeClr val="dk1"
                                                                               ^
-:2: namespace error : Namespace prefix a on shade is not defined
wps:spPr><wps:style><a:lnRef idx="2"><a:schemeClr val="dk1"><a:shade val="50000"
                                                                               ^
-:2: namespace error : Namespace prefix a on fillRef is not defined
meClr val="dk1"><a:shade val="50000"/></a:schemeClr></a:lnRef><a:fillRef idx="1"
                                                                               ^
-:2: namespace error : Namespace prefix a on schemeClr is not defined
e val="50000"/></a:schemeClr></a:lnRef><a:fillRef idx="1"><a:schemeClr val="dk1"
                                                                               ^
-:2: namespace error : Namespace prefix a on effectRef is not defined
nRef><a:fillRef idx="1"><a:schemeClr val="dk1"/></a:fillRef><a:effectRef idx="0"
                                                                               ^
-:2: namespace error : Namespace prefix a on schemeClr is not defined
><a:schemeClr val="dk1"/></a:fillRef><a:effectRef idx="0"><a:schemeClr val="dk1"
                                                                               ^
-:2: namespace error : Namespace prefix a on fontRef is not defined
a:effectRef idx="0"><a:schemeClr val="dk1"/></a:effectRef><a:fontRef idx="minor"
                                                                               ^
-:2: parser error : Opening and ending tag mismatch: body line 2 and txbxContent
:sz w:val="24"/></w:rPr><w:t>value</w:t></w:r></w:p></w:r></w:p></w:txbxContent>
                                                                               ^
-:2: parser error : Opening and ending tag mismatch: document line 2 and txbx
24"/></w:rPr><w:t>value</w:t></w:r></w:p></w:r></w:p></w:txbxContent></wps:txbx>
                                                                               ^
-:2: parser error : Extra content at the end of the document
24"/></w:rPr><w:t>value</w:t></w:r></w:p></w:r></w:p></w:txbxContent></wps:txbx>
                                                                               ^

I'm trying to implement the following test case to demonstrate the issue but the xmldom parser is too lenient to pick up the XML errors:

  it('shape enclosed in if block', async () => {
    const result = await reporter.render({
      template: {
        engine: 'handlebars',
        recipe: 'docx',
        docx: {
          templateAsset: {
            content: fs.readFileSync(path.join(__dirname, 'shape-in-if.docx'))
          }
        }
      },
      data: {
        key: 'value'
      }
    })

    // Write document for easier debugging
    fs.writeFileSync('out.docx', result.content)

    const files = await decompress()(result.content)

    const doc = new DOMParser().parseFromString(
      files.find(f => f.path === 'word/document.xml').data.toString()
    )

    const generalTextElements = nodeListToArray(doc.getElementsByTagName('w:t'))

    generalTextElements.length.should.be.eql(2)
    should(generalTextElements[0].textContent).be.eql('value')
  })
})

Adding a newline right after {{#if key}} works around the issue, but it is not satisfying in my use case as it adds undesired line breaks in the output:

image

The difference with the broken output is this (ok left, ko right):

image

As you can see, some tags are closed too early in the broken output.

I believe I've narrowed down the issue a bit as commenting one of the two following calls eliminates the xmldom errors and produces correct ouput:

pdecat commented 3 years ago

I found the issue, the document that errors contains a <w:p> tag nested in another <w:p> tag and this is not supported by the removeBlockHelper() function which does simple string searching and stops on the first closing </w:p> tag which is the nested one, not the outer one.

The full document at the post process step when removeBlockHelper() is invoked, pretty printed:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:o="urn:schemas-microsoft-com:office:office"
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    xmlns:v="urn:schemas-microsoft-com:vml"
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:w10="urn:schemas-microsoft-com:office:word"
    xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
    xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
    xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
    xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14">
    <w:body>
        <w:p __block_helper_container__="true">
            <w:pPr>
                <w:pStyle w:val="TableContents"/>
                <w:rPr/>
            </w:pPr>
            <w:r>
                <w:rPr/>
                <w:t __block_helper__="true"></w:t>
            </w:r>
            <w:r>
                <w:rPr/>
                <mc:AlternateContent>
                    <mc:Choice Requires="wps">
                        <w:drawing>
                            <wp:inline distT="0" distB="9525" distL="0" distR="5715" wp14:anchorId="30CF43F6">
                                <wp:extent cx="1534795" cy="220345"/>
                                <wp:effectExtent l="0" t="0" r="5715" b="9525"/>
                                <wp:docPr id="1" name="Shape1_1"/>
                                <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
                                    <a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
                                        <wps:wsp>
                                            <wps:cNvSpPr/>
                                            <wps:spPr>
                                                <a:xfrm flipH="1">
                                                    <a:off x="0" y="0"/>
                                                    <a:ext cx="1534320" cy="219600"/>
                                                </a:xfrm>
                                                <a:prstGeom prst="homePlate">
                                                    <a:avLst>
                                                        <a:gd name="adj" fmla="val 50000"/>
                                                    </a:avLst>
                                                </a:prstGeom>
                                                <a:solidFill>
                                                    <a:srgbClr val="25b52c"/>
                                                </a:solidFill>
                                                <a:ln w="0">
                                                    <a:noFill/>
                                                </a:ln>
                                            </wps:spPr>
                                            <wps:style>
                                                <a:lnRef idx="2">
                                                    <a:schemeClr val="dk1">
                                                        <a:shade val="50000"/>
                                                    </a:schemeClr>
                                                </a:lnRef>
                                                <a:fillRef idx="1">
                                                    <a:schemeClr val="dk1"/>
                                                </a:fillRef>
                                                <a:effectRef idx="0">
                                                    <a:schemeClr val="dk1"/>
                                                </a:effectRef>
                                                <a:fontRef idx="minor"/>
                                            </wps:style>
                                            <wps:txbx>
                                                <w:txbxContent>
                                                    <w:p>
                                                        <w:pPr>
                                                            <w:overflowPunct w:val="false"/>
                                                            <w:rPr/>
                                                        </w:pPr>
                                                        <w:r>
                                                            <w:rPr>
                                                                <w:sz w:val="24"/>
                                                            </w:rPr>
                                                            <w:t>value</w:t>
                                                        </w:r>
                                                    </w:p>
                                                </w:txbxContent>
                                            </wps:txbx>
                                            <wps:bodyPr lIns="90000" rIns="90000" tIns="45000" bIns="45000">
                                                <a:noAutofit/>
                                            </wps:bodyPr>
                                        </wps:wsp>
                                    </a:graphicData>
                                </a:graphic>
                            </wp:inline>
                        </w:drawing>
                    </mc:Choice>
                    <mc:Fallback>
                        <w:pict>
                            <v:shapetype id="shapetype_15" coordsize="21600,21600" o:spt="15" adj="10800" path="m,l@2,l21600,10800l@2,21600l,21600xe">
                                <v:stroke joinstyle="miter"/>
                                <v:formulas>
                                    <v:f eqn="val 21600"/>
                                    <v:f eqn="val #0"/>
                                    <v:f eqn="sum width 0 @1"/>
                                    <v:f eqn="sum @2 width 0"/>
                                    <v:f eqn="prod 1 @3 2"/>
                                    <v:f eqn="prod @2 1 2"/>
                                </v:formulas>
                                <v:path gradientshapeok="t" o:connecttype="rect" textboxrect="0,0,@4,21600"/>
                                <v:handles>
                                    <v:h position="@2,0"/>
                                </v:handles>
                            </v:shapetype>
                            <v:shape id="shape_0" ID="Shape1_1" fillcolor="#25b52c" stroked="f" style="position:absolute;margin-left:0pt;margin-top:-18.1pt;width:120.75pt;height:17.25pt;flip:x;mso-wrap-style:square;v-text-anchor:top;mso-position-vertical:top" wp14:anchorId="30CF43F6" type="shapetype_15">
                                <v:textbox>
                                    <w:txbxContent>
                                        <w:p>
                                            <w:pPr>
                                                <w:overflowPunct w:val="false"/>
                                                <w:rPr/>
                                            </w:pPr>
                                            <w:r>
                                                <w:rPr>
                                                    <w:sz w:val="24"/>
                                                </w:rPr>
                                                <w:t>value</w:t>
                                            </w:r>
                                        </w:p>
                                    </w:txbxContent>
                                </v:textbox>
                                <v:fill o:detectmouseclick="t" type="solid" color2="#da4ad3"/>
                                <v:stroke color="#3465a4" joinstyle="round" endcap="flat"/>
                                <w10:wrap type="square"/>
                            </v:shape>
                        </w:pict>
                    </mc:Fallback>
                </mc:AlternateContent>
            </w:r>
        </w:p>
        <w:p __block_helper_container__="true">
            <w:pPr>
                <w:pStyle w:val="TableContents"/>
                <w:bidi w:val="0"/>
                <w:jc w:val="left"/>
                <w:rPr/>
            </w:pPr>
            <w:r>
                <w:rPr/>
                <w:t __block_helper__="true"></w:t>
            </w:r>
        </w:p>
        <w:sectPr>
            <w:type w:val="nextPage"/>
            <w:pgSz w:w="11906" w:h="16838"/>
            <w:pgMar w:left="1134" w:right="1134" w:header="0" w:top="1134" w:footer="0" w:bottom="1134" w:gutter="0"/>
            <w:pgNumType w:fmt="decimal"/>
            <w:formProt w:val="false"/>
            <w:textDirection w:val="lrTb"/>
            <w:docGrid w:type="default" w:linePitch="100" w:charSpace="0"/>
        </w:sectPr>
    </w:body>
</w:document>

The matched XML fragment:

<w:p __block_helper_container__="true">
    <w:pPr>
        <w:pStyle w:val="TableContents"/>
        <w:rPr/>
    </w:pPr>
    <w:r>
        <w:rPr/>
        <w:t __block_helper__="true"></w:t>
    </w:r>
    <w:r>
        <w:rPr/>
        <mc:AlternateContent>
            <mc:Choice Requires="wps">
                <w:drawing>
                    <wp:inline distT="0" distB="9525" distL="0" distR="5715" wp14:anchorId="30CF43F6">
                        <wp:extent cx="1534795" cy="220345"/>
                        <wp:effectExtent l="0" t="0" r="5715" b="9525"/>
                        <wp:docPr id="1" name="Shape1_1"/>
                        <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
                            <a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
                                <wps:wsp>
                                    <wps:cNvSpPr/>
                                    <wps:spPr>
                                        <a:xfrm flipH="1">
                                            <a:off x="0" y="0"/>
                                            <a:ext cx="1534320" cy="219600"/>
                                        </a:xfrm>
                                        <a:prstGeom prst="homePlate">
                                            <a:avLst>
                                                <a:gd name="adj" fmla="val 50000"/>
                                            </a:avLst>
                                        </a:prstGeom>
                                        <a:solidFill>
                                            <a:srgbClr val="25b52c"/>
                                        </a:solidFill>
                                        <a:ln w="0">
                                            <a:noFill/>
                                        </a:ln>
                                    </wps:spPr>
                                    <wps:style>
                                        <a:lnRef idx="2">
                                            <a:schemeClr val="dk1">
                                                <a:shade val="50000"/>
                                            </a:schemeClr>
                                        </a:lnRef>
                                        <a:fillRef idx="1">
                                            <a:schemeClr val="dk1"/>
                                        </a:fillRef>
                                        <a:effectRef idx="0">
                                            <a:schemeClr val="dk1"/>
                                        </a:effectRef>
                                        <a:fontRef idx="minor"/>
                                    </wps:style>
                                    <wps:txbx>
                                        <w:txbxContent>
                                            <w:p>
                                                <w:pPr>
                                                    <w:overflowPunct w:val="false"/>
                                                    <w:rPr/>
                                                </w:pPr>
                                                <w:r>
                                                    <w:rPr>
                                                        <w:sz w:val="24"/>
                                                    </w:rPr>
                                                    <w:t>value</w:t>
                                                </w:r>
                                            </w:p>

That fragment is then parsed by xmldom at https://github.com/jsreport/jsreport-docx/blob/2.9.1/lib/postprocess/removeBlockHelper.js#L14

And this is where the following errors come from:

[xmldom warning]        unclosed xml attribute
@#[line:1,col:163]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:184]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:210]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:221]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:422]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:497]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:584]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:1133]
[xmldom warning]        unclosed xml attribute
@#[line:1,col:1143]

And the parsed result is then:

<w:p __block_helper_container__="true"
    xmlns:w="">
    <w:pPr>
        <w:pStyle w:val="TableContents"/>
        <w:rPr/>
    </w:pPr>
    <w:r>
        <w:rPr/>
        <w:t __block_helper__="true"/>
    </w:r>
    <w:r>
        <w:rPr/>
        <mc:AlternateContent xmlns:mc=""/>
        <mc:Choice Requires="wps"/>
        <w:drawing/>
        <wp:inline distT="0" distB="9525" distL="0" distR="5715"
            xmlns:wp14="" wp14:anchorId="30CF43F6"
            xmlns:wp=""/>
        <wp:extent cx="1534795" cy="220345"/>
        <wp:effectExtent l="0" t="0" r="5715" b="9525"/>
        <wp:docPr id="1" name="Shape1_1"/>
        <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/>
        <a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
            xmlns:a=""/>
        <wps:wsp xmlns:wps=""/>
        <wps:cNvSpPr/>
        <wps:spPr>
            <a:xfrm flipH="1">
                <a:off x="0" y="0"/>
                <a:ext cx="1534320" cy="219600"/>
            </a:xfrm>
            <a:prstGeom prst="homePlate">
                <a:avLst>
                    <a:gd name="adj" fmla="val 50000"/>
                </a:avLst>
            </a:prstGeom>
            <a:solidFill>
                <a:srgbClr val="25b52c"/>
            </a:solidFill>
            <a:ln w="0">
                <a:noFill/>
            </a:ln>
        </wps:spPr>
        <wps:style>
            <a:lnRef idx="2">
                <a:schemeClr val="dk1">
                    <a:shade val="50000"/>
                </a:schemeClr>
            </a:lnRef>
            <a:fillRef idx="1">
                <a:schemeClr val="dk1"/>
            </a:fillRef>
            <a:effectRef idx="0">
                <a:schemeClr val="dk1"/>
            </a:effectRef>
            <a:fontRef idx="minor"/>
        </wps:style>
        <wps:txbx/>
        <w:txbxContent/>
        <w:p>
            <w:pPr>
                <w:overflowPunct w:val="false"/>
                <w:rPr/>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:sz w:val="24"/>
                </w:rPr>
                <w:t>value</w:t>
            </w:r>
        </w:p>
    </w:r>
</w:p>
pdecat commented 3 years ago

Submitted https://github.com/jsreport/jsreport-docx/pull/27 to try and address this issue.