Run a function on data in a go html/template - templates

I want to add hyphens (-) to a string in a go template when someone tries to save it. I'm using some modified code from the go wiki tutorial here: https://golang.org/doc/articles/wiki/
Code:
<h1>Editing {{.Title}}</h1>
<form action="/save/{{.Title}}" method="POST">
<div><input name="title" type="text" placeholder="title"></div>
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>
The line with
<form action="/save/{{.Title}}" method="POST">
is the relevant line. I need to transform .Title which might be something like "the quick brown fox" to "the-quick-brown-fox".
As you can see in the code above, you can add a function like println, but I'm not sure how I would do this for my case.

You can pass a template.FuncMap to the template and then you can do something like:
{{ .Title | title }}
https://play.golang.org/p/KWy_KRttD_
func Sluggify(s string) string {
return strings.ToLower(s) //for example
}
func main() {
funcMap := template.FuncMap {
"title": Sluggify,
}
tpl := template.Must(template.New("main").Funcs(funcMap).Parse(`{{define "T"}}Hello {{.Title | title }} Content: {{.Content}}{{end}}`))
tplVars := map[string]string {
"Title": "Hello world",
"Content": "Hi there",
}
tpl.ExecuteTemplate(os.Stdout, "T", tplVars)
}

All of your *Page structs are created by the loadPage function. So it would seem to be easiest to just create your hyphenated title then and store it in your page struct:
type Page struct {
Title string
HyphenTitle string
Body []byte
}
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body, HyphenTitle: hyphenate(title)}, nil
}
func hyphenate (s string) string {
return strings.Replace(s," ","-",-1)
}
Then just use {{.HyphenTitle}} where you want it.

Related

Regex for replace a string [duplicate]

