guigrpa / docx-templates

Template-based docx report creation
MIT License
916 stars 145 forks source link

go to the next page with a specific condition (conditional page break) #283

Open mehrdadTemp opened 2 years ago

mehrdadTemp commented 2 years ago

HI

how to go to the next page (go to next docx page and keep going) with a specific condition like

+++IF condition +++ +++= page_break +++ +++END-IF+++

In python can use this //code from docxtpl import DocxTemplate, R, InlineImage page_break = R('\f') //docx template {% if condition %} {{r page_break }} {% endif %}

and how use index in FOR

and when try merge two docx file catch this error "For source /word/document.xml, cannot find part word/media/template_document.xml_img1.jpg from rel img1=media/template_document.xml_img1" i can not get this error with docxtpl before

jjhbw commented 2 years ago

Haven't tried this before, but what about adding a literal page break in between an IF-END-IF in the template? If not, you can look into using literal XML for this. I don't know the required XML magic, though. You may be able to find that here http://officeopenxml.com/anatomyofOOXML.php or here https://stackoverflow.com/questions/2795315/create-page-break-using-openxml

Would appreciate it if you could post in this thread what ended up working for you.

SuchiraD commented 1 year ago

I have faced a similar situation and following is what I have used. In JS code,

createReport({
            template,
            data: {
                ...data,
                pageBreak: '||<w:br w:type="page" />||',
            },
            cmdDelimiter: ['{{', '}}'],
        });

In template

{{ IF condition }}
{{pageBreak}}
{{ END-IF }}
davidjb commented 1 year ago

In a similar vein, I solved this with:

const report = await createReport({
  ...
  additionalJsContext: {
      pagebreak: (show) => (show || typeof show === 'undefined') ? '||<w:br w:type="page"/>||' : '',
  }
})

which slims down the syntax a bit whilst allowing any given conditional or falsy value to prevent the break from showing, such as:

{{ pagebreak($idx < $component.issues.length-1) }}
{{ pagebreak($idx < 5) }}
{{ pagebreak($name === 'Alice') }}
{{ pagebreak($data) }}

pagebreak() can also be called without an argument to always output a break (or used within other contexts such as IF).

jjhbw commented 1 year ago

Thanks for this solution @davidjb !! 🥳

JPBM135 commented 1 year ago

I encountered a problem with this method, since the <w:br w:type="page"/> comes wrapped inside an

<w:t xml:space="preserve">
    <w:br w:type="page" />
</w:t>

So I'd actually had to replace the text manually, here is the snippet if anyone wants it:

export function sanitizeDocumentXml(bin: Uint8Array) {
  try {
    writeFileSync('/tmp/template.docx', bin);
    execSync('unzip -o /tmp/template.docx -d /tmp/template');

    const documentXml = readFileSync('/tmp/template/word/document.xml', 'utf8');

    const sanitizedDocumentXml = documentXml.replaceAll(
      '<w:t xml:space="preserve"><w:br w:type="page" /></w:t>',
      '<w:br w:type="page" />',
    );

    writeFileSync('/tmp/template/word/document.xml', sanitizedDocumentXml);
    execSync('cd /tmp/template && zip -r /tmp/template.docx ./*');

    return readFileSync('/tmp/template.docx');
  } catch (error) {
    console.log('[sanitizeDocumentXml]: error', error);
    return bin;
  }
}

I believe this is compatible with most operation systems, but I tested in linux

Vitomir2 commented 1 year ago

I have faced a similar situation and following is what I have used. In JS code,

createReport({
            template,
            data: {
                ...data,
                pageBreak: '||<w:br w:type="page" />||',
            },
            cmdDelimiter: ['{{', '}}'],
        });

In template

{{ IF condition }}
{{pageBreak}}
{{ END-IF }}

Hey @SuchiraD, for some reason this does not break my pages. Here is my template:

*FOR chartData IN chartsData*
*pageBreak*

*$chartData.question*

*IMAGE chart($chartData.imageSrc)*

*IF $chartData.table*
*IMAGE table($chartData.table)*
*END-IF*

*END-FOR chartData*

then, the code:

const buffer = await createReport({
        cmdDelimiter: ['*', '*'],
        template,
        additionalJsContext: {
            pageBreak: '||<w:br w:type="page" />||,
            chartsData,
            chart: (imageSrc) => {
                ...
            },
            table: (table) => {
                ...
            }
        }
    })

and here is the result: image

SuchiraD commented 7 months ago

then, the code:

const buffer = await createReport({
        cmdDelimiter: ['*', '*'],
        template,
        additionalJsContext: {
            pageBreak: '||<w:br w:type="page" />||,
            chartsData,
            chart: (imageSrc) => {
                ...
            },
            table: (table) => {
                ...
            }
        }
    })

and here is the result: image

@Vitomir2 , Sorry for very late late reply. Somehow I have missed this comment

I am not very clear on the second part of your comment. Were you able to do the pageBreak by adding it in additionalJsContext?