Invalid XML Error When Generating PDF from Advanced PDF Template in SuiteScript 2.1
Hello everyone,
I have created a custom Suitelet where users can select a Sales Order and submit it to generate a PDF using an advanced PDF template. However, when executing the script, I encounter an “Invalid XML” error.
If there are two Sales Orders, I want both in a single PDF, with the first Sales Order on the first page and the second Sales Order on the second page.
Here is a snippet of my code:
My code is below
/**
* @NApiVersion 2.1
* @NScriptType Suitelet
*/
define([‘N/render’, ‘N/record’, ‘N/file’, ‘N/log’], function (render, record, file, log) {
function onRequest(context) {
if (context.request.method === ‘GET’) {
var orderIds = JSON.parse(context.request.parameters.orders || ‘[]’);
if (!orderIds.length) {
context.response.write(‘No orders selected.’);
return;
}
log.debug(‘Order IDs’, orderIds);
var xmlContent = ‘<?xml version=”1.0″ encoding=”UTF-8″?>\n<pdfset>’;
orderIds.forEach(function (soId) {
try {
var salesOrder = record.load({ type: record.Type.SALES_ORDER, id: soId });
var singleRenderer = render.create();
singleRenderer.setTemplateById(102); // Update with your actual PDF template ID
singleRenderer.addRecord(‘record’, salesOrder);
var pdfBody = singleRenderer.renderAsString();
pdfBody = cleanXml(pdfBody);
xmlContent += `<pdf>${pdfBody}</pdf>\n`;
} catch (e) {
log.error(‘Error loading Sales Order or rendering PDF’, e);
}
});
xmlContent += `</pdfset>`;
var txtFile = file.create({
name: ‘testDebug.txt’,
fileType: file.Type.PLAINTEXT,
contents: String(xmlContent),
folder: 2552 // Replace with your desired folder ID in NetSuite
});
// Save the file in the file cabinet
var fileId = txtFile.save();
log.debug(‘Final XML Content’, xmlContent);
try {
// Ensure the XML content is valid before converting to PDF
if (!isValidXml(xmlContent)) {
context.response.write(‘Error: Invalid XML structure. Check logs for details.’);
log.error(‘Invalid XML structure’, xmlContent);
return;
}
var finalPdf = render.xmlToPdf({ xmlString: xmlContent });
saveAndServePdf(finalPdf, context);
} catch (e) {
log.error(‘Error rendering final PDF’, e);
context.response.write(‘Error generating PDF: ‘ + e.message);
}
}
}
function cleanXml(xmlString) {
return xmlString
.replace(/<\?xml.*?\?>/g, ”) // Remove XML declaration inside content
.replace(/<!DOCTYPE[^>]*>/g, ”) // Remove DOCTYPE declaration
.replace(/[\u0000-\u001F\u007F-\u009F]/g, ”) // Remove control characters
.replace(/&(?!amp;|lt;|gt;|quot;|apos;)/g, ‘&’) // Escape unencoded ampersands
.replace(/>/g, ‘>’) // Escape greater than symbol
.replace(/</g, ‘<’); // Escape less than symbol
}
function isValidXml(xmlString) {
try {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xmlString, ‘application/xml’);
return !xmlDoc.querySelector(‘parsererror’); // Returns true if XML is valid
} catch (e) {
return false;
}
}
function saveAndServePdf(finalPdf, context) {
var mergedPdfFile = file.create({
name: ‘Merged_SalesOrders.pdf’,
fileType: file.Type.PDF,
contents: finalPdf.getContents(),
folder: 2552, // Update with your actual folder ID
isOnline: true
});
var mergedFileId = mergedPdfFile.save();
var fileRecord = file.load({ id: mergedFileId });
context.response.write(`<script>window.open(‘${fileRecord.url}’, ‘_blank’);</script>`);
}
return { onRequest: onRequest };
});