I have a string with html markup in it (differMarkup) and would like to run that string through a tokenizer that would identify specific tags (like ins, dels, movs) and replace them with the span tag and add data attributes to it as well.
So the input looks like this:
`<h1>No Changes Here</h1>
<p>This has no changes</p>
<p id="1"><del>Delete </del>the first word</p>
<p id="2"><ins>insertion </ins>Insert a word at the start</p>`
And intended output would be this:
`<h1>No Changes Here</h1>
<p>This has no changes</p>
<p id="1"><span class="del" data-cid=1>Delete</span>the first word</p>
<p id="2"><span class="ins" data-cid=2>insertion</span>Insert a word at the start</p>
`
This is what I currently have. For some reason I'm not able to append the html tags to the finalMarkup var when setting it to span.
const (
htmlTagStart = 60 // Unicode `<`
htmlTagEnd = 62 // Unicode `>`
differMarkup = `<h1>No Changes Here</h1>
<p>This has no changes</p>
<p id="1"><del>Delete </del>the first word</p>
<p id="2"><ins>insertion </ins>Insert a word at the start</p>` // Differ Markup Output
)
func readDifferOutput(differMarkup string) string {
finalMarkup := ""
tokenizer := html.NewTokenizer(strings.NewReader(differMarkup))
token := tokenizer.Token()
loopDomTest:
for {
tt := tokenizer.Next()
switch {
case tt == html.ErrorToken:
break loopDomTest // End of the document, done
case tt == html.StartTagToken, tt == html.SelfClosingTagToken:
token = tokenizer.Token()
tag := token.Data
if tag == "del" {
tokenType := tokenizer.Next()
if tokenType == html.TextToken {
tag = "span"
finalMarkup += tag
}
//And add data attributes
}
case tt == html.TextToken:
if token.Data == "span" {
continue
}
TxtContent := strings.TrimSpace(html.UnescapeString(string(tokenizer.Text())))
finalMarkup += TxtContent
if len(TxtContent) > 0 {
fmt.Printf("%s\n", TxtContent)
}
}
}
fmt.Println("tokenizer text: ", finalMarkup)
return finalMarkup
}
```golang
Basically you want to replace some nodes in your HTML text. For such tasks it's much easier to work with DOMs (Document Object Model) than to handle the tokens yourself.
The package you're using golang.org/x/net/html also supports modeling HTML documents using the html.Node type. To acquire the DOM of an HTML document, use the html.Parse() function.
So what you should do is traverse the DOM, and replace (modify) the nodes you want to. Once you're done with the modifications, you can get back the HTML text by rendering the DOM, for that use html.Render().
This is how it can be done:
const src = `<h1>No Changes Here</h1>
<p>This has no changes</p>
<p id="1"><del>Delete </del>the first word</p>
<p id="2"><ins>insertion </ins>Insert a word at the start</p>`
func main() {
root, err := html.Parse(strings.NewReader(src))
if err != nil {
panic(err)
}
replace(root)
if err = html.Render(os.Stdout, root); err != nil {
panic(err)
}
}
func replace(n *html.Node) {
if n.Type == html.ElementNode {
if n.Data == "del" || n.Data == "ins" {
n.Attr = []html.Attribute{{Key: "class", Val: n.Data}}
n.Data = "span"
}
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
replace(child)
}
}
This will output:
<html><head></head><body><h1>No Changes Here</h1>
<p>This has no changes</p>
<p id="1"><span class="del">Delete </span>the first word</p>
<p id="2"><span class="ins">insertion </span>Insert a word at the start</p></body></html>
This is almost what you want, the "extra" thing is that the html package added wrapper <html> and <body> elements, along with an empty <head>.
If you want to get rid of those, you may just render the content of the <body> element and not the entire DOM:
// To navigate to the <body> node:
body := root.FirstChild. // This is <html>
FirstChild. // this is <head>
NextSibling // this is <body>
// Render everyting in <body>
for child := body.FirstChild; child != nil; child = child.NextSibling {
if err = html.Render(os.Stdout, child); err != nil {
panic(err)
}
}
This will output:
<h1>No Changes Here</h1>
<p>This has no changes</p>
<p id="1"><span class="del">Delete </span>the first word</p>
<p id="2"><span class="ins">insertion </span>Insert a word at the start</p>
And we're done. Try the examples on the Go Playground.
If you want the result as a string (instead of printed to the standard output), you may use bytes.Buffer as the output for rendering, and call its Buffer.String() method in the end:
// Render everyting in <body>
buf := &bytes.Buffer{}
for child := body.FirstChild; child != nil; child = child.NextSibling {
if err = html.Render(buf, child); err != nil {
panic(err)
}
}
fmt.Println(buf.String())
This outputs the same. Try it on the Go Playground.

Go template can't call method on field

I have a wrapper around net/mail.Address that provides some marshalling logic. I'm trying to use it in a template, but I keep getting can't evaluate field String in type EmailAddress. The template docs say:
The name of a niladic method of the data, preceded by a period,
such as
.Method
The result is the value of invoking the method with dot as the
receiver, dot.Method().
and
Method invocations may be chained and combined with fields and keys
to any depth:
.Field1.Key1.Method1.Field2.Key2.Method2
So with that in mind I've written this:
package main
import (
"bytes"
"fmt"
"html/template"
"net/mail"
"os"
)
type EmailAddress struct{ mail.Address }
type emailFormatter struct {
From EmailAddress
To EmailAddress
}
var tmpl = template.Must(template.New("Sample Text").Parse("From: {{.From.String}}\r" + `
To: {{.To.String}}` + "\r" + `
Content-Type: text/html` + "\r" + `
Subject: Sample Text` + "\r\n\r" + `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sample Text</title>
<meta charset="utf-8"/>
</head>
<body>
Sample Text
</body>
</html>
`));
func main() {
to := EmailAddress{
mail.Address{
Address: "em#i.l",
Name: "",
},
}
from := EmailAddress{
mail.Address{
Address: "no-reply#test.quest",
Name: "",
},
}
fmt.Println(to.String()) //outputs (as expected) "<em#i.l>"
fmt.Println(from.String()) //outputs (as expected) "<no-reply#test.quest>"
f := emailFormatter{
To: to,
From: from,
}
var buff bytes.Buffer
if err := tmpl.Execute(&buff, f); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Println(buff.String())
}
I've separately verified that calling EmailAddress.String is totally legal, so I can't figure out why the only output of this is:
Error: template: Sample Text:1:13: executing "Sample Text" at <.From.String>: can't evaluate field String in type main.EmailAddress
EDIT
At a commenter's suggestion I changed the calls from .From.String and .To.String to .From.Address.String and .To.Address.String, because
"String isn't defined on EmailAddress, it's defined on EmailAddress.Address"
but the result is the same:
Error: template: Sample Text:1:13: executing "Sample Text" at <.From.Address.String>: can't evaluate field String in type mail.Address
Since String is defined with a pointer receiver you need to pass an "addressable" instance of mail.Address to the template to be able to execute that method.
You can do this by passing in a pointer to f.
if err := tmpl.Execute(&buff, &f); err != nil {
panic(err)
}
Or you can do it by passing in pointer to EmailAddress.
type emailFormatter struct {
From *EmailAddress
To *EmailAddress
}
// ...
f := emailFormatter{
To: &to,
From: &from,
}
// ...
if err := tmpl.Execute(&buff, f); err != nil {
panic(err)
}
Or by passing in a pointer to mail.Address.
type EmailAddress struct{ *mail.Address }
// ...
to := EmailAddress{
&mail.Address{
Address: "em#i.l",
Name: "",
},
}
from := EmailAddress{
&mail.Address{
Address: "no-reply#test.quest",
Name: "",
},
}
f := emailFormatter{
To: to,
From: from,
}
// ...
if err := tmpl.Execute(&buff, f); err != nil {
panic(err)
}
Note that the reason you don't need to do that in the Go code is because there the compiler does it for you.
For example:
fmt.Println(to.String())
becomes:
fmt.Println((&to).String())
A method call x.m() is valid if the method set of (the type of) x
contains m and the argument list can be assigned to the parameter list
of m. If x is addressable and &x's method set contains m, x.m() is
shorthand for (&x).m()

How can I replace all with argument in Golang regex?

I am using Golang regex package, I want to use regex ReplaceAllStringFunc with argument, not only with the source string.
For example, I want to update this text
"<img src=\"/m/1.jpg\" /> <img src=\"/m/2.jpg\" /> <img src=\"/m/3.jpg\" />"
To (change "m" to "a" or anything else):
"<img src=\"/a/1.jpg\" /> <img src=\"/a/2.jpg\" /> <img src=\"/a/3.jpg\" />"
I would like to have something like:
func UpdateText(text string) string {
re, _ := regexp.Compile(`<img.*?src=\"(.*?)\"`)
text = re.ReplaceAllStringFunc(text, updateImgSrc)
return text
}
// update "/m/1.jpg" to "/a/1.jpg"
func updateImgSrc(imgSrcText, prefix string) string {
// replace "m" by prefix
return "<img src=\"" + newImgSrc + "\""
}
I checked the doc, ReplaceAllStringFunc doesn't support argument, but what would be the best way to achieve my goal?
More generally, I would like to find all occurrences of one pattern then update each with a new string which is composed by source string + a new parameter, could anyone give any idea?
I agree with the comments, you probably don't want to parse HTML with regular expressions (bad things will happen).
However, let's pretend it's not HTML, and you want to only replace submatches. You could do this
func UpdateText(input string) (string, error) {
re, err := regexp.Compile(`img.*?src=\"(.*?)\.(.*?)\"`)
if err != nil {
return "", err
}
indexes := re.FindAllStringSubmatchIndex(input, -1)
output := input
for _, match := range indexes {
imgStart := match[2]
imgEnd := match[3]
newImgName := strings.Replace(input[imgStart:imgEnd], "m", "a", -1)
output = output[:imgStart] + newImgName + input[imgEnd:]
}
return output, nil
}
see on playground
(note that I've slightly changed your regular expression to match the file extension separately)
thanks for kostix's advice, here is my solution using html parser.
func UpdateAllResourcePath(text, prefix string) (string, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(text))
if err != nil {
return "", err
}
sel := doc.Find("img")
length := len(sel.Nodes)
for index := 0; index < length; index++ {
imgSrc, ok := sel.Eq(index).Attr("src")
if !ok {
continue
}
newImgSrc, err := UpdateResourcePath(imgSrc, prefix) // change the imgsrc here
if err != nil {
return "", err
}
sel.Eq(index).SetAttr("src", newImgSrc)
}
newtext, err := doc.Find("body").Html()
if err != nil {
return "", err
}
return newtext, nil
}

