javascriptnode.jssmart-mobile-studio

Need help importing NodeJS-Module "htmlparser2" in SmartMobileStudio


I tried to use the htmlparser2 module (installed via npm install htmlparser2) in SmartMobileStudio. The module itself works well in direct javascript (using a slightly changed sample from htmlparser2's Homepage):

var htmlparser = require("htmlparser2");
var parser = new htmlparser.Parser({
    onopentag: function(name, attribs){
        console.log(name);
        console.log(attribs);
    }
});
parser.write("<img src='image1.jpg'>");
parser.end();

Using SMS (2.0.0.9 Beta) I tried to import the module like this:

unit NodeJS.htmlparser2;

interface

uses
  NodeJS.Core,
  NodeJS.events;

type
  TOnTag = procedure(name: string; attribs: Variant);
  TOnText = procedure(text: string);

  JParser = class external(NodeJS.events.JNodeEventEmitter)
    procedure write(s: string);
    procedure &end;
  end;

  Jhtmlparser_Exports = class external
  public
    function Parser(onopentag: TOnTag; ontext: TOnText; onclosetag: TOnTag): JParser;
  end;

function htmlparser2: Jhtmlparser_Exports;

implementation

function htmlparser2: Jhtmlparser_Exports;
begin
  result := Jhtmlparser_Exports( require("htmlparser2") );
end;

end.

I changed the project generated by the Node.js-New-Project-Template like this:

[...]

procedure TServer.Run;
begin
  var htmlparser := NodeJS.htmlparser2.htmlparser2;

  var parser := htmlparser.Parser(
   procedure (Name: string; Attribs: Variant)
   begin
     console_.log([Name]);
     console_.log([Attribs]);
   end,
   nil,
   nil);

  parser.write("<img src='image1.jpg'>");
  parser.end();
end;

The Problem is that the emitted code is not correct, but nearly:

[...]
parser = htmlparser.Parser(function (Name$3, Attribs) {
   console_().log([Name$3].slice());
   console_().log([Attribs].slice());
},null,null);
[...]

This works:

[...]
parser = new htmlparser.Parser({onopentag: function (Name$3, Attribs) {
   console_().log([Name$3].slice());
   console_().log([Attribs].slice());
}});
[...]

The difference is the "new" keyword and the named event callback "onopentag". What do I need to write to generate working js code?


Solution

  • Because a class can't be "exported" in pascal like you can with javascript and requirejs/nodejs, we need to use an external class trick: we can set some code for the external class name. Second, we need to supply an "options" object (more verbose but needed for type safety).

    JParserOptions = class
      onopentag : TOnTag;
      ontext    : TOnText;
      onclosetag: TOnTag
    end;
    
    JParser = class external '(htmlparser2()).Parser' (NodeJS.events.JNodeEventEmitter)
      constructor Create(options: JParserOptions);
    
      procedure write(s: string);
      procedure &end;
    end;
    

    We can use this as follows:

    var options = JParserOptions.Create;
    options.onopentag := procedure (Name: string; Attribs: Variant)
      begin
        console_.log([Name]);
        console_.log([Attribs]);
      end;
    
    var parser := new JParser(options);