Read the values of PDF Acroform Fields in JavaScript - javascript

I need to parse a PDF file with a lot of Acroform fields and extract the field name (/T in PDF markup) and value (/V in PDF markup) for each field. I'd like to do this client side in JavaScript but I'm not finding existing examples or libraries that seem to do this. Does any one have any suggestions on how to approach it?
Edit: I want to do this client side for performance reasons. I will be taking the field values from the PDF and saving them to a database. But for some of the fields I will need to prompt for additional input before saving. I'd like to do that all on the client and then send the values to the server for saving instead of uploading the PDF to the server, parsing it on the server, sending the fields that need additional input back to the client, and then sending those results to be saved.

The company I work for has PDFTron WebViewer SDK which can you read through all the form fields, read/edit the values, all client side in the browser, and you can also allow the user to view the entire PDF and manually fill in any fields.
Sample: https://www.pdftron.com/webviewer/demo/pdf-forms
I need to parse a PDF file with a lot of Acroform fields and extract the field name (/T in PDF markup) and value (/V in PDF markup) for each field.
You can see live code in the pdf-forms sample linked above, but this code below would iterate all fields and print the value, if any, to the console.
viewerElement.addEventListener('ready', function() {
var viewerInstance = viewer.getInstance();
const docViewer = viewerInstance.docViewer;
const annotManager = docViewer.getAnnotationManager();
const fieldManager = annotManager.getFieldManager();
const checkField = (field) => {
// Do something with data
const { name, value } = field;
console.log(name + ' ' + value);
// Check children fields
field.children.forEach(checkField);
}
docViewer.on('annotationsLoaded', function() {
fieldManager.forEachField(checkField);
});
});
I will be taking the field values from the PDF and saving them to a
database.
If you just want the field values, you can get that easily from WebViewer, in PDF ISO XFDF XML format, which you can send to your server and merge to the PDF using any PDF SDK that supports XFDF. This way, you can avoid transferring the entire PDF back and forth, and just send the much smaller form field values as XML.
var xmlXfdfFieldData = annotManager.exportAnnotations({fields:true, widgets:false, links:false});
for some of the fields I will need to prompt for additional input
before saving.
The WebViewer UI/UX is completely customizable so you can interact/prompt the user on which fields to update.
This sample below shows how to change color and opacity of form fields, which would be a starting point you can use to apply your own business logic.
https://www.pdftron.com/documentation/web/guides/form-samples#customizing-form-fields

It turns out not to be that hard to parse what I need out of the PDF and a library would probably be overkill. Here's the code I'm using:
function importPdf(event) {
const reader = new FileReader();
reader.onload = function () {
const matches = reader.result.match(/((obj)[\s\S]*?(endobj))/g);
matches.forEach(match => {
const tIndex = match.indexOf('/T (');
const vIndex = match.indexOf('/V (');
if(tIndex > -1 && vIndex > -1) {
const fieldNameSegment = match.substring(tIndex + 4);
const fieldNameValue = fieldNameSegment.substring(0,fieldNameSegment.indexOf(')'));
const valueSegment = match.substring(vIndex + 4);
const dataValue = valueSegment.substring(0, valueSegment.indexOf(')'));
console.log(fieldNameValue, dataValue);
}
});
}
reader.readAsText(event.target.files[0]);
}
<input type="file" id="file-input" onchange="importPdf(event)">

Related

How to send multiple attachments via email

I have a project to create PDF out from google form and send multiple PDF through email in Google Script,
The relevant CODE segment is as follows ,
if(TF_Samples!= null){
//Generating Lab Reports for TAPE LIFT
for (var Sample_Number in TF_Samples) {
//Creating Report File - Word Version
Template_Doc = Tape_Template_Doc ;
Report_File_Word = Template_Doc.makeCopy(Report_Folder_Word);
Report_File_Word.setName(data[0]+"-TAPE LIFT");
Opened_Report_Doc = DocumentApp.openById(Report_File_Word.getId());
//Getting the Content of the Report & Replacing the Fields
Report_Body = Opened_Report_Doc.getBody();
Report_Body.replaceText("{{ Client Address }}",data[2]);
Report_Body.replaceText("{{ Sample Date }}",data[1]);
Report_Body.replaceText("{{ Sample Number }}",TF_Samples[Sample_Number]);
//Saving & Closing the Report
Opened_Report_Doc.saveAndClose();
//Creating PDF version of Report
Blob_PDF = Report_File_Word.getAs(MimeType.PDF);
Report_PDF_Version = Report_Folder_PDF.createFile(Blob_PDF).setName(data[0]+"-SPORE TRAPE");
}
}
I managed to send email with single attachment but since here I am looping through a for loop I don't know how to do that.
Create a global array and push within each loop iteration the file into the array
Sample modification:
...
var files = [];
for (var Sample_Number in TF_Samples) {
...
Report_PDF_Version = Report_Folder_PDF.createFile(Blob_PDF).setName(data[0]+"-SPORE TRAPE");
files.push(Report_PDF_Version);
}
GmailApp.sendEmail(recipient, subject, body, {attachments:files});
...
as specified in the documentation, the attachment argument must be an array of blobs.
So, instead of converting the blob to a drive file just send the blobs like this :
Blob_PDF = Report_File_Word.getAs(MimeType.PDF);
and push these blobs in an array one by one.
The argument will then be valid.

