Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converting [\Svg] Tyxml_svg.elt to [\Svg] Js_of_ocaml_tyxml.Tyxml_js.Html.elt #323

Open
Mbodin opened this issue Nov 24, 2023 · 6 comments

Comments

@Mbodin
Copy link
Contributor

Mbodin commented Nov 24, 2023

I'm easily lost in the documentation of TyXML, and in particular I would really appreciate some kind of diagram showing all the conversions that can be invoked between the different types of TyXML/Js_of_ocaml/etc. So it is possible that this issue is just a documentation issue. It might also be an issue belonging to another project (maybe Js_of_ocaml_tyxml?), as I'm missing subtleties between the separation of these projects.

Here is my issue: the core of my program produces a representation of an SVG file in the type [`Svg] Tyxml_svg.elt (which is the type that comes out when I use the let%svg syntax with Tyxml openned). I have two interfaces for it, on terminal and web.

In the terminal version, I need to produce a SVG file from it. This is done easily enough with Format.fprintf fmt "%a@." (Tyxml.Svg.pp ~indent:true ()) svg.

In the web version, I use the module Tyxml_js of Js_of_ocaml_tyxml to produce my webpage, as this type is easy to manipulate and to convert into an actual webpage using one of the To_dom.of_* functions.

The issue is that I can't insert as-is my image svg : [`Svg] Tyxml_svg.elt as these types are not compatible.

I suspect that underneath, they both use Xml.elt as a representation, but that this type constraint is hidden in the module definition of Tyxml_svg (for good reasons, as not all Xml.elt are valid SVG elements). I however have the impression that no conversion function has been provided before hiding the elt type under the module signature, so we are stuck within Tyxml_svg.elt.

Maybe I am doing things in the wrong way here: do tell me if I should use other types for this usage ☺ TyXML is really useful for building typesafe SVG documents and webpages, but I still have trouble converting from types to types (which is why a recapitulating diagram would really help ☺).

