org.jpedal.objects.javascript.RhinoParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of OpenViewerFX Show documentation
Show all versions of OpenViewerFX Show documentation
An Open Source JavaFX PDF Viewer
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2016 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* RhinoParser.java
* ---------------
*/
package org.jpedal.objects.javascript;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.SwingUtilities;
import org.jpedal.objects.Javascript;
import org.jpedal.objects.acroforms.AcroRenderer;
import org.jpedal.objects.acroforms.ReturnValues;
import org.jpedal.objects.acroforms.actions.ActionHandler;
import org.jpedal.objects.acroforms.creation.FormFactory;
import org.jpedal.objects.javascript.defaultactions.DisplayJavascriptActions;
import org.jpedal.objects.javascript.defaultactions.JpedalDefaultJavascript;
import org.jpedal.objects.javascript.functions.JSFunction;
import org.jpedal.objects.layers.Layer;
import org.jpedal.objects.layers.PdfLayerList;
import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.StringUtils;
public class RhinoParser extends DefaultParser implements ExpressionEngine{
/** version details of our library, so javascript knows what it can do,
* also adds static adobe methods to javascript for execution if called
*/
private static final String viewerSettings = AformDefaultJSscript.getViewerSettings()+
AformDefaultJSscript.getstaticScript();
private org.mozilla.javascript.Context cx;
private org.mozilla.javascript.Scriptable scope;
private String functions="";
/** used to stop the thread that called execute from returning
* before the javascript has been executed on the correct thread.
*/
private boolean javascriptRunning;
private final Javascript JSObj;
public RhinoParser(final Javascript js) {
JSObj = js;
}
/** make sure the contaxt has been exited */
@Override
public void flush() {
if(acro!=null && acro.getFormFactory()!=null){
if(acro.getCompData().formsRasterizedForDisplay()){
flushJS();
}else if (SwingUtilities.isEventDispatchThread()){
flushJS();
}else {
final Runnable doPaintComponent = new Runnable() {
@Override
public void run() {
flushJS();
}
};
try {
SwingUtilities.invokeAndWait(doPaintComponent);
} catch (final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
}
}
/** should only be called from our Thread code and not by any other access, as it wont work properly */
public void flushJS(){
//clear the stored functions when moving between files
functions="";
// Make sure we exit the current context
if (cx != null){
// The context could be in a different thread,
// we need to check for this and exit in a set way.
try{
org.mozilla.javascript.Context.exit();
// remembering to reset cx to null so that we recreate the contaxt for the next file
cx = null;
}catch(final IllegalStateException e){
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
}
/** NOT TO BE USED, is a dummy method for HTML only, WILL BE DELETED ONE DAY */
public void setJavaScriptEnded(){
javascriptRunning = false;
}
/**
* store and execute code
*/
public void executeFunctions(final String code, final FormObject ref, final AcroRenderer acro) {
//set to false at end of executeJS method
javascriptRunning=true;
if(acro.getFormFactory().getType()== FormFactory.SWING){
if (SwingUtilities.isEventDispatchThread()){
executeJS( code, ref, acro);
}else {
final Runnable doPaintComponent = new Runnable() {
@Override
public void run() {
executeJS( code, ref, acro);
}
};
try {
SwingUtilities.invokeAndWait(doPaintComponent);
} catch (final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
while(javascriptRunning){
try {
Thread.sleep(1000);
} catch (final InterruptedException e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
}
}
/** should only be called from our Thread code and not by any other access, as it wont work properly */
public void executeJS(String code, final FormObject ref, final AcroRenderer acro) {
final String defSetCode;
//NOTE - keep everything inside thr try, catch, finally, as the finally tidy's up so that the code will return properly.
try {
//if we have no code dont do anything
if(code.isEmpty() && functions.isEmpty()) {
return;
}
//check if any functions defined in code and save
String func = "";
int index1 = code.indexOf("function ");
while(index1!=-1){//if we have functions
int i = index1+8, bracket=0;
char chr = code.charAt(i);
while(true){//find the whole function
if(chr=='{'){
bracket++;
}
if(chr=='}'){
bracket--;
if(bracket==0) {
break;
}
}
//remember to get next char before looping again
chr = code.charAt(i++);
}
//find beginning of line for start
int indR = code.lastIndexOf('\r', index1);
int indN = code.lastIndexOf('\n', index1);
final int indS = ((indN", 1, null);
// Now evaluate the string we've collected.
cx.evaluateString(scope, code, "", 1, null);
} catch (final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}finally{
//sync any changes made in Layers (we need to get as method static at moment)
final PdfLayerList layersObj=acro.getActionHandler().getLayerHandler();
if(layersObj!=null && layersObj.getChangesMade()){
if(Layer.debugLayer) {
System.out.println("changed");
}
try {
//if we call decode page, i'm pritty sure we will recall the layers code, as we would recall all the JS
//hence the infinate loop
acro.getActionHandler().getPDFDecoder().decodePage(-1);
//@fixme
//final org.jpedal.gui.GUIFactory swingGUI=((org.jpedal.examples.viewer.gui.SwingGUI)acro.getActionHandler().getPDFDecoder().getExternalHandler(Options.GUIContainer));
// if(swingGUI!=null) {
// swingGUI.rescanPdfLayers();
//}
//repaint pdf decoder to make sure the layers are repainted
//((org.jpedal.PdfDecoder)acro.getActionHandler().getPDFDecoder()).repaint();//good idea mark
} catch (final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
}
//run through all forms and see if they have changed
//acro.updateChangedForms();
//always set the javascript flag to false so that the execute calling thread can resume from its endless loop.
javascriptRunning = false;
}
}
/** replace javascript variables with our own so rhino can easily identify them and pass excution over to us */
private static String preParseCode(String script) {
final String[] searchFor = {"= (\"%.2f\",","this.ADBE"," getField(","\ngetField(","\rgetField(",
"(getField(","this.getField(","this.resetForm(","this.pageNum"," this.getOCGs(","\nthis.getOCGs(",
"\rthis.getOCGs("," getOCGs(","\ngetOCGs(","\rgetOCGs(",".state="};
final String[] replaceWith = {"= util.z(\"%.2f\",","ADBE"," acro.getField(","\nacro.getField(","\racro.getField(",
"(acro.getField(","acro.getField(","acro.resetForm(","acro.pageNum"," layers.getOCGs(","\nlayers.getOCGs(",
"\rlayers.getOCGs("," layers.getOCGs(","\nlayers.getOCGs(","\rlayers.getOCGs(","\rlayers.getOCGs("};
for(int i=0;iindexStart)) {
indexStart = indextmp;
}
buf.append(script.substring(0,indexStart+1));
//find the end of the string
int speech = script.indexOf('\"',indexs);
speech = script.indexOf('\"',speech+1);
while(script.charAt(speech-1)=='\\') {
speech = script.indexOf('\"', speech);
}
//make sure there is an argument ',' after it
final int startArgs = script.indexOf(',',speech);
final int endArgs = script.indexOf(')',startArgs);
//setup arguments string so we can setup in javascript
final String arguments = script.substring(startArgs+1, endArgs);
if(arguments.equals("printfArgs")) {
break printf;
}
final StringTokenizer tok = new StringTokenizer(arguments,", ");
//create array in javascript code
buf.append("var printfArgs=new Array();\n");
//add arguments to the array
int i=0;
while(tok.hasMoreTokens()){
buf.append("printfArgs[");
buf.append(i++);
buf.append("]=");
buf.append(tok.nextToken());
buf.append(";\n");
}
//add printf command with new array as argument
buf.append(script.substring(indexStart+1, startArgs+1));
buf.append("printfArgs");
buf.append(script.substring(endArgs));
script = buf.toString();
}
script = checkAndReplaceCode("event.value=AFMakeNumber(acro.getField(\"sum\").value)(8)","", script);
script = checkAndReplaceCode("calculate = false", "calculate = 0", script);
script = checkAndReplaceCode("calculate = true", "calculate = 1", script);
script = checkAndReplaceCode("calculate=false", "calculate=0", script);
script = checkAndReplaceCode("calculate=true", "calculate=1", script);
return script;
}
// private static String checkAndAddParentToKids(String script, AcroRenderer acro){
//
// String startCode = "acro.getField(\"";
// //find start of GetField statement
// int startIndex = script.indexOf(startCode);
// if(startIndex!=-1){
// int startNameInd = startIndex+15;
// int endNameInd = script.indexOf("\")", startIndex);
// int endIndex = script.indexOf(';', startIndex)+1;
//
// //get the name its calling
// String name = script.substring(startNameInd, endNameInd);
// //if it ends with a . then we have to replace with all kids.
// if(name.endsWith(".")){
//
// //removed by Mark 16042013
// String[] allFieldNames = acro.getChildNames(name);
//
// // add start of script
// StringBuilder buf = new StringBuilder();
// buf.append(script.substring(0,startIndex));
//
// // add modified script with all fieldnames
// for (int j = 0; j < allFieldNames.length; j++) {
// if(j>0)
// buf.append('\n');
// buf.append(startCode);
// buf.append(allFieldNames[j]);
// buf.append(script.substring(endNameInd,endIndex));
// }
//
// // add end of script
// buf.append(script.substring(endIndex,script.length()));
// script = buf.toString();
// }
// }
// return script;
// }
/** replace the searchFor string with the replaceWith string within the code specified */
private static String checkAndReplaceCode(final String searchFor, final String replaceWith,String script) {
final int index = script.indexOf(searchFor);
if(index!=-1){
String buf = script.substring(0, index) +
replaceWith +
checkAndReplaceCode(searchFor, replaceWith, script.substring(index + searchFor.length(), script.length()));
script = buf;
}
return script;
}
/** add the javascript standard execution objects, that acrobat app has defined functions for. */
private void addStdObject(final AcroRenderer acro) {
Object objToJS = org.mozilla.javascript.Context.javaToJS( new JpedalDefaultJavascript(scope,cx), scope );
//util added for jpedal use ONLY
org.mozilla.javascript.ScriptableObject.putProperty( scope, "util", objToJS );
// app added so that methods difined within adobes javascript can be implemented
org.mozilla.javascript.ScriptableObject.putProperty( scope, "app", objToJS );
final org.mozilla.javascript.Scriptable globalObj = cx.newObject(scope);
//global is added to allow javascript to define and create its own variables
org.mozilla.javascript.ScriptableObject.putProperty( scope, "global", globalObj );
final org.mozilla.javascript.Scriptable ADBE = cx.newObject(scope);
//global is added to allow javascript to define and create its own variables
org.mozilla.javascript.ScriptableObject.putProperty( scope, "ADBE", ADBE );
objToJS = org.mozilla.javascript.Context.javaToJS( new DisplayJavascriptActions(), scope );
// display adds constant definitions of values
org.mozilla.javascript.ScriptableObject.putProperty( scope, "display", objToJS );
// color adds default constant colors.
org.mozilla.javascript.ScriptableObject.putProperty( scope, "color", objToJS );
// add layers to rhino
final PdfLayerList layerList = acro.getActionHandler().getLayerHandler();
if(layerList!=null){
objToJS = org.mozilla.javascript.Context.javaToJS( layerList, scope );
//not working yet.
org.mozilla.javascript.ScriptableObject.putProperty( scope, "layers", objToJS );//CHRIS @javascript
}
// add a wrapper for accessing the forms etc
objToJS = org.mozilla.javascript.Context.javaToJS( acro, scope );
//acro added to replace 'this' and to allow access to form objects via the acroRenderer
org.mozilla.javascript.ScriptableObject.putProperty( scope, "acro", objToJS );
}
/** add functions to the javascript code to be executed within rhino */
@Override
public int addCode(final String value) {
functions += preParseCode(value);
return 0;
}
/** typeToReturn
* 0 = String,
* 1 = Double,
* -1 = guess
*/
public org.mozilla.javascript.Scriptable generateJStype(final String textString, final boolean returnAsString){
if(returnAsString){
return cx.newObject(scope, "String", new Object[] { textString });
}else {
if(textString!=null && !textString.isEmpty() && StringUtils.isNumber(textString) &&
!(textString.length()==1 && textString.indexOf('.')!=-1)){//to stop double trying to figure out "."
final Double retNum = Double.valueOf(textString);
return cx.newObject(scope, "Number", new Object[] { retNum });
}else {
return cx.newObject(scope, "String", new Object[] { textString });
}
}
}
/**
* execute javascript and reset forms values
*/
@Override
public int execute(final FormObject form, final int type, final String code, final int eventType, final char keyPressed) {
int messageCode;
final String js = code;
//convert into args array
final String[] args= JSFunction.convertToArray(js);
final String command=args[0];
if(command.startsWith("AF")) {
messageCode = handleAFCommands(form, command, js, args, eventType, keyPressed);
} else {
executeFunctions(js, form, acro);
messageCode = ActionHandler.VALUESCHANGED;
}
if(type == PdfDictionary.F) {
calcualteEvent();
messageCode = ActionHandler.VALUESCHANGED;
}
return messageCode;
}
private void calcualteEvent() {
// System.out.println("CALC");
final List obs = acro.getCompData().getFormComponents(null, ReturnValues.FORMOBJECTS_FROM_REF, -1);
final Object[] formObjects = obs.toArray();
for(final Object o : formObjects) {
final FormObject formObject = (FormObject) o;
final String ref = formObject.getObjectRefAsString();
final String name = formObject.getTextStreamValue(PdfDictionary.T);
final String command = JSObj.getJavascriptCommand( (name != null ? name : ref), PdfDictionary.C2);
if(command != null) {
// System.out.println(command);
execute(formObject, PdfDictionary.C2, command, ActionHandler.FOCUS_EVENT, ' ');
}
}
}
}