Add to array from json and execute data in template

I have a little qiestion!
How add to array data from json and execute template. Simple. But Not working!
package main
import (
"fmt"
"html/template"
"os"
"encoding/json"
)
type Person struct {
Name string
Jobs []*Job
}
type Job struct {
Employer string
Role string
}
const templ = `The name is {{.Name}}.
{{with .Jobs}}
{{range .}}
An employer is {{.Employer}}
and the role is {{.Role}}
{{end}}
{{end}}
`
func main() {
job1 := Job{Employer: "Monash", Role: "Honorary"}
job2 := Job{Employer: "Box Hill", Role: "Head of HE"}
byt := []byte(`{"num":6.13,"Jobs":[{"Employer": "test1", "Role": "test1"},{"Employer": "test2", "Role": "test2"}]}`)
var dat map[string]interface{}
if err := json.Unmarshal(byt, &dat); err != nil {
panic(err)
}
fmt.Println(dat)
// HOW ADD FROM ARRAY 'dat' TO STRUCT 'Job'
// LINE 54
person := Person{
Name: "jan",
Jobs: []*Job{&job1, &job2},
}
t := template.New("Person template")
t, err := t.Parse(templ)
checkError(err)
err = t.Execute(os.Stdout, person)
checkError(err)
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
Here you can play/test code: http://play.golang.org/p/AB8hGLrLRy
Watch line 46.
Thank you very much!
First thing, your dat var isn't of the right type. You should have something like that:
dat := struct {
Jobs []*Job
}
That way, when unmarshalling your JSON string into dat, the Jobs key will be filled with a array of *Job initialized with your data's array.
I'm using an anonymous struct, but you could also have a named struct (see #RoninDev's comment for an example).
Then, just add them to the person array, something like that:
person.Jobs = append(person.Jobs, jobs.Jobs...)
Note the ... operator, which use an array as a variadic argument.
You can see the full working code on this playground.
You're unmarshalling the json fine, it's just into the most ambiguous type the language offers... You could unbox it with a series of type asserts but I don't recommend it. The json can easily be modeled in Go with a structure like the following;
type Wrapper struct {
Num float64 `json:"num"`
Jobs []*Job `json:"Jobs"`
}
Here's an updated example of your code using that struct; http://play.golang.org/p/aNLK_Uk2km
After you've deserialized them you can just use append to add them to the jobs array in the person object like person.Jobs = append(person.Jobs, dat.Jobs...)

