How to recalculate page number when combining mult

2020-02-01 05:42发布

问题:

I have decided to pass this Q/A after receiving both questions in chat and comment on answer on how to handle page number in "combined" jasper reports

When combining reports in java using

JasperPrint jasperPrint1 = JasperFillManager.fillReport(report1, paramMap);
JasperPrint jasperPrint2 = JasperFillManager.fillReport(report2, paramMap);
List<JasperPrint> jasperPrintList = Arrays.asList(jasperPrint1, jasperPrint2); 

JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));

The page number and total page number

<textField>
    <reportElement x="435" y="30" width="80" height="20" uuid="14bad2ac-3a56-4cf4-b4dd-310b8fcd2120"/>
    <textElement textAlignment="Right"/>
    <textFieldExpression><![CDATA["Page "+$V{PAGE_NUMBER}+" of"]]></textFieldExpression>
</textField>
<textField evaluationTime="Report">
        <reportElement x="515" y="30" width="40" height="20" uuid="06567ba6-6243-43e9-9813-f6593528524c"/>
        <textFieldExpression><![CDATA[" " + $V{PAGE_NUMBER}]]></textFieldExpression>
</textField>

Is always restarted for each report that I'm including in list, is there a way that I can recalculate these?

I want to have on each page the current page (and correct total number of pages) considering my final combined report.

回答1:

The jasper-report way would be to not combine reports in java but to use a main report and include the different reports as subreports in this main report, moving the page number to the main report.

However, if we like to combine in java as in questions, we can also use java to edit and correct the page numbers.

This idea is to set "markers" in report and then post-process the JasperPrint, replacing the markers with the actual page number.

Example

jrxml (without shame using freemaker style)

<textField>
    <reportElement x="435" y="30" width="80" height="20" uuid="14bad2ac-3a56-4cf4-b4dd-310b8fcd2120"/>
    <textElement textAlignment="Right"/>
    <textFieldExpression><![CDATA["${CURRENT_PAGE_NUMBER}"]]></textFieldExpression>
</textField>
<textField evaluationTime="Report">
    <reportElement x="515" y="30" width="40" height="20" uuid="06567ba6-6243-43e9-9813-f6593528524c"/>
    <textFieldExpression><![CDATA["${TOTAL_PAGE_NUMBER}"]]></textFieldExpression>
</textField>

java

Define my markers

private static final String CURRENT_PAGE_NUMBER = "${CURRENT_PAGE_NUMBER}";
private static final String TOTAL_PAGE_NUMBER = "${TOTAL_PAGE_NUMBER}";

after the fillReport of my reports replace all my markers with the true numbers

//First loop on all reports to get totale page number
int totPageNumber=0;
for (JasperPrint jp : jasperPrintList) {
    totPageNumber += jp.getPages().size();
}

//Second loop all reports to replace our markers with current and total number
int currentPage = 1;
for (JasperPrint jp : jasperPrintList) {
    List<JRPrintPage> pages = jp.getPages();
    //Loop all pages of report
    for (JRPrintPage jpp : pages) {
        List<JRPrintElement> elements = jpp.getElements();
        //Loop all elements on page
        for (JRPrintElement jpe : elements) {
            //Check if text element
            if (jpe instanceof JRPrintText){
                JRPrintText jpt = (JRPrintText) jpe;
                //Check if current page marker
                if (CURRENT_PAGE_NUMBER.equals(jpt.getValue())){
                    jpt.setText("Page " + currentPage + " of"); //Replace marker
                    continue;
                }
                //Check if totale page marker
                if (TOTAL_PAGE_NUMBER.equals(jpt.getValue())){
                    jpt.setText(" " + totPageNumber); //Replace marker
                }
            }
        }
        currentPage++;
    }
}

Note: If it was code in one of my projects I would not nest this quantity of for and if statements, but extract code to different method's, but for clarity of post I have decided to post it all as one code block

Now its ready for the export

JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));
....


回答2:

That's correct but if there are JRPrintFrame's and if your remark is in this code can not find it. Below is the one working for me: (You should find out in which container element the TOTAL_PAGE_NUMBER in!

private void correctPageNumbers(List<JasperPrint> jasperPrintList) {
// First loop on all reports to get totale page number
int totPageNumber = 0;
for (JasperPrint jp : jasperPrintList) {
    totPageNumber += jp.getPages().size();
}

// Second loop all reports to replace our markers with current and total
// number
int currentPage = 1;
for (JasperPrint jp : jasperPrintList) {
    List<JRPrintPage> pages = jp.getPages();
    // Loop all pages of report
    for (JRPrintPage jpp : pages) {
        List<JRPrintElement> elements = jpp.getElements();

        // Loop all elements on page
        for (JRPrintElement jpe : elements) {
            if (jpe instanceof JRSubreport) {
                JRSubreport jrr = (JRSubreport) jpe;
                jrr.getBackcolor();
            }
            // Check if text element
            if (jpe instanceof JRPrintText) {
                JRPrintText jpt = (JRPrintText) jpe;
                System.out.println("jpt.value: " + jpt.getValue() + "  |  " + "jpt.text" + jpt.getText());
                // Check if totale page marker
                if (TOTAL_PAGE_NUMBER.equals(jpt.getValue())) {
                    jpt.setText(" " + totPageNumber); // Replace marker
                }
            } else if (jpe instanceof JRTemplatePrintFrame) {
                List<JRPrintElement> frameElements = ((JRTemplatePrintFrame) jpe).getElements();
                for (JRPrintElement jpef : frameElements) {
                    if (jpef instanceof JRPrintText) {
                        JRPrintText jpt = (JRPrintText) jpef;
                        System.out.println("jpt.value: " + jpt.getValue() + "  |  " + "jpt.text"
                                + jpt.getText());
                        // Check if totale page marker
                        if (TOTAL_PAGE_NUMBER.equals(jpt.getValue())) {
                            jpt.setText(" " + totPageNumber); // Replace
                                                                // marker
                        }
                    }
                }
            }
        }
        currentPage++;
    }
}

}



回答3:

I was inspired by @Adityaz7 answer, I think his method is the best one. But I've done this without the need to add a library to the project and to create a temporary PDF file.

First of all modify your template and add one parameter to hold the number of previous pages:

<parameter name="PREVIOUS_PAGE_NUMBER" class="java.lang.Integer"/>

In the report element that contains the page number insert this expression:

<textFieldExpression><![CDATA[String.valueOf($V{PAGE_NUMBER} + $P{PREVIOUS_PAGE_NUMBER})]></textFieldExpression>

You have to use String.valueOf() method because if you use the simple sum of the elements $V{PAGE_NUMBER} + $P{PREVIOUS_PAGE_NUMBER}, Jasper will process this as a sum of strings and will print the two numbers as concatenated. (e.g. will print 11 instead of 2)

In your java code create a variable to hold the number of previous pages and pass it to Jasper as parameter. Then fill the first report and simply add the size of the list that contains the report pages to prevPageNumber, and pass it as parameter to the second report and so on:

int prevPageNumber = 0;
HashMap<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("PREVIOUS_PAGE_NUMBER", prevPageNumber);

// fill the first report with prevPageNumber = 0
JasperPrint jasperPrint1 = JasperFillManager.fillReport(report1, paramMap);

if(jasperPrint1 != null && jasperPrint1.getPages() != null)
{   
    // add previous report page number
    prevPageNumber += jasperPrint1.getPages().size(); 
}

paramMap.put("PREVIOUS_PAGE_NUMBER", prevPageNumber);
JasperPrint jasperPrint2 = JasperFillManager.fillReport(report2, paramMap);

I wrote this inline to respect the structure OP used in the question, but of course is better to do this in a loop if you have more than 2 report to print.



回答4:

There's another easy solution which doesn't get into all those jasper classes like JasperPrint and JasperPages. The approach is to get No. of pages in previous prints and adding it to the PAGE_NUMBER variable of the next print.

Suppose we have three jasper prints. jasperPrint1, jasperPrint2, jasperPrint3 and because jasperPrint1 has dynamic content and variable no. of pages it's hard to know page number for the next prints. Solution for this is -

export the first print to temporary pdf file -

JasperExportManager.exportReportToPdfFile(jasperPrint1, 
tempPdfFile.getAbsolutePath());

initialize a variable which keeps track of pages of all the previous prints with 0;

pafeSofar = 0

write a method to return no. of pages in a pdf file:

private int numberOfPages(String filePath) throws IOException {
     PdfReader pdfReader = new PdfReader(filePath);
    return pdfReader.getNumberOfPages();
    }

Here PdfReader is a class from iText. You can add a maven dependency or manually download a .jar and add it to your class path.

Maven Dependency for itext pdf reader is following:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.0</version>
</dependency>

use this method to get no. of pages in previous report.

pagesSofar = pagesSofar + numberOfPages(tempPdfFile.getAbsolutePath());`

Create a parameter to pass this variable into and add that parameter to PAGE_NUMBER in other report.

parameterMap.put("previousPageNumber", pagesSofar);`

in your .jrxml file:

<textField>
<reportElement x="232" y="1" width="100" height="20"/>
<textFieldExpression><![CDATA[$V{PAGE_NUMBER}+$P{previousPageNumber}]]>
</textFieldExpression>
</textField>`

Here parameterMap is the map of your parameters that you'll pass in each report -

JasperPrint jasperPrint2 = JasperFillManager.fillReport(jasperReport3, parameterMap, new JREmptyDataSource());