Textgenerierung mit Go-Vorlagen

Überblick

Als Softwareentwickler ist Text überall um uns herum. Code ist Text, HTML ist Text, XNL / JSON / YAML / TOML ist Text. Markdown ist Text. CSV ist Text. Alle diese Textformate sind sowohl für Menschen als auch für Maschinen gedacht. Menschen sollten in der Lage sein, Textformate mit einfachen Texteditoren zu lesen und zu bearbeiten. 

In vielen Fällen müssen Sie jedoch Text in einem bestimmten Format erstellen. Sie können von einem Format in ein anderes konvertieren, ein eigenes DSL erstellen, automatisch einen Hilfscode generieren oder eine E-Mail mit benutzerspezifischen Informationen anpassen. Was auch immer der Bedarf ist, Go kann Sie mit seinen leistungsstarken Vorlagen auf dem Weg unterstützen. 

In diesem Lernprogramm erfahren Sie mehr über die Besonderheiten von Go-Vorlagen und deren Verwendung für die Erstellung von Text.

Was sind Go-Vorlagen??

Go-Vorlagen sind Objekte, die Text mit speziellen Platzhaltern verwalten, die als Aktionen bezeichnet werden und in doppelte geschweifte Klammern eingeschlossen sind: einige Aktion. Wenn Sie die Vorlage ausführen, stellen Sie eine Go-Struktur bereit, die die Daten enthält, die die Platzhalter benötigen. 

Hier ist ein kurzes Beispiel, das Klopfwitze erzeugt. Ein Schlag-Witz hat ein sehr strenges Format. Die einzigen Dinge, die sich ändern, sind die Identität des Klopfers und der Pointe.

Paket Hauptimport ("Text / Vorlage" "os") Typ Joke struct Who-String Punchline-Zeichenfolge func main () t: = template.New ("Knock Knock Joke") text: = 'Knock Knock \ nWho ist da? .Wer, wer, wer? .Punchline 't.Parse (Text) witzelt: = [] Witz "Etch", "Ich segne dich!", "Kuh geht", "Nein, Kuh geht moo!", Für _, Witz: = Bereichswitze t.Execute (os.Stdout, Witz) Ausgabe: Knock Knock Wer ist da? Etch Etch wer? Gesundheit! Klopf klopf Wer ist da? Kuh geht Kuh geht wer Nein, die Kuh macht moo!

Grundlegendes zu Vorlagenaktionen

Die Vorlagensyntax ist sehr leistungsfähig und unterstützt Aktionen wie Datenzugriffsfunktionen, Funktionen, Pipelines, Variablen, Bedingungen und Schleifen.

Daten Accessors

Datenzugriffe sind sehr einfach. Sie ziehen einfach Daten aus der Struktur. Sie können auch in verschachtelte Strukturen bohren:

func main () Familie: = Familie Vater: Person "Tarzan", Mutter: Person "Jane", ChildrenCount: 2, t: = template.New ("Vater") text: = "Der Vater Name ist .Father.Name "t.Parse (Text) t.Execute (os.Stdout, Familie) 

Wenn die Daten keine Struktur sind, können Sie sie einfach verwenden . um auf den Wert direkt zuzugreifen:

func main () t: = template.New ("") t.Parse ("Alles geht: . \ n") t.Execute (os.Stdout, 1) t.Execute (os.Stdout, "two") t.Execute (os.Stdout, 3.0) t.Execute (os.Stdout, map [string] int "four": 4) Ausgabe: Alles geht: 1 Alles geht: Zwei Alles geht: 3 Alles ist möglich: map [four: 4] 

Wir werden später sehen, wie mit Arrays, Slices und Karten umzugehen ist.

Funktionen

Funktionen erhöhen wirklich das, was Sie mit Vorlagen machen können. Es gibt viele globale Funktionen, und Sie können sogar vorlagenspezifische Funktionen hinzufügen. Die vollständige Liste der globalen Funktionen finden Sie auf der Go-Website.

Hier ist ein Beispiel für die Verwendung von printf Funktion in einer Vorlage:

func main () t: = template.New ("") t.Parse ('Nur zwei Dezimalstellen von π: printf "% .2f".') t.Execute (os.Stdout, math. Pi) Ausgabe: Halten Sie nur 2 Dezimalstellen von π: 3.14 

Pipelines

Mit Pipelines können Sie mehrere Funktionen auf den aktuellen Wert anwenden. Das Kombinieren verschiedener Funktionen erweitert die Möglichkeiten, wie Sie Ihre Werte schneiden und würfeln können. 