Passing A Js File Objects Array To Flask

I wonder if someone can help me with this as I'm new to JavaScript.
I have a form, and within it I have a file input. However, when a file is uploaded I need to manipulate it and add new properties. So I extract the file from the input like so:
let uploadedFile = [];
for (let val of this.files) {
uploadedFile.push(val)
}
for (val of uploadedFile){
//Check file extension to classify the file type:
let extension = val.name.split('.')
extension = String((extension[extension.length-1]).toLowerCase());
let fileType = val.type;
//Arrays to hold extensions types we want to identify
const imageExtensions = ['jpeg','jpg','png']
const documentExtensions = ['xls','xlsx','csv','pdf']
//Classify files and push to global files array
if (imageExtensions.includes(extension)){
let newFile = new File([val], `${crname}-nr-${FileCount}.${extension}`, {type: `${fileType}`});
newFile.originalName = `${val.name}`
globalFilesArray.push(newFile);
}
}
So as you can see the new file objects are pushed to an array named 'globalFilesArray'
Now in Flask i'm typically accessing the form data using the request module.
Must I now pass the array into a separate hidden file input so I can access it when the entire form is submitted?
I'm a bit stumped with this because I need this to happen only when the form is submitted so I can also access the rest of the input fields in the form.

ANTLR in Javascript : impossible to parse again a part of code