When looking for similar issues online, we find several instances of people getting confused on how to incorporate SVG documents within HTML in a typed way (e.g. https://fa.caml.narkive.com/0g4ImGGn/caml-list-js-of-ocaml-and-svg ), and I think that it would be really useful to clarify how to do it properly with a small example.

@Mbodin Mbodin changed the title Converting `[Svg] Tyxml_svg.elt to [Svg] Js_of_ocaml_tyxml.Tyxml_js.Html.elt` Converting [\Svg] Tyxml_svg.elt to [\Svg] Js_of_ocaml_tyxml.Tyxml_js.Html.elt Nov 27, 2023
@Mbodin
Copy link
Contributor Author

Mbodin commented Nov 27, 2023

As I understand, one of the issues here is that TyXML is meant to produce well-typed HTML/SVG trees from an implementation module, not necessarily to convert these trees from implementations to implementations. As such Tyxml_js kind of lives in a different realm than base Tyxml and are not meant to be converted from each other (even though they are based on very similar types). Am I wrong on that?

This would mean that for my usage, I would need to parameterise the core of my program by a module Svg : Svg_sigs.Make(Xml).T. That way, in the console implementation I can instantiate it with Tyxml_svg, and in the web implementation with Tyxml_js.Svg. It will have to be trickier than that as in the web interface I also produce an SVG file which is meant to be downloaded and not inserted into the DOM: I would thus have to instantiate both versions of the module from the core in the web version. It seems a bit heavy, but I guess it's doable. I would welcome any more direct approach with some type conversion 🤗​

The other issue I am facing here is that I don't understand how to correctly convert a Tyxml.Svg.doc = [`Svg] Tyxml_svg.elt into a [`Svg] Tyxml.Html.elt (in order to insert it into a webpage using TyXML). I did find the functions Svg.doc_toelt and Html.tot that seem to do exactly this, but from the comments it seems that they are unsafe? I'm feeling that a more direct conversion function is lacking.

@Drup
Copy link
Member

Drup commented Nov 27, 2023

For your second, subsidiary, question, the function you are looking for is Html.svg, which allow to insert an svg document inside the HTML "<svg>" element .

For the more general question: there are no conversion between Tyxml.Svg and Tyxml_js.Svg, because it's actually not the same underlying representation at all (The js_of_ocaml one is a virtual-dom like thingy). It's probably feasible to convert in the forward direction (by converting to Tyxml.Xml.elt and interpreting that), but it's a bit tricky. Your idea of functorizing your program over the Svg module is right. We don't have very good documentation for this, but it's a supported use-case that shouldn't be too hard to figure out.
To recover the svg file in the web version, just insert the svg element in a DOM node and print that (with the right DOM method), you'll get a valid svg file, no need to instantiate the functor twice.

@Mbodin
Copy link
Contributor Author

Mbodin commented Nov 27, 2023

Thanks for the answer!

After a lot of thousand-lines-long error messages (which is typical of functor error with very large module signature), I think that I got it right. The wrap type was kind of tricky to understand within the error messages: a small example to understand this would be nice ☺

Here is the signature that I finally got:

module Image (Xml : Xml_sigs.T with module W = Xml_wrap.NoWrap) (Svg : Svg_sigs.Make(Xml).T) : sig ... end

Is it the expected one?

@Mbodin
Copy link
Contributor Author

Mbodin commented Nov 27, 2023

Thanks for the suggestion to use the DOM functions to convert the object into a string! But it seems that it won't work in my setting: let me share some thoughts here ☺

There is no To_dom.of_svg function in Tyxml_js: what I did was to insert it within a <span> (which is fine as I wanted to assign a CSS class to it anyway), and call the To_dom.of_span with this object.

let%html displayed_img =
  "<span class = 'output'>"
    [Html.svg [img]]
  "</span>" in
let displayed_img = To_dom.of_span displayed_img in
Dom.appendChild output displayed_img

This works great! The SVG appears in the webpage. Thanks!

I'm surprised that it needed a list by the way: I would have expected to write Html.svg img. But that's fine ☺

But I still have to convert it to SVG later on. I don't have a handle to the DOM equivalent of Html.svg [img]. I can use displayed_img##.innerHTML, but this leads to another issue: this string contains quotes, and it seems that TyXML doesn't escape them well in the href attribute. So the following snippet produces an invalid webpage. This should probably be reported as a separate issue as it should be easy to build with a minimal example.

let file = Js.to_string displayed_img##.innerHTML in
let url = "data:image/svg+xml;utf8," ^ file in
let%html button = "<a href = "url" download = 'file.svg'></a>" in
(* ... *)

Another option would be to use something like below, but in addition to be unsafe, it produces the string [object SVGSVGElement] instead of the representation.

match Js.Opt.to_option displayed_img##.firstChild with
| None -> assert false
| Some img -> (Js.Unsafe.coerce img : <toString : Js.js_string Js.t Js.meth> Js.t)##toString

I guess a proper option would be to use XMLSerializer, but I haven't seen it in Js_of_ocaml. It all feels a bit complicated when I actually have access to a representation of the SVG image within TyXML! I feel that instantiating the module twice is still the best option here. Or did you have something else in mind?

@Drup
Copy link
Member

Drup commented Nov 27, 2023

Here is the signature that I finally got:

module Image (Xml : Xml_sigs.T with module W = Xml_wrap.NoWrap) (Svg : Svg_sigs.Make(Xml).T) : sig ... end

Is it the expected one?

That looks about right, indeed ! I agree the wrap module is a bit difficult to wrap (!) your head around. You are poking at the innards a bit here, and such usage are well supported, but not well documented.

There is no To_dom.of_svg function in Tyxml_js

Indeed, it seems there are some functions missing. Note that you can always manufacture your own To_dom.of_thingy function by going through of_node and adding some Js.Unsafe.coerce. That's how they are implemented anyway.

In general, it seems all the other remarks stem to some partial supports for SVG, which I must admit are not as well seasoned at the other parts of the Tyxml/Js_of_ocaml APIs.

@Mbodin
Copy link
Contributor Author

Mbodin commented Dec 8, 2023

That was a side question here, but I tried to investigate on this:

I can use displayed_img##.innerHTML, but this leads to another issue: this string contains quotes, and it seems that TyXML doesn't escape them well in the href attribute. So the following snippet produces an invalid webpage. This should probably be reported as a separate issue as it should be easy to build with a minimal example.

I tried to build a minimal example for this, but it seems that I can't reproduce the issue. Maybe I'm misunderstanding something there. Here is my code:

open Js_of_ocaml
open Tyxml
open Js_of_ocaml_tyxml
open Tyxml_js

let file = {|This is a test file with quotes and a backslash \'".|}

let url = "data:text/plain;charset=UTF-8," ^ file

let%html p =
  {|
      <p>
        <a href = |}url{| download = "test.txt">Test</a>
      </p>
  |}

let () =
  let p = To_dom.of_p p in
  Dom.appendChild Dom_html.document##.body p ;
  let pre = To_dom.of_pre [%html "<pre>" [Html.txt (Js.to_string p##.innerHTML)] "</pre>" ] in
  Dom.appendChild Dom_html.document##.body pre

The <p> element does look weird on Firefox with an unescaped double quote, but the <pre> element looks just fine, displaying <a href="data:text/plain;charset=UTF-8,This is a test file with quotes and a backslash \'&quot;." download="test.txt">Test</a> (in other words, the double quote was replaced by &quot;). Browsers (tested on Firefox and Chrome) seem to deal fine with it, even though the href attribute of <p> is unescaped (but correctly marked as the content of the string in the inspector). So maybe that's not an issue after all ☺

Just to double-check, using base Tyxml instead of Tyxml_js and outputting the output on a terminal does escape the quotes. So this looks fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants