如何在 Golang 模板中截断字符串

huangapple go评论90阅读模式
英文:

How to truncate a string in a Golang template

问题

在golang中,有没有一种方法可以在html模板中截断文本?

例如,我在模板中有以下内容:

{{ range .SomeContent }}
 ....
    {{ .Content }}
 ....

{{ end }}

{{ .Content }} 生成的内容是:Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam tempus sem ipsum, vel accumsan felis vulputate id. Donec ultricies sem purus, non aliquam orci dignissim et. Integer vitae mi arcu. Pellentesque a ipsum quis velit venenatis vulputate vulputate ut enim.

我想将其缩减为25个字符。

英文:

In golang, is there a way to truncate text in an html template?

For example, I have the following in my template:

{{ range .SomeContent }}
 ....
    {{ .Content }}
 ....

{{ end }

{{ .Content }} produces: Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam tempus sem ipsum, vel accumsan felis vulputate id. Donec ultricies sem purus, non aliquam orci dignissim et. Integer vitae mi arcu. Pellentesque a ipsum quis velit venenatis vulputate vulputate ut enim.

I would like to reduce that to 25 characters.

答案1

得分: 66

你可以在模板中使用printf,它的作用类似于fmt.Sprintf。在你的情况下,截断字符串可以这样简单实现:

{{ printf "%.25s" .Content }}

你也可以将数字作为单独的整数参数传递给printf

{{ printf "%.*s" 25 .Content }}

请注意文档中的说明:
> 宽度和精度以Unicode代码点(即符文)为单位进行测量。(这与C语言的printf不同,C语言中单位始终以字节为度量单位。)

英文:

You can use printf in templates, which acts as fmt.Sprintf. In your case truncating a string would be as easy as:

{{ printf "%.25s" .Content }}

You can also pass the number as a separate integer argument to printf:

{{ printf "%.*s" 25 .Content }}

Note from the documentation:
> Width and precision are measured in units of Unicode code points, that is, runes. (This differs from C's printf where the units are always measured in bytes.)

答案2

得分: 15

你可以使用文档中的 slice 函数。下面的示例应该可以工作:

{{ slice .Content  0 25}}

> slice 函数通过其余的参数对第一个参数进行切片操作。因此,在 Go 语法中,"slice x 1 2" 表示 x[1:2],"slice x" 表示 x[:],"slice x 1" 表示 x[1:],"slice x 1 2 3" 表示 x[1:2:3]。第一个参数必须是字符串、切片或数组。

请注意,它不处理索引超出范围的问题和多字节字符串——如果 .Content 是一个字符串。

请注意,slice 函数仅在 Go 1.13 及以上版本中可用。如果嵌入了 text/template 的程序是使用旧版本的 Go 编译的,请使用 sprintf

英文:

You can use slice from the documentation. The below sample must work:

{{ slice .Content  0 25}}

> slice returns the result of slicing its first argument by the remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2],
while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first argument must be a string, slice, or array.

Beware it doesn't handle index out of range issues and multi-byte strings — in case .Content is a string.

Note that slice is only available from Go 1.13. If the program that embeds text/template has been compiled using an older version of Go, use sprintf.

答案3

得分: 9

**更新:**现在下面的代码对于那些使用国际化程序的人来说是Unicode兼容的。

需要注意的一点是,下面的bytes.Runes("string")操作是一个O(N)的操作,将runes转换为字符串也是如此,所以这段代码对字符串进行了两次循环。可能更高效的做法是对PreviewContent()函数进行如下修改:

func (c ContentHolder) PreviewContent() string {
    var numRunes = 0
    for index, _ := range c.Content {
        numRunes++
        if numRunes > 25 {
            return c.Content[:index]
        }
    }
    return c.Content
}

你有几个选项可以放置这个函数。假设你有某种类型的内容持有者,可以使用以下代码:

type ContentHolder struct {
    Content string
    //其他字段在这里
}

func (c ContentHolder) PreviewContent() string {
    // 这个转换是O(N)的
    runes := bytes.Runes([]byte(c.Content))
    if len(runes) > 25 {
        return string(runes[:25])
    }
    return string(runes)
}

然后你的模板将如下所示:

{{ range .SomeContent }}
....
{{ .PreviewContent }}
....
{{ end }}

另一个选项是创建一个函数,该函数将获取字符串的前25个字符。代码如下(由@Martin DrLík修订的代码,链接到代码):

package main

import (
    "html/template"
    "log"
    "os"
)

func main() {

    funcMap := template.FuncMap{

        // 现在是Unicode兼容的
        "truncate": func(s string) string {
            var numRunes = 0
            for index, _ := range s {
                numRunes++
                if numRunes > 25 {
                    return s[:index]
                }
            }
            return s
        },
    }

    const templateText = `
    Start of text
    {{ range .}}
    Entry: {{.}}
    Truncated entry: {{truncate .}}
    {{end}}
    End of Text
    `
    infoForTemplate := []string{
        "Stackoverflow is incredibly awesome",
        "Lorem ipsum dolor imet",
        "Some more example text to prove a point about truncation",
        "ПриветМирПриветМирПриветМирПриветМирПриветМирПриветМир",
    }

    tmpl, err := template.New("").Funcs(funcMap).Parse(templateText)
    if err != nil {
        log.Fatalf("parsing: %s", err)
    }

    err = tmpl.Execute(os.Stdout, infoForTemplate)
    if err != nil {
        log.Fatalf("execution: %s", err)
    }

}

这将输出:

Start of text

Entry: Stackoverflow is incredibly awesome
Truncated entry: Stackoverflow is incredib

Entry: Lorem ipsum dolor imet
Truncated entry: Lorem ipsum dolor imet

Entry: Some more example text to prove a point about truncation
Truncated entry: Some more example text to

Entry: ПриветМирПриветМирПриветМирПриветМирПриветМирПриветМир
Truncated entry: ПриветМирПриветМирПриветМ

End of Text
英文:

Update: Now the code below is unicode compliant for those who are working with international programs.

One thing to note is that bytes.Runes("string") below is an O(N) operation, as is the converstion from runes to a string, so this code loops over the string twice. It is likely to be more efficient to do the code below for PreviewContent()

func (c ContentHolder) PreviewContent() string {
    var numRunes = 0
    for index, _ := range c.Content {
         numRunes++
         if numRunes > 25 {
              return c.Content[:index]
         }
    }
    return c.Content
}

You have a couple options for where this function can go. Assuming that you have some type of content holder, the below can be used:

type ContentHolder struct {
    Content string
    //other fields here
}

func (c ContentHolder) PreviewContent() string {
    // This cast is O(N)
    runes := bytes.Runes([]byte(c.Content))
	if len(runes) > 25 {
         return string(runes[:25])
	}
	return string(runes)
}

Then your template will look like this:

{{ range .SomeContent }}
....
{{ .PreviewContent }}
....
{{ end }}

The other option is to create a function that will take then first 25 characters of a string. The code for that looks like this (revision of code by @Martin DrLík, link to code)

package main
import (
    "html/template"
    "log"
    "os"
)

func main() {

    funcMap := template.FuncMap{

        // Now unicode compliant
        "truncate": func(s string) string {
             var numRunes = 0
             for index, _ := range s {
                 numRunes++
                 if numRunes > 25 {
                      return s[:index]
                 }
            }
            return s
       },
    }

    const templateText = `
    Start of text
    {{ range .}}
    Entry: {{.}}
    Truncated entry: {{truncate .}}
    {{end}}
    End of Text
    `
    infoForTemplate := []string{
        "Stackoverflow is incredibly awesome",
        "Lorem ipsum dolor imet",
        "Some more example text to prove a point about truncation",
        "ПриветМирПриветМирПриветМирПриветМирПриветМирПриветМир",
    }

    tmpl, err := template.New("").Funcs(funcMap).Parse(templateText)
    if err != nil {
        log.Fatalf("parsing: %s", err)
    }

    err = tmpl.Execute(os.Stdout, infoForTemplate)
    if err != nil {
        log.Fatalf("execution: %s", err)
    }

}

This outputs:

Start of text

Entry: Stackoverflow is incredibly awesome
Truncated entry: Stackoverflow is incredib

Entry: Lorem ipsum dolor imet
Truncated entry: Lorem ipsum dolor imet

Entry: Some more example text to prove a point about truncation
Truncated entry: Some more example text to

Entry: ПриветМирПриветМирПриветМирПриветМирПриветМирПриветМир
Truncated entry: ПриветМирПриветМирПриветМ

End of Text

答案4

得分: 4

需要为Unicode字符串添加更多的魔法

这是不正确的,请参考下面的内容

import "unicode/utf8"

func Short(s string, i int) string {
    if len(s) < i {
        return s
    }
    if utf8.ValidString(s[:i]) {
        return s[:i]
    }
    // 省略部分
    // 实际上,一个rune可以有1-4个字节的宽度(不是1或2个)
    return s[:i+1] // 或者 i-1
}

但是上面的i不是字符的数量,而是字节数。在play.golang.org上链接到这段代码。

希望这可以帮到你。


编辑

更新:检查字符串长度。参见下面的@geoff评论。

参见这个答案,并在这里进行尝试。这是另一种解决方案。

package main

import "fmt"

func Short(s string, i int) string {
    runes := []rune(s)
    if len(runes) > i {
        return string(runes[:i])
    }
    return s
}

func main() {
    fmt.Println(Short("Hello World", 5))
    fmt.Println(Short("Привет Мир", 5))
}

但是,如果你对字节长度感兴趣:

func truncateStrings(s string, n int) string {
    if len(s) <= n {
        return s
    }
    for !utf8.ValidString(s[:n]) {
        n--
    }
    return s[:n]
}

play.golang.org上。这个函数永远不会引发panic(如果n >= 0),但是你可能会得到一个空字符串play.golang.org


此外,还要记住这个实验性的包golang.org/x/exp/utf8string

utf8string包提供了一种通过rune而不是字节来索引字符串的高效方法。

英文:

Needs more magic for Unicode strings

This is not correct, see below

<!-- language: go -->

import &quot;unicode/utf8&quot;

func Short( s string, i int) string {
	if len( s ) &lt; i {
		return s
	}
	if utf8.ValidString( s[:i] ) {
		return s[:i]
	}
    // The omission.
    // In reality, a rune can have 1-4 bytes width (not 1 or 2)
	return s[:i+1] // or i-1
}

But i above is not the number of chars. It's the number of bytes. Link to this code on play.golang.org

I hope this helps.


Edit

Updated: check string length. See @geoff comment below

See that answer, and play here. It's another solution.

<!-- language: go -->

package main

import &quot;fmt&quot;

func Short( s string, i int ) string {
    runes := []rune( s )
    if len( runes ) &gt; i {
        return string( runes[:i] )
    }
    return s
}

func main() {
    fmt.Println( Short( &quot;Hello World&quot;, 5 ) )
    fmt.Println( Short( &quot;Привет Мир&quot;, 5 ) )
}

But if you are interested in the length in bytes:

func truncateStrings(s string, n int) string {
    if len(s) &lt;= n {
        return s
    }
    for !utf8.ValidString(s[:n]) {
        n--
    }
    return s[:n]
}

play.golang.org. This function never panics (if n >= 0), but you can obtain an empty string play.golang.org


Also, keep in mind this experimental package golang.org/x/exp/utf8string

> Package utf8string provides an efficient way to index strings by rune rather than by byte.

答案5

得分: 1

有很多好的答案,但有时候截断而不切割单词更加用户友好。Hugo提供了一个用于此目的的模板函数
但在Hugo之外使用它比较困难,所以我已经实现了以下代码:

func TruncateByWords(s string, maxWords int) string {
    processedWords := 0
    wordStarted := false
    for i := 0; i < len(s); {
        r, width := utf8.DecodeRuneInString(s[i:])
        if !unicode.IsSpace(r) {
            i += width
            wordStarted = true
            continue
        }

        if !wordStarted {
            i += width
            continue
        }

        wordStarted = false
        processedWords++
        if processedWords == maxWords {
            const ending = "..."
            if (i + len(ending)) >= len(s) {
                // 源字符串结尾比 "..." 还要短
                return s
            }

            return s[:i] + ending
        }

        i += width
    }

    // 源字符串包含的单词数少于 maxWords
    return s
}

以下是对该函数的测试:

func TestTruncateByWords(t *testing.T) {
    cases := []struct {
        in, out string
        n       int
    }{
        {"a bcde", "a...", 1},
        {"a b", "a b", 2},
        {"a b", "a b", 3},

        {"a b c", "a b c", 2},
        {"a b cd", "a b cd", 2},
        {"a b cde", "a b...", 2},

        {"  a   b    ", "  a   b...", 2},

        {"AB09C_D EFGH", "AB09C_D...", 1},
        {"Привет Гоферам", "Привет...", 1},
        {"Here are unicode spaces", "Here are...", 2},
    }

    for i, c := range cases {
        got := TruncateByWords(c.in, c.n)
        if got != c.out {
            t.Fatalf("#%d: %q != %q", i, got, c.out)
        }
    }
}
英文:

There are a lot of good answers, but sometimes it's more user-friendly to truncate without cutting words. Hugo offers template function for it.
But it's difficult to use outside of Hugo, so I've implemented it:

func TruncateByWords(s string, maxWords int) string {
	processedWords := 0
	wordStarted := false
	for i := 0; i &lt; len(s); {
		r, width := utf8.DecodeRuneInString(s[i:])
		if !unicode.IsSpace(r) {
			i += width
			wordStarted = true
			continue
		}

		if !wordStarted {
			i += width
			continue
		}

		wordStarted = false
		processedWords++
		if processedWords == maxWords {
			const ending = &quot;...&quot;
			if (i + len(ending)) &gt;= len(s) {
				// Source string ending is shorter than &quot;...&quot;
				return s
			}

			return s[:i] + ending
		}

		i += width
	}

	// Source string contains less words count than maxWords.
	return s
}

And here is a test for this function:

func TestTruncateByWords(t *testing.T) {
	cases := []struct {
		in, out string
		n       int
	}{
		{&quot;a bcde&quot;, &quot;a...&quot;, 1},
		{&quot;a b&quot;, &quot;a b&quot;, 2},
		{&quot;a b&quot;, &quot;a b&quot;, 3},

		{&quot;a b c&quot;, &quot;a b c&quot;, 2},
		{&quot;a b cd&quot;, &quot;a b cd&quot;, 2},
		{&quot;a b cde&quot;, &quot;a b...&quot;, 2},

		{&quot;  a   b    &quot;, &quot;  a   b...&quot;, 2},

		{&quot;AB09C_D EFGH&quot;, &quot;AB09C_D...&quot;, 1},
		{&quot;Привет Гоферам&quot;, &quot;Привет...&quot;, 1},
		{&quot;Here are unicode spaces&quot;, &quot;Here are...&quot;, 2},
	}

	for i, c := range cases {
		got := TruncateByWords(c.in, c.n)
		if got != c.out {
			t.Fatalf(&quot;#%d: %q != %q&quot;, i, got, c.out)
		}
	}
}

答案6

得分: -2

str := "xxxx"
n := 2
if len(str) > n {
    fmt.Println(str[:n])
}

假设我们需要 ASCII 字符串的四分之一:

str[:len(str)/4]
英文:
str := &quot;xxxx&quot;
n := 2
if len(str) &gt; n {
    fmt.Println(str[:n])
}

lest say we need quarter of ascii string

str[:len(str)/4]

huangapple
  • 本文由 发表于 2014年5月5日 14:50:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/23466497.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定