I've tried to develop an UI in JavaScript to parse specific code described in my ANTLR4 grammar. I use a visitor to parse all parts of code and generate a questionnaire. Afterwards, depending on users' input in a JavaScript form, I parse the last part of my code to generate results (it's close to the calculator mechanism drafted into ANTLR book). So far so good. Nonetheless, I would like to modify inputs in the JavaScript form and parse a second time to regenerate and recalculate some results. At this moment, the AST tree becomes empty. I've tried to reinitialise the lexer the parser, the visitor as such as to create a new instance of the parser. It seems that previous parser and lexer are still active and it is impossible to "move the cursor up" to parse again a specific block of my source code.
Thanks for your precious help.
Chris
Below summarised file and script.
grammar.G4
pre
: title ('\n')+
author ('\n')+
;
peri
: (statement ('\n')+)*
(answer ('\n')+)*
;
post
: (feedback ('\n')+)*
;
exercise
: pre peri post
;
//End of Grammar
javascript main class :
class MyExercise {
constructor(){
this.chars = antlr4.CharStreams.fromBuffer(input,'utf-8');
this.lexer = new MyLexer(this.chars);
this.tokens = new antlr4.CommonTokenStream(this.lexer);
this.parser = new MyParser(this.tokens);
this.visitor = new LabeledVisitor(this.exercise,this.parser);
this.parser.buildParseTrees = true;
tree = this.parser.pre();
this.visitor.visitPre(tree);
tree = this.parser.peri();
this.visitor.visitPeri(tree);
this.generateAnswersHTML() // generate HTML results and also inputfields to collect values from user.
this.generateSubmitButton(); // generate submit HTML button
}
generateSubmitButton(){
var button = document.createElement('input');
button.setAttribute('type','submit');
button.setAttribute('value','Check answer');
button.addEventListener("click",this.checkAnswers.bind(this));
document.getElementById("answer").appendChild(button);
}
checkAnswers(object){
var tree = this.parser.post();
this.visitor.visitPost(tree);
this.generateFeeback(); //Generate HTML feedbacks (function of inputed values by user)
}
}
It works well the first time but when I click again on the button checkanswer which calls checkAnswer() method, the tree becomes empty.
There are 2 ways to reparse your input:
Recreate your input stream, token source + parser every time.
Reset your input stream and token source, by
Recreate your input stream or load the new text into your existing one.
Call parser.reset().
Set your input stream again in the token source (to reset it, just calling .reset() won't cut it). lexer.setInputStream(input);
Set your token source in the token stream again, for the same reason. tokens.setTokenSource(lexer);

How to pass the csv file from file cabinet in NetSuite to ftp server using JavaScript?

Could you help me do the task using JavaScript?
I have a task and if i do it manually it looks like this:
i create Saved Search in NetSuite.
Download the result of created saved search in csv.
The i put this file on ftp server, using FileZilla. (i had a connection with server previously: write a domain, username and password - that's all)
Now, a need it solve through sutlet script.
1. Create Saved Search - done
2. Create csv with result of saved search in content and put it in file cabinet in the NetSuite - done
3. Ok, now i have a needs me file but i do not understand how to pass it on ftp.
*i tried to study several articles, but frankly speaking could not to solve my problem. Moreover, their article seems about manually method not automative
this aritcle - https://ursuscode.com/netsuite-tips/suitescript-2-0-sftp-tool/*
var searchResult = Contacts.run().getRange(0,999);
log.debug('Contacts', searchResult);
var Header = 'INTERNAL ID' + ';' + 'FIRST NAME' + ';' + 'LAST NAME';
var Content = "";
for (var i = 0; i < searchResult.length; i++) {
var internalid = searchResult[i].getValue('internalid');
var FirstName = searchResult[i].getValue('firstname');
var LastName = searchResult[i].getValue('lastname');
Content = Content + internalid + ';'
+ FirstName + ';'
+ LastName;
Content = Content + '\n';
}
var fileObj = file.create({
name: 'test.csv',
fileType: file.Type.CSV,
contents: Header + '\n' + Content
});
fileObj.folder = 45434;
var fileId = fileObj.save();
var savedFileObj = file.load({
id: fileId
});
var myPwGuid = '';
var myHostKey = ''
var objConnection = sftp.createConnection({
username: '',
passwordGuid: myPwGuid,
url: 'ftp.expertsender.com',
hostKey: myHostKey
});
NetSuite does not support ftp, it only supports sftp.
NetSuite has the support for SFTP, FTP and SFTP runs in different port numbers, However FTP transfers data in plain text format which will compromise your security, using SFTP is the better option as it will transfer your data in encrypted format and security is assured.
In your example, I believe you're calling FTP request which will not work in this case.
Oki,
Now, the article you did mention is the right one : why ? Because the first required step to be able to use SFTP is to generate a GUID. You are talking about manual methods, well yes, including the one in that Article, but it is not a problem, because once you have generated the GUID, you don't need to change it, so it is a one time action, unless your ftp credential change.
So, first step : use "ursuscode" to create a Suitelet. Deploy that suitelet and use it to generate the GUID (it is a form where you need to set the ftp password, host...). Using the same form, you can then generate the HOST key (check the video).
Second step, use the Generated GUID and HOST Key in your code.
Third step, add the code to upload the file : from netsuite help page, here is an example:
connection.upload({
directory: 'relative/path/to/remote/dir',
filename: 'newFileNameOnServer.js',
file: myFileToUpload,
replaceExisting: true
});
By the way, you can upload the file without the need to Save and Reload it again (https://system.na2.netsuite.com/app/help/helpcenter.nl?fid=section_4617004932.html).
Note: remember that this is an SFTP, so probably support only SFTP not FTP.
Suggestion: About the GUID (and the other data needed for the connection), I suggest that you use a Script Parameter to provide the GUID to your script code, so if your password change, you can regenerate the GUID and update the script parameter value without the need to touch your code.
Hope this helps!

How to retrieve name of Javascript events attached to form in CRM 2016 server side

I am trying to retrieve All the Javascript events/libraries attached to the form of particular entity from the server side.
I am able to retrieve the all the forms of that particular entity by using query expression
QueryExpression q = new QueryExpression("systemform");
q.ColumnSet = new ColumnSet() { AllColumns = true };
q.Criteria.AddCondition(new ConditionExpression("objecttypecode", ConditionOperator.Equal, "account"));
EntityCollection ec = serviceProxy.RetrieveMultiple(q);
I just need to know the Javascript library attached to OnLoad or OnSave Events in CRM form.
Querying the formxml attribute on the form will give you what you are looking for. For e.g. to get all the attribute, event and function names on contact form:
var attributeEventsDetails =
XDocument.Parse(xrmServiceContext.SystemFormSet.FirstOrDefault(form => form.Name == "contact").FormXml)
.Descendants("event")
.Select(descendants =>
new
{
AttributeName = descendants.Attribute("attribute"),
EventName = descendants.Attribute("name"),
FunctionName =
descendants.Descendants()
.FirstOrDefault(childDesc => childDesc.Name == "Handler")
.Attribute("functionName")
});
Just FYI. systemform object doesn't contain entity forms. It contains dashboards - https://msdn.microsoft.com/en-us/library/gg334669.aspx
To get what you need you will have to get entity metadata that will contain forms. Description of a form is an Xml that contains what you need.

Categories