mscgenjs/mscgenjs-core

AST to SVG api?

Closed this issue · 7 comments

I've been digging in to some of the internals, but I'm having a hard time following in some places. It looks like many of the low-level APIs assume the DOM is present - what I'm looking for is something that just receives the AST, and returns SVG... did I just miss something obvious?

My use case is that I need to dynamically render multiple code blocks (the elements don't have ids), and the page content gets updated via JS, so I need to update periodically. I was hoping to be able to do something like:

    function update() {
        document.querySelectorAll('.lang-msgenny').forEach(function(el) {
            var ast = msgennyparser.parse(el.textContent);
            el.innerHTML = renderer.astToSvg(ast);
        });
    }

I think my issue is that (from what I can tell) the renderer expects an element id so it can fetch the element to render to, and what I want is to either pass in the target element directly, or just receive the SVG output so I can do something else with it.

I've been looking at this a little too long though, I may have overlooked something.

Additionally

  • for the renderMsc function 'ast' (or 'json') is an acceptable input type, so if you want/ need to pass an ast you can (although I don't think you'll need it in your scenario):
mscgen.renderMsc(
    yourAST, 
    {
        inputType: "ast", 
        elementId: "idOfAnExistingElement"
    }
);
  • if you want the svg; renderMsc accepts a callback; the second parameter will contain the svg (a sample is in the readme).
  • I acknowledge the code is hard to follow in some places. Each time I touch a piece I try to make it a little simpler/ more readable, but I don't revisit
  • about reliance on the DOM; true. I'd like to get rid of that, but in a few places the code really needs to measure how big things are (to prevent overlapping and to correctly size boxy things)- and I haven't found a good way to do that without the DOM. Suggestions welcome :-).

Thanks a bunch for the reply, much appreciated! I'll keep playing and see if I can work around it, starting with the example you mention in mscgen-inpage.

The hard to follow comment was mostly related to me being tired :) This is a very cool project, thank you for your work on it.

As for the DOM dependency, my only thought at the moment, and it might not work (not familiar enough yet w/ the internals), would be to pass the target height/width in explicitly, and maybe fall back to checking the DOM if it was not specified?

I wonder if Viz.js has a similar situation, and what it is doing... but given that it's built via emscripten, I'm not sure where to start looking.

So, I got somewhere, but it's truly hacky. :) The project that I'm in right now doesn't have a build system of any sort, so I resorted to forking/hacking mscgen-inpage. I built a version of that which exposes mscgennyparser and mscrender on the window object, and comments out the default behavior when the script loads.

Then I added my code to look for the .lang-msgenny elements, and parse and render. I had to wipe out the element's innerHTML first, in order to get rid of the original text - I want the rendered SVG to replace the original text, but it looks to be added as a child element of the specified ID.

I keep a map of already parsed ids, because there's an edge case where my update method may get called twice on the same page. Anyway, that's about it, when the page content changes, my update runs again, and I see the rendered SVGs in place of the original msgenny code blocks.

    const els = document.querySelectorAll('.lang-msgenny');
    for (var i in els) {
      let el = els[i];
      let id = 'lang-msgenny'+'-'+i;
      if (this.parsedMscgen[id] === true) {
        continue;
      }

      this.parsedMscgen[id] = true;
      el.id = id;
      const ast = window.msc.msgennyparser.parse(el.textContent);
      el.innerHTML = "";
      window.msc.mscrender.renderAST(ast, null, id, window);
    }

Anyway, this works for now, but I also get an error that I'm having a hard time explaining:

TypeError: Cannot read property 'charAt' of undefined
    at peg$parsewhitespace (mscgen-inpage.js:7157)
    at peg$parse_ (mscgen-inpage.js:7396)
    at peg$parseprogram (mscgen-inpage.js:4948)
    at Object.peg$parse [as parse] (mscgen-inpage.js:7904)
    at VueComponent.updated (app.js:233)
    at callHook (vue.js:2669)
    at callUpdatedHooks (vue.js:2769)
    at flushSchedulerQueue (vue.js:2754)
    at Array.<anonymous> (vue.js:707)
    at nextTickHandler (vue.js:654)

Looks like the parser is getting something it doesn't like... but the SVGs are still rendering, so I dunno.

I tracked down that error I detailed above for what it's worth - it was my fault of course. There was an edge case where the node got double parsed again, and el.textContent was returning the svg text... so, disregard. :)

I'll go ahead and close as there's not really an "issue". @sverweij I appreciate the responses a ton, and thank you for this library.