Im folgenden Code werden drei Funktionen verkettet. Zuerst führt die Call-Funktion die Funktionsübergabe an Ausführen(). Dann ist die len function gibt die Länge des Ergebnisses der Eingabefunktion zurück, in diesem Fall 3. Endlich, das printf Funktion druckt die Anzahl der Elemente.

func main () t: = template.New ("") t.Parse ('call. | len | printf "% d items"') t.Execute (os.Stdout, func () Zeichenfolge  return "abc") Ausgabe: 3 Elemente

Variablen

Manchmal möchten Sie das Ergebnis einer komplexen Pipeline mehrmals wiederverwenden. Mit Go-Vorlagen können Sie eine Variable definieren und beliebig oft wiederverwenden. Im folgenden Beispiel werden der Vorname und der Nachname aus der Eingabestruktur extrahiert, in Anführungszeichen gesetzt und in den Variablen gespeichert $ F und $ L. Dann werden sie in normaler und umgekehrter Reihenfolge gerendert. 

Ein weiterer netter Trick hier ist, dass ich eine anonyme Struktur an die Vorlage übergeben, um den Code übersichtlicher zu gestalten und ihn nicht mit Typen zu überladen, die nur an einer Stelle verwendet werden.

func main () t: = template.New ("") t.Parse ('$ F: =. Erster Name | printf "% q" $ L: =. Letzter Name | printf "% q"  Normal: $ F $ L Umkehren: $ L $ F ') t.Execute (os.Stdout, struct Vorname String Nachname "Gigi "," Sayfan ",) Ausgabe: Normal:" Gigi "" Sayfan "Umgekehrt:" Sayfan "" Gigi "

Conditionals

Aber lass uns nicht aufhören. Sie können sogar Bedingungen in Ihren Vorlagen haben. Da ist ein Wenn-Ende Aktion und if-else-end Aktion. Die if-Klausel wird angezeigt, wenn die Ausgabe der bedingten Pipeline nicht leer ist:

func main () t: = template.New ("") t.Parse ('if. - . else Keine Daten verfügbar end') t. Ausführen (os.Stdout, "42") t.Execute (os.Stdout, "") Ausgabe: 42 Es sind keine Daten verfügbar 

Beachten Sie, dass die else-Klausel eine neue Zeile verursacht und der Text "Keine Daten verfügbar" deutlich eingerückt ist.

Schleifen

Go-Vorlagen haben auch Schleifen. Dies ist sehr nützlich, wenn Ihre Daten Slices, Maps oder andere iterierbare Elemente enthalten. Das Datenobjekt für eine Schleife kann ein beliebiges iterierbares Go-Objekt wie Array, Slice, Map oder Channel sein. Mit der Range-Funktion können Sie das Datenobjekt durchlaufen und für jedes Element eine Ausgabe erstellen. Mal sehen, wie man eine Karte durchläuft:

func main () t: = template.New ("") e: = 'Name, Bewertung Bereich $ k, $ v: =. $ k Bereich $ s: = $ v , $ s end end 't.Parse (e) t.Execute (os.Stdout, map [string] [] int "Mike"): 88, 77 , 99, "Betty": 54, 96, 78, "Jake": 89, 67, 93,) Ausgabe: Name, Ergebnisse Betty, 54,96,78 Jake, 89,67,93 Mike, 88,77,99 

Wie Sie sehen, ist der führende Whitespace immer noch ein Problem. Ich konnte keinen angemessenen Weg finden, um es innerhalb der Vorlagensyntax anzusprechen. Es ist eine Nachbearbeitung erforderlich. Theoretisch können Sie einen Bindestrich setzen, um Leerzeichen vor oder nach einer Aktion zu trimmen. Dies funktioniert jedoch nicht in der Nähe von Angebot.

Textvorlagen

Textvorlagen werden im Text- / Vorlagenpaket implementiert. Zusätzlich zu allem, was wir bisher gesehen haben, kann dieses Paket auch Vorlagen aus Dateien laden und mithilfe der Vorlagenaktion mehrere Vorlagen erstellen. Das Template-Objekt selbst verfügt über viele Methoden, um solche erweiterten Anwendungsfälle zu unterstützen:

  • ParseFiles ()
  • ParseGlob ()
  • AddParseTree ()
  • Klon()
  • DefinedTemplates ()
  • Delims ()
  • ExecuteTemplate ()
  • Funk ()
  • Sieh nach oben()
  • Möglichkeit()
  • Vorlagen ()

Aus Platzgründen werde ich nicht weiter ins Detail gehen (vielleicht in einem anderen Tutorial).

HTML-Vorlagen 

