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

时间:2014-05-05 06:50:05

标签: go

在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。整数mi arcu。 Pellentesque a ipsum quis velit venenatis vulputate vulputate ut enim。

我想将其减少到25个字符。

7 个答案:

答案 0 :(得分:31)

您可以在模板中使用printf,模板充当fmt.Sprintf。在您的情况下,截断字符串将非常简单:

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

答案 1 :(得分:7)

更新:现在,以下代码符合unicode标准,适用于使用国际计划的用户。

需要注意的一点是,下面的bytes.Runes(“string”)是一个O(N)操作,从符文到字符串的转换也是如此,因此这段代码在字符串上循环两次。为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
    //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)
}

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

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

另一个选项是创建一个函数,该函数将接受字符串的前25个字符。代码看起来像这样(由@MartinDrLí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)
    }

}

输出:

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

答案 2 :(得分:3)

需要更多魔术用于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]
    }
    // The omission.
    // In reality, a rune can have 1-4 bytes width (not 1 or 2)
    return s[:i+1] // or i-1
}

但上面的i不是字符的数量。这是字节数。在play.golang.org

上链接到此代码

我希望这会有所帮助。


修改

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

请参阅that回答,然后播放here。这是另一种解决方案。

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。此函数从不会发生混乱(如果n> = 0),但您可以获得空字符串play.golang.org


另外,请记住这个实验包golang.org/x/exp/utf8string

  

包utf8string提供了一种通过符文而不是按字节索引字符串的有效方法。

答案 3 :(得分:3)

您可以使用documentation中的slice。以下示例必须有效:

NAME                  READY   STATUS    RESTARTS   AGE
pod/elasticsearch-0   0/1     Pending   0          45m


NAME                    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
service/elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP   30h




NAME                             READY   AGE
statefulset.apps/elasticsearch   0/3     45m


Name:           elasticsearch-0
Namespace:      logging
Priority:       0
Node:           <none>
Labels:         app=elasticsearch
                controller-revision-hash=elasticsearch-6dd997c6d8
                statefulset.kubernetes.io/pod-name=elasticsearch-0
Annotations:    <none>
Status:         Pending
IP:
Controlled By:  StatefulSet/elasticsearch
Init Containers:
  fix-permissions:
    Image:      busybox
    Port:       <none>
    Host Port:  <none>
    Command:
      sh
      -c
      chown -R 1000:1000 /usr/share/elasticsearch/data
    Environment:  <none>
    Mounts:
      /usr/share/elasticsearch/data from data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gvqz5 (ro)
  increase-vm-max-map:
    Image:      busybox
    Port:       <none>
    Host Port:  <none>
    Command:
      sysctl
      -w
      vm.max_map_count=262144
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gvqz5 (ro)
  increase-fd-ulimit:
    Image:      busybox
    Port:       <none>
    Host Port:  <none>
    Command:
      sh
      -c
      ulimit -n 65536
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gvqz5 (ro)
Containers:
  elasticsearch:
    Image:       docker.elastic.co/elasticsearch/elasticsearch:7.2.0
    Ports:       9200/TCP, 9300/TCP
    Host Ports:  0/TCP, 0/TCP
    Limits:
      cpu:  1
    Requests:
      cpu:  100m
    Environment:
      cluster.name:                  k8s-logs
      node.name:                     elasticsearch-0 (v1:metadata.name)
      discovery.seed_hosts:          elasticsearch-0.elasticsearch,elasticsearch-1.elasticsearch,elasticsearch-2.elasticsearch
      cluster.initial_master_nodes:  elasticsearch-0,elasticsearch-1,elasticsearch-2
      ES_JAVA_OPTS:                  -Xms512m -Xmx512m
    Mounts:
      /usr/share/elasticsearch/data from data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gvqz5 (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  data:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  data-elasticsearch-0
    ReadOnly:   false
  default-token-gvqz5:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-gvqz5
    Optional:    false
QoS Class:       Burstable
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age                   From               Message
  ----     ------            ----                  ----               -------
  Warning  FailedScheduling  3m39s (x44 over 64m)  default-scheduler  pod has unbound immediate PersistentVolumeClaims

答案 4 :(得分:1)

您可以定义一个功能。看看http://golang.org/pkg/text/template/#example_Template_func

编辑: 在操场上看到它:http://play.golang.org/p/OP2x5vDCtn

答案 5 :(得分:0)

有很多很好的答案,但有时候在不切词的情况下截断会更方便用户。雨果为此提供template function。 但是在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 !isSeparator(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) {
                // Source string ending is shorter than "..."
                return s
            }

            return s[:i] + ending
        }

        i += width
    }

    // Source string contains less words count than 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)
        }
    }
}

答案 6 :(得分:0)

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

别说我们需要四分之一的ASCII字符串

str[:len(str)/4]