Opening dialog boxes from xslt - It works!

Post here questions and problems related to oXygen frameworks/document types.
Patrik
Posts: 228
Location: Hamburg/Germany

Opening dialog boxes from xslt - It works!

Tue May 19, 2015 2:43 pm

Hi,

I lately implmeented a custom saxon instruction that allows to create a a message box or option dialog from within an XSLT.

Since it turned out to be pretty easy to implement and very useful within my framework, I just wanted to share this idea - and the joy over oxygen, that even this is possible.

It might as well be a nice standard extension for oxygen. It allows meto implement more transformations and user actions completely in xslt without having to compile myjava code and restart oxygen after modifications...

(If someone should be interested - I will gladly share my code.)

Regards,

Patrik
Radu
Posts: 5873

Re: Opening dialog boxes from xslt - It works!

Wed May 20, 2015 10:31 am

Hi Patrik,

A predefined XSLTOperation has a script parameter which usually contains its XSLT code.
The script value can also contain $ask editor variables which should get expanded before the XSLT script is run:

http://www.oxygenxml.com/doc/ug-oxygen/#topics/editor-variables.html

so that would be another possibility of doing things.

Your choice is also interesting and flexible. It would be great if you could maybe post a small sequence of steps as an email on the Oxygen Users List, maybe others would find it useful as well.
But you would still need to compile Java code when you make changes to the functionality of the shown dialog.

Another possibility I would see would be if the Java extension function which gets called from the XSLT would use the Rhyno libraries which come bundled with Oxygen to call Javascript code which can also show swing-based dialogs or call API on the Oxygen side. So all the functionality of showing the dialog and returning the result would be in Javascript without the need to compile Java code.

In Oxygen 17 we added a new default bundled Author operation called JSOperation:

http://www.oxygenxml.com/doc/ug-oxygen/#topics/dg-default-author-operations.html

It has an implementation which takes Javascript code as a parameter and runs it using the Rhyno library:

Code: Select all

  /**
   * @see ro.sync.ecss.extensions.api.AuthorOperation#doOperation(ro.sync.ecss.extensions.api.AuthorAccess, ro.sync.ecss.extensions.api.ArgumentsMap)
   */
  @Override
  public void doOperation(AuthorAccess authorAccess, ArgumentsMap args)
      throws IllegalArgumentException, AuthorOperationException {
    Object scriptValue = args.getArgumentValue(ARGUMENT_SCRIPT);

    if (!(scriptValue instanceof String)) {
      throw new IllegalArgumentException("The argument \"script\" was not defined as a string object!");
    }

    // Get the Javascript script.
    String script = (String)scriptValue;
    //Use it
    org.mozilla.javascript.Context cx = org.mozilla.javascript.Context.enter();
    try{
      org.mozilla.javascript.Scriptable globalScope = cx.initStandardObjects();
     
      String baseLocation = authorAccess.getUtilAccess().expandEditorVariables(EditorVariables.FRAMEWORK_URL,
          authorAccess.getEditorAccess().getEditorLocation());
      if(baseLocation == null || baseLocation.contains(EditorVariables.FRAMEWORK_URL)) {
        //This means the framework is internal, we cannot set it as a base system ID to the source.
        //But let's use instead the place from where the XML was loaded.
        baseLocation = authorAccess.getEditorAccess().getEditorLocation().toString();
      }
     
      //Also evaluate a common script in a common location
      URL commonScriptURL = new URL(new URL(baseLocation), "commons.js");
      InputStream commonIS = null;
      try {
        commonIS = commonScriptURL.openStream();
        cx.evaluateReader(globalScope, new InputStreamReader(commonIS, "UTF8"), commonScriptURL.toExternalForm(), 1, null);
      } catch (IOException e) {
        //Ignore
      } finally{
        if(commonIS != null){
          try {
            commonIS.close();
          } catch (IOException e) {
            //Ignore
          }
        }
      }
      //The current script UTL
      URL dummyScriptURL = new URL(new URL(baseLocation), "currentScript.js");
      // Now evaluate the string we've collected. We'll ignore the result.
      cx.evaluateString(globalScope, script, dummyScriptURL.toString(), 1, null);
     
      //Aliases for author access
      Object wrappedDispatcher =  org.mozilla.javascript.Context.javaToJS(authorAccess, globalScope);
      org.mozilla.javascript.ScriptableObject.defineProperty(globalScope, "authorAccess", wrappedDispatcher, org.mozilla.javascript.ScriptableObject.CONST);
     
      Object fObj = globalScope.get("doOperation", globalScope);
      org.mozilla.javascript.Function f = (org.mozilla.javascript.Function)fObj;
      f.call(cx, globalScope, globalScope, new Object[0]);
    } catch(org.mozilla.javascript.RhinoException ex){
      throw new AuthorOperationException("Could not evaluate script: " + ex.getMessage(), ex);
    } catch (MalformedURLException e) {
      throw new AuthorOperationException("Could not evaluate script: " + e.getMessage(), e);
    } finally{
      org.mozilla.javascript.Context.exit();
    }
  }


That Javascript code is run in the Oxygen class loader so it can call directly and interact with our API like for example:

Code: Select all

function doOperation(){
        Packages.ro.sync.ecss.dita.DITAAccess.insertImage(authorAccess, 'test.png');
}


Also it can create Swing dialogs, show them and return a certain value:

Code: Select all

var frame = new Packages.javax.swing.JFrame();
 frame.setSize(100, 100);
 frame.setVisible(true);


Although this Javascript code approach is a little bit strange I find this useful because before this JSOperation if one of our clients needed to have a custom Author operation, he needed to know Java, he needs to compile the code even if we gave him the code of the custom operation.
With this new operation I can give directly a small Javascript code and tell him to create the operation having as a parameter the Javascript code.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
Patrik
Posts: 228
Location: Hamburg/Germany

Re: Opening dialog boxes from xslt - It works!

Wed May 20, 2015 12:07 pm

Just for completeness:

One use-cases would be a transformation that potentially overwrites an existing file and asks the user before doing so (if the file already exists). The code looks like this:

Code: Select all

<xsl:if test="java:fileExists($myFile)">
   <xsl:variable name="userDecision" as="xs:integer">
      <gui:option-dialog title="Warning" options="('Continue', 'Abort')" default="1">
         <xsl:text/>The file '<xsl:value-of select="$myFile"/>' already exists and will be overwritten.<xsl:text/>
      </gui:option-dialog>
   </xsl:variable>
   <xsl:if test="$userDecision = 1">
      <xsl:message terminate="yes">Aborted on user request</xsl:message>
   </xsl:if>
</xsl:if>


BTW: From saxon-xslt you can already call java methods without any custom instruction: http://www.saxonica.com/documentation/# ... /functions.
But it is no fun to implement more complex statements this way!

Regards,
Patrik
Patrik
Posts: 228
Location: Hamburg/Germany

Re: Opening dialog boxes from xslt - It works!

Sat Oct 24, 2015 11:26 am

Hi,

I just uploaded my implementation together with the compiled java library and two samples to github...
https://github.com/dita-semia/XsltGui/releases

Patrik
Radu
Posts: 5873

Re: Opening dialog boxes from xslt - It works!

Mon Oct 26, 2015 10:32 am

Hi Patrik,

Thanks for the link to your project, I added it to a list of public hosted Oxygen extensions that is hosted for now on our blog:

http://blog.oxygenxml.com/2014/11/public-hosted-oxygen-plugin-and.html

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com

Return to “SDK-API, Frameworks - Document Types”

Who is online

Users browsing this forum: bk-one and 2 guests