-1

A contractor created a fairly complex XSLT for a client of mine. It works great in various transform testing tools, using real data from our application. But Nokogiri barfs on it during transform. With $VERBOSE and $DEBUG set, I still get the uninformative:

Exception `RuntimeError' at nokogiri/XsltStylesheet.java:231 - java.lang.NullPointerException

So I thought I would create a custom Nokogiri Gem with some debug code in it, using this in my Gemfile:

gem 'nokogiri', :git => 'ssh://git@192.168.185.65:7999/bssc/nokogiri.git'

That doesn't work because of a versioning issue I don't understand how to resolve:

Source does not contain any versions of 'nokogiri java'

So at this point I thought I'd reach out and ask the community for a better idea. I'm sure it's something in the stylesheet that Nokogiri is not happy with, but it is difficult to locate because of the complexity of the XSLT.

Suggestions?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
jpa57
  • 587
  • 4
  • 11
  • Are you using some private repository? That's a non-routing IP in your spec. If you're really getting deep into XSLT you may have exposed a bug in Nokogiri. If you can make a minimal test case, could make for an interesting bug report. – tadman Jul 06 '17 at 00:47
  • The easiest way to debug a gem is to edit the source where it's installed, then uninstall/reinstall it to wipe out your debugging junk later. The second easiest is to `git clone` the repository and use `gem 'nokogiri', path: '...path/to/repo'` to use a local copy in another location. This requires a `.gemspec` file that's valid, though, and not all repositories do this. – tadman Jul 06 '17 at 00:48
  • Thanks @tadman, that URL is our local 'bitbucket server' where I pushed a copy of the source after modifying a few lines of code near the exception. We've had trouble trying to distill to a minimal test case and think I need to instrument the code to get closer. Nokogiri does not seem to have a .gemspec; perhaps that's the source of the version issue? – jpa57 Jul 06 '17 at 01:03
  • If there's no `.gemspec` then you'll need to work with the locally installed copy, or a dupe of it that does. – tadman Jul 06 '17 at 02:03
  • That's the ticket. Thanks. I can edit the source under ~/.rvm, and then run their build_all script. I had to modify the build_all script to get down to the essence since it's otherwise checking for Docker in some way that my system doesn't match. I'm getting instrumentation now! – jpa57 Jul 06 '17 at 02:54

2 Answers2

2

... have done some Java native ext lifting with Nokogiri (performance/cleanup updates in 1.7/1.8)

it depends whether you have a trace that points to a reasonable location or not, try jruby -Xbacktrace.style=raw ... if that does not reveal valuable insight you might need to get into building the gem yourself (there's a rake task for it just make sure you're doing it under JRuby).

you can not use Bundler with :git since the .gemspec is generated depending on the Ruby platform, thus you will need to build and manually gem install (adjust the version and set it with Bundler so you know which one you're using).

good luck!

kares
  • 7,076
  • 1
  • 28
  • 38
0

I'm not sure this is the best way, but editing the source in my RVM environment, followed by a minimized build_all, allowed me to remove the exception handling. My modified XsltStylesheet.java :

    @JRubyMethod
    public IRubyObject serialize(ThreadContext context, IRubyObject doc) throws IOException, TransformerException {
        XmlDocument xmlDoc = (XmlDocument) doc;
        TransformerImpl transformer = (TransformerImpl) this.sheet.newTransformer();
        ByteArrayOutputStream writer = new ByteArrayOutputStream();
        StreamResult streamResult = new StreamResult(writer);
        SerializationHandler serializationHandler = transformer.createSerializationHandler(streamResult);
        serializationHandler.serialize(xmlDoc.getNode());
        return context.getRuntime().newString(writer.toString());
    }

    @JRubyMethod(rest = true, required=1, optional=2)
    public IRubyObject transform(ThreadContext context, IRubyObject[] args) throws TransformerException, IOException {
        Ruby runtime = context.getRuntime();
System.out.println("in transform");
        argumentTypeCheck(runtime, args[0]);
System.out.println("before listener");

        // NokogiriXsltErrorListener elistener = new NokogiriXsltErrorListener();
    System.out.println("before dom");

        DOMSource domSource = new DOMSource(((XmlDocument) args[0]).getDocument());
        final DOMResult result; String stringResult = null;
    System.out.println("try transform");

            result = tryXsltTransformation(context, args, domSource, null); // DOMResult





        if (stringResult == null) {
            return createDocumentFromDomResult(context, runtime, result);
        } else {
            return createDocumentFromString(context, runtime, stringResult);
        }
    }

    private DOMResult tryXsltTransformation(ThreadContext context, IRubyObject[] args, DOMSource domSource, NokogiriXsltErrorListener elistener) throws TransformerException {
        Transformer transf = sheet.newTransformer();
        transf.reset();
        // transf.setErrorListener(elistener);
        if (args.length > 1) {
            addParametersToTransformer(context, transf, args[1]);
        }

        DOMResult result = new DOMResult();
        transf.transform(domSource, result);
        return result;
    }

Here is my build-all script:

#! /usr/bin/env bash
#
#  script to build gems for all relevant platforms:
#  - MRI et al (standard gem)
#  - windows (x86-mingw32 and x64-mingw32)
#  - jruby
#

# Load RVM into a shell session *as a function*
if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
    source "$HOME/.rvm/scripts/rvm"
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
    source "/usr/local/rvm/scripts/rvm"
else
    echo "ERROR: An RVM installation was not found.\n"
fi

set -o errexit

rm -rf tmp pkg
bundle exec rake clean clobber

# holding pen
rm -rf gems
mkdir -p gems

# windows

# MRI

# jruby
bundle exec rake clean clobber
bundle exec rake generate

gem install bundler --conservative
bundle install --quiet --local || bundle install
bundle exec rake gem
cp -v pkg/nokogiri*java.gem gems

This produced the output:

...
try transform
file:///Users/jeff/Documents/BlueSageSource/plus/dummy.xsl; Line #0; Column #0; org.apache.xpath.objects.XRTreeFrag cannot be cast to org.apache.xpath.objects.XNodeSet
file:///Users/jeff/Documents/BlueSageSource/plus/dummy.xsl; Line #0; Column #0; java.lang.NullPointerException

The 'cannot be cast' message led me to other information that indicates that Nokogiri does not support XSLT 2. This is disappointing, not so much because of the limitation, but because it is not well documented, and failing with a null pointer exception is at best unfriendly. I think we have enough information to create a minimal failing case to file as a request for enhancement.

references:

Nokogiri does not intend to support XSLT 2

Nokogiri does not support XSLT 2

jpa57
  • 587
  • 4
  • 11