I am trying to use SAP Crystal Reports in an Electron application (a desktop Node.js application). I have a few *.rpt
files that should read data from an SQLite database and then print some reports.
But it looks like there is no JavaScript library that would enable me to do that. The only thing I have found is SAP Crystal Reports JavaScript API which is just a client library for SAP Business Intelligence Platform that I don't use. I simply need to print some reports without relying on any external server.
Has anybody managed to make printing work with Crystal Reports in a Node.js application?
It looks like there is no way to interact with Crystal Reports directly from a Node.js application. So I have decided to write a simple command-line Java program which makes use of Crystal Reports Java SDK. I run this Java program from my Node.js application.
I have a single cr-cli/CrystalReport.java
file in my project:
import com.crystaldecisions.sdk.occa.report.application.*;
import com.crystaldecisions.sdk.occa.report.data.*;
import com.crystaldecisions.sdk.occa.report.document.*;
import com.crystaldecisions.sdk.occa.report.lib.*;
import com.crystaldecisions.sdk.occa.report.exportoptions.*;
import java.io.*;
import java.nio.file.*;
import org.apache.commons.cli.*;
public class CrystalReport {
public static void main(String args[]) {
Options options = new Options();
Option databaseOption = new Option("d", "database", true, "database file path");
databaseOption.setRequired(true);
options.addOption(databaseOption);
Option exportOption = new Option("e", "export", true, "output PDF file path");
options.addOption(exportOption);
Option reportOption = new Option("r", "report", true, "report file path");
reportOption.setRequired(true);
options.addOption(reportOption);
Option selectionOption = new Option("s", "selection", true, "record selection formula");
selectionOption.setRequired(true);
options.addOption(selectionOption);
CommandLine cmd = null;
try {
CommandLineParser parser = new DefaultParser();
cmd = parser.parse(options, args);
} catch (ParseException e) {
System.out.println(e.getMessage());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("cr-cli", options);
System.exit(1);
}
String databasePath = cmd.getOptionValue("database");
String exportPath = cmd.getOptionValue("export");
String reportPath = cmd.getOptionValue("report");
String selectionFormula = cmd.getOptionValue("selection");
try {
CrystalReport report = new CrystalReport(reportPath, databasePath, selectionFormula);
if (exportPath != null) {
Files.createDirectories(Paths.get(exportPath).getParent());
report.export(exportPath);
} else {
report.print();
}
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
private ReportClientDocument report;
public CrystalReport(String reportPath, String databasePath, String selectionFormula) throws ReportSDKException {
this.report = new ReportClientDocument();
this.report.open(reportPath, 0);
this.report.setRecordSelectionFormula(selectionFormula);
CrystalReport.replaceDatabaseConnection(this.report, databasePath);
SubreportController subreportController = report.getSubreportController();
for (String subreportName : subreportController.getSubreportNames()) {
ISubreportClientDocument subreport = subreportController.getSubreport(subreportName);
CrystalReport.replaceDatabaseConnection(subreport, databasePath);
}
}
private static void replaceDatabaseConnection(IReportClientDocument report, String databasePath) throws ReportSDKException {
DatabaseController databaseController = report.getDatabaseController();
IConnectionInfo oldConnectionInfo = databaseController.getConnectionInfos(null).getConnectionInfo(0);
PropertyBag attributes = new PropertyBag(oldConnectionInfo.getAttributes());
attributes.put("Connection URL", "jdbc:sqlite:" + databasePath);
IConnectionInfo newConnectionInfo = new ConnectionInfo(oldConnectionInfo);
newConnectionInfo.setAttributes(attributes);
int replaceParams = DBOptions._ignoreCurrentTableQualifiers + DBOptions._doNotVerifyDB;
databaseController.replaceConnection(oldConnectionInfo, newConnectionInfo, null, replaceParams);
}
public void export(String exportPath) throws IOException, FileNotFoundException, ReportSDKException {
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) this.report.getPrintOutputController().export(ReportExportFormat.PDF);
byte byteArray[] = new byte[byteArrayInputStream.available()];
File file = new File(exportPath);
FileOutputStream fileOutputStream = new FileOutputStream(file);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(byteArrayInputStream.available());
int x = byteArrayInputStream.read(byteArray, 0, byteArrayInputStream.available());
byteArrayOutputStream.write(byteArray, 0, x);
byteArrayOutputStream.writeTo(fileOutputStream);
}
public void print() throws ReportSDKException {
this.report.getPrintOutputController().printReport(new PrintReportOptions());
}
}
With these files in cr-cli/lib
folder:
com.azalea.ufl.barcode.1.0.jar
commons-configuration-1.2.jar
CrystalCommon2.jar
DatabaseConnectors.jar
JDBInterface.jar
log4j-api.jar
pfjgraphics.jar
sqlite-jdbc-3.41.0.0.jar
XMLConnector.jar
commons-cli-1.5.0.jar
commons-lang-2.1.jar
CrystalReportsRuntime.jar
icu4j.jar
jrcerom.jar
log4j-core.jar
QueryBuilder.jar
webreporting.jar
xpp3.jar
commons-collections-3.2.2.jar
commons-logging.jar
cvom.jar
jai_imageio.jar
keycodeDecoder.jar
logging.jar
sap.com~tc~sec~csi.jar
webreporting-jsf.jar
I compile it using the following command:
javac -classpath './cr-cli/lib/*' -source 8 -target 8 ./cr-cli/CrystalReport.java
And then I run it from my Electron (Node.js) application this way:
import {exec} from 'child_process';
import {app} from 'electron';
import path from 'path';
interface CrystalReportsCliOptions {
databasePath: string;
exportPath?: string;
reportPath: string;
selectionFormula: string;
}
const cliPath = path.join(app.isPackaged ? process.resourcesPath : process.cwd(), 'cr-cli');
export async function runCrystalReportsCli(options: CrystalReportsCliOptions) {
const javaVersion = await detectJavaVersion();
if (javaVersion < 8) {
return;
}
const command = ['java'];
if (javaVersion >= 9) {
command.push('--add-exports', 'java.base/sun.security.action=ALL-UNNAMED');
}
command.push('-classpath', `"${cliPath + path.delimiter + path.join(cliPath, 'lib/*')}"`);
command.push('CrystalReport');
command.push('--database', options.databasePath);
command.push('--report', options.reportPath);
command.push('--selection', `"${options.selectionFormula}"`);
if (options.exportPath) {
command.push('--export', options.exportPath);
}
return new Promise((resolve, reject) => {
exec(command.join(' '), (error, stdout) => (error ? reject(error) : resolve(stdout)));
});
}
function detectJavaVersion(): Promise<number> {
return new Promise(resolve => {
exec('java -version', (error, _, stderr) => {
if (error) {
resolve(0);
} else {
const [versionLine] = stderr.split('\n');
const version = versionLine.split('"')[1];
const majorVersion = Number(version.split('.')[version.startsWith('1.') ? 1 : 0]);
resolve(majorVersion);
}
});
});
}