Robert B.: Beitrag fürs Wiki/Blog?

Hallo,

ich habe mir seit längerem Gedanken gemacht, wie man direkt im HTML notiertes SVG [1] speichern bzw. zum Speichern anbieten kann. Bei externen Inhalten (nicht nur Grafiken) habe ich im Kontextmenü immer den Punkt Datei Speichern, bei

<figure>
  <svg width="" height="" >
    <title>Titel der Grafik</title>
    <!-- … Elemente und so … -->
  </svg>
  <figcaption>Eine skalierbare Vektorgrafik</figcaption>
</figure>

nicht (getestet mit Firefox, Chromium, Konqueror und Links2). Um an die Grafik zu kommen, kann ich natürlich den Teilbaum des svg-Elements aus dem DOM kopieren, aber das ist nicht für alle Nutzer eine brauchbare Idee. Dann bin ich über data:-URLs gestolpert und hatte eine Idee:

<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function(evt) {
	let svg = '<?xml version="1.0" encoding="UTF-8"?>'
			+ document.getElementById('svg2').outerHTML;
	const prot = 'data:image/svg+xml;charset=UTF-8';
	document.getElementById('dl_url').href = prot + ','
			+ encodeURIComponent(svg);
});
</script>

<!-- … -->

<figure>
	<svg xmlns="http://www.w3.org/2000/svg"
			id="svg2" version="1.1"
			height="300" width="400" viewBox="0 0 400 300">
		<!-- … -->
	</svg>
	<figcaption>Diese Grafik ist direkt im HTML-Code enthalten.
		Statt des Rechtsklicks auf die Grafik kann ein
		<a id="dl_url">Download-Link für das Kontextmenü</a>
		angegeben werden (sofern JavaScript aktiv ist).
	</figcaption>
</figure>

Das Prinzip ist recht simpel: Ich hole mir den „äußeren HTML-Code“ [2] des/der gewünschten SVG(s) und erstelle damit einen Link mit data-URL als Ziel. Man kann den Link nicht anklicken, aber das Ziel per Rechtsklick → Speichern Unter speichern.

Zum Ausprobieren gibt es auch eine fertige Seite, auf der ich außerdem probiert habe, wie sich die Variante mit Base64-Kodierung verhält. Spoiler: Sofern man ASCII-Zeichen verwendet ist das im Browser nicht problematisch, bei Zeichen außerhalb von ASCII wird es interessant: In meiner Grafik (UTF-8) sind Zeichen enthalten, die es auch in ISO-8859-1 gibt. JavaScript speichert diese mit ihren ISO-8859-1-Codepoints in der SVG-Datei, die damit natürlich entgegen der Zeichensatz-Angabe nicht mehr UTF-8 ist. Wenn man dann dem Hinweis in der MDN folgt, erschließt sich, dass man btoa für Unicode (16 Bit Zeichensatz) verwenden muss. Die gespeicherte SVG-Datei ist dann allerdings UTF-16 kodiert.

Meine Frage in die Runde wäre jetzt, ob das ein Thema fürs Wiki oder das Blog wäre?

Viele Grüße
Robert


  1. Spricht man heute eigentlich noch von Dateninseln? ↩︎

  2. siehe auch ↩︎

  1. Hallo Robert,

    Um an die Grafik zu kommen, kann ich natürlich den Teilbaum des svg-Elements aus dem DOM kopieren, aber das ist nicht für alle Nutzer eine brauchbare Idee.

    Das Speichern als Blob sollte Browser-übergreifend funktionieren, bei meinem Editor-Ansatz über den Export-Button. Übergeben wird ein SVG-String.

    Grüße,
    Thomas

    1. Hallo Thomas,

      Das Speichern als Blob sollte Browser-übergreifend funktionieren, bei meinem Editor-Ansatz über den Export-Button. Übergeben wird ein SVG-String.

      vielen Dank für deinen Link. Der Download wird über das gleichnamige Attribut des Links realisiert, wie ich ausprobiert habe. Die Idee habe ich für meinen Testcase übernommen – das Schöne daran ist, dass der Browser fragt, ob ich die Datei Öffnen oder Speichern möchte.

      Zum Blob habe ich in der MDN allerdings den Hinweis gefunden, dass URL.createObjectURL() experimentell ist.

      Viele Grüße
      Robert

      1. Hallo Robert,

        die deutsche Version der MDN ist nicht so toll - in dem Fall veraltet. In der englischen Version fehlt der Hinweis und da ist auch ein Link auf eine Recommendation.

        Beachten muss man allerdings dies:

        To release an object URL, call revokeObjectURL().

        und das tut Thomas nicht.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo zusammen,

          die deutsche Version der MDN ist nicht so toll - in dem Fall veraltet. In der englischen Version fehlt der Hinweis und da ist auch ein Link auf eine Recommendation.

          Beachten muss man allerdings dies:

          To release an object URL, call revokeObjectURL().

          und das tut Thomas nicht.

          Danke für den Hinweis. Hat sch bisher nicht negativ ausgewirkt, ist nun eingebaut.

          Grüße,
          Thomas

          1. Hallo Thomas,

            es ist ein Speicherleck, und man merkt es auf den modernen Hosentaschen-Großrechnern vermutlich erst dann, wenn man 100000 Data-URLs zu je 10MB aufgebaut hat und 1 Gigabytelein RAM vom den üblichen 16 oder 32 fehlt.

            Ich finde es auch merkwürdig, dass diese Funktion einen manuellen Revoke braucht und das nicht der Garbage Collector erledigt, wie sonst. Das vergessen vermutlich die meisten, und die allermeisten merken es nicht weil nur ein paar kleine Data-URLs erzeugt werden.

            Rolf

            --
            sumpsi - posui - obstruxi
  2. Servus!

    Hallo,

    ich habe mir seit längerem Gedanken gemacht, wie man direkt im HTML notiertes SVG [^1] speichern bzw. zum Speichern anbieten kann.

    [•••]

    Das Prinzip ist recht simpel: Ich hole mir den „äußeren HTML-Code“ [^2] des/der gewünschten SVG(s) und erstelle damit einen Link mit data-URL als Ziel. Man kann den Link nicht anklicken, aber das Ziel per Rechtsklick → Speichern Unter speichern.

    Meine Frage in die Runde wäre jetzt, ob das ein Thema fürs Wiki oder das Blog wäre?

    Auf jeden Fall! Wsl. eher für den Blog mit ein paar Links zurück ins Wiki!

    Ich habe Dir mal ne PN geschickt!

    Herzliche Grüße

    Matthias Scharwies

    --
    25 Jahre SELFHTML → SELF-Treffen 05.-07. Juni 2020 in Mannheim
    1. Hallo Matthias,

      ich wollte den Blob schon selbst vorschlagen, aber Thomas hat das sehr clever realisiert. Man muss es in seinem Code nur finden 🤔, dann ist dem nichts mehr hinzuzufügen (außer einem Code-Style, der zu JavaScript und nicht zu COBOL passt 😜).

      Darum sollte seine Lösung sollte unbedingt Beachtung finden. Sie erzeugt eine UTF-8 Data-URL und generiert bei Klick sofort den Download. Das finde ich wesentlich eleganter als ein manuelles Durchlaufen der Codepoints und ein "Save As" auf einem Link.

      Es sei denn, das funktioniert auf gewissen Browsern nicht, aber da ich da eine Funktion mit ms-Präfix sehe, glaube ich, dass sogar Nostalgiker wie ich damit leben können 😉

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hallo Rolf,

        ich wollte den Blob schon selbst vorschlagen, aber Thomas hat das sehr clever realisiert. Man muss es in seinem Code nur finden 🤔, dann ist dem nichts mehr hinzuzufügen (außer einem Code-Style, der zu JavaScript und nicht zu COBOL passt 😜).

        schaue ich mir mal an.

        Darum sollte seine Lösung sollte unbedingt Beachtung finden. Sie erzeugt eine UTF-8 Data-URL und generiert bei Klick sofort den Download. Das finde ich wesentlich eleganter als ein manuelles Durchlaufen der Codepoints und ein "Save As" auf einem Link.

        Wo wird denn in meinem Code etwas manuell durchlaufen? Das läuft voll automatisch, sobald der DOMContentLoaded ist.

        Viele Grüße
        Robert

        1. Hallo Robert,

          vielleicht war bin ja zu dumm. Aber ich muss auf den Link mit der rechten Maustaste klicken und "Link Speichern Unter" auswählen. Bei Thomas klicke ich auf den "Export" Button und der Download startet automatisch.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Hallo Rolf,

            vielleicht war bin ja zu dumm. Aber ich muss auf den Link mit der rechten Maustaste klicken und "Link Speichern Unter" auswählen. Bei Thomas klicke ich auf den "Export" Button und der Download startet automatisch.

            wenn ich dem Link das Attribut download gebe, dann reicht ein normaler Klick und ich werde gefragt, ob ich die Datei Öffnen oder Speichern möchte – den Punkt von Thomas habe ich übernommen. Was ich allerdings immer noch verstanden habe: Wo werden in meinem Code irgendwelche „Codepoints […] manuell Durchlaufen“?

            Viele Grüße
            Robert

            1. Hallo Robert,

              da:

               	let ucBytes = new Uint16Array(svg.length);
              	for (let i = 0; i < ucBytes.length; ++i) {
              		ucBytes[i] = svg.charCodeAt(i);
              	}
              

              Das ist nicht nötig, wenn Du einen Blob erzeugst.

              Rolf

              --
              sumpsi - posui - obstruxi
              1. Hallo Rolf,

                da:

                 	let ucBytes = new Uint16Array(svg.length);
                	for (let i = 0; i < ucBytes.length; ++i) {
                		ucBytes[i] = svg.charCodeAt(i);
                	}
                

                Das ist nicht nötig, wenn Du einen Blob erzeugst.

                Das ist korrekt – und es ist auch nicht nötig, wenn ich encodeURIComponent verwende 😉

                Viele Grüße
                Robert

                1. Hallo Robert,

                  oder gleich URL.createObjectURL(contentBlob), wie Thomas.

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. Hallo Rolf,

                    oder gleich URL.createObjectURL(contentBlob), wie Thomas.

                    … und dabei etwas beachten.

                    Viele Grüße
                    Robert

  3. Moin,

    da in meinem Beispiel

    <figure>
      <svg width="" height="" >
        <title>Titel der Grafik</title>
        <!-- … Elemente und so … -->
      </svg>
      <figcaption>Eine skalierbare Vektorgrafik</figcaption>
    </figure>
    

    die Namensraumangabe fehlt (braucht ja bei HTML5 auch nicht zu sein), muss natürlich der JavaScript-Code angepasst sein, damit SVG-Viewer und -Editoren damit arbeiten können:

    document.addEventListener('DOMContentLoaded', function(evt) {
    	let so = document.getElementById('svg2');
    	if (so.attributes['xmlns'] === undefined) {
    		so.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    	}
    	let svg = '<?xml version="1.0" encoding="UTF-8"?>'
    			+ so.outerHTML;
    	const prot = 'data:image/svg+xml;charset=UTF-8';
    	document.getElementById('dl_url').href = prot + ','
    			+ encodeURIComponent(svg);
    });
    

    Viele Grüße
    Robert

  4. Nachtrag 2: Man kann so Ostereier finden, die @Matthias Scharwies und ich versteckt haben.