HTML-Vorlagen werden im Paket html / template definiert. Es hat genau dieselbe Schnittstelle wie das Textvorlagenpaket, aber es ist so konzipiert, dass HTML generiert wird, das vor der Code-Injektion sicher ist. Dies geschieht durch vorsichtiges Bereinigen der Daten, bevor sie in die Vorlage eingebettet werden. Die Arbeitsannahme ist, dass Vorlagenautoren vertrauenswürdig sind, aber die Daten, die für die Vorlage bereitgestellt werden, nicht vertrauenswürdig sind. 

Das ist wichtig. Wenn Sie automatisch Vorlagen anwenden, die Sie von nicht vertrauenswürdigen Quellen erhalten, schützt Sie das Paket html / template nicht. Es liegt in Ihrer Verantwortung, die Vorlagen zu überprüfen.

Lassen Sie uns den Unterschied zwischen der Ausgabe von sehen Text / Vorlage und HTML / Vorlage. Bei Verwendung von Text / Vorlage ist es einfach, JavaScript-Code in die generierte Ausgabe einzufügen.

Pakethauptimport ("text / template" "os") func main () t, _: = template.New (""). Parse ("Hallo, .!") d: = ""t.Execute (os.Stdout, d) Ausgabe: Hallo, ! 

Aber das importieren HTML / Vorlage anstatt Text / Vorlage verhindert diesen Angriff, indem die Skript-Tags und die Klammern maskiert werden:

Hallo, !

Umgang mit Fehlern

Es gibt zwei Arten von Fehlern: Analysefehler und Ausführungsfehler. Das Parse () Die Funktion analysiert den Vorlagentext und gibt einen Fehler zurück, den ich in den Codebeispielen ignoriert habe. Im Produktionscode möchten Sie diese Fehler jedoch frühzeitig abfangen und beheben. 

Wenn Sie einen schnellen und schmutzigen Ausgang wünschen, dann die Muss() method nimmt die Ausgabe einer Methode, die zurückgibt (* Vorlage, Fehler)-mögen Klon(), Parse (), oder ParseFiles ()-und Panik, wenn der Fehler nicht gleich Null ist. So überprüfen Sie einen expliziten Parsing-Fehler:

func main () e: = "Ich bin eine schlechte Vorlage. " _, err: = template.New (""). Parse (e) if err! = nil msg: = "Fehler beim Analyse: '% s'. \ nFehler:% v \ n "fmt.Printf (msg, e, err) Ausgabe: Die Vorlage konnte nicht analysiert werden: 'Ich bin eine fehlerhafte Vorlage, '. Fehler: template:: 1: unerwartete nicht geschlossene Aktion im Befehl 

Verwenden Muss() Nur Panik, wenn mit der Vorlage etwas nicht stimmt:

func main () e: = "Ich bin eine schlechte Vorlage. " template.Must (template.New (""). Parse (e)) Ausgabe: panic: template:: 1: unerwartetes Schließen Aktion in Befehl 

Die andere Art von Fehler ist ein Ausführungsfehler, wenn die bereitgestellten Daten nicht mit der Vorlage übereinstimmen. Sie können dies auch explizit überprüfen oder verwenden Muss() in Panik geraten Ich empfehle in diesem Fall, dass Sie einen Wiederherstellungsmechanismus prüfen und einen solchen haben. 

Normalerweise muss das gesamte System nicht heruntergefahren werden, nur weil eine Eingabe die Anforderungen nicht erfüllt. Im folgenden Beispiel erwartet die Vorlage ein Feld namens Name auf der Datenstruktur, aber ich gebe eine Struktur mit einem Feld namens Vollständiger Name.

func main () e: = "Es muss ein Name vorhanden sein: .Name" t, _: = template.New (""). Parse (e) err: = t.Execute (os.Stdout, struct FullName-Zeichenfolge "Gigi Sayfan",) if err! = nil fmt.Println ("Ausführung fehlgeschlagen.", err) Ausgabe: Es muss ein Name vorhanden sein: Ausführung fehlgeschlagen. template:: 1: 24: "" um ausführen <.Name>: Feld Name kann nicht im Typ struct FullName-Zeichenfolge ausgewertet werden. 

Fazit

Go verfügt über ein leistungsstarkes und ausgereiftes Templating-System. Sie wird in vielen großen Projekten wie Kubernetes und Hugo mit großem Erfolg eingesetzt. Das html / template-Paket bietet eine sichere, industrietaugliche Einrichtung zur Bereinigung der Ausgabe von webbasierten Systemen. In diesem Tutorial haben wir alle Grundlagen und einige Anwendungsfälle behandelt. 

Es gibt noch weitere erweiterte Funktionen in den Vorlagenpaketen, die auf die Freischaltung warten. Spielen Sie mit Vorlagen und integrieren Sie diese in Ihre Programme. Sie werden angenehm überrascht sein, wie prägnant und lesbar Ihr Textgenerierungscode aussieht.