Accessing struct variable in slice of many structs in html template golang

I'm attempting to send a slice containing many structs to an html template.
I have a 'post' struct
type Post struct {
threadID int
subject string
name string
text string
date_posted string
}
I create a slice of type Post ( posts := []Post{} )
this slice is then populated using rows from my database and then executed on my template.
defer latest_threads.Close()
for latest_threads.Next(){
var threadID int
var subject string
var name string
var text string
var date_posted string
latest_threads.Scan(&threadID, &subject, &name, &text, &date_posted)
post := Post{
threadID,
subject,
name,
text,
date_posted,
}
posts = append(posts, post)
}
t, error := template.ParseFiles("thread.html")
if error != nil{
log.Fatal(error)
}
t.Execute(w, posts)
}
The program compiles / runs okay but when viewing the html output from the template
{{.}}
{{range .}}
<div>{{.threadID}}</div>
<h3>{{.subject}}</h3>
<h3>{{.name}}</h3>
<div>{{.date_posted}}</div>
<div><p>{{.text}}</p></div>
<br /><br />
{{end}}
{{.}} outputs just fine however upon reaching the first {{.threadID}} in {{range .}} the html stops.
<!DOCTYPE html>
<html>
<head>
<title> Test </title>
</head>
<body>
//here is where {{.}} appears just fine, removed for formatting/space saving
<div>
It's not really intuitive, but templates (and encoding packages like JSON, for that matter) can't access unexported data members, so you have to export them somehow:
Option 1
// directly export fields
type Post struct {
ThreadID int
Subject, Name, Text, DatePosted string
}
Option 2
// expose fields via accessors:
type Post struct {
threadID int
subject, name, text, date_posted string
}
func (p *Post) ThreadID() int { return p.threadID }
func (p *Post) Subject() string { return p.subject }
func (p *Post) Name() string { return p.name }
func (p *Post) Text() string { return p.text }
func (p *Post) DatePosted() string { return p.date_posted }
Update template
(this part is mandatory regardless of which option you chose from above)
{{.}}
{{range .}}
<div>{{.ThreadID}}</div>
<h3>{{.Subject}}</h3>
<h3>{{.Name}}</h3>
<div>{{.DatePosted}}</div>
<div><p>{{.Text}}</p></div>
<br /><br />
{{end}}
And this should work.