如何在golang中使用椭圆形截断文本?

7

我希望能够干净地切割一个超过一定字符数的段落,而不在中间切割单词。

例如:

长期以来,人们一直认为阅读者在查看页面布局时会被其可读内容所分心。使用Lorem Ipsum的重点是它具有相对正常的字母分布,而不是使用“Content here,content here”,使其看起来像可读的英语。

应该变成:

长期以来,人们一直认为阅读者在查看页面布局时会被其可读内容所分心...

这是我想出来的函数:

 func truncateText(s string, max int) string {
    if len(s) > max {
        r := 0
        for i := range s {
            r++
            if r > max {
                return s[:i]
            }
        }
    }
    return s
}

但它只是粗暴地切断了文本。我想知道如何修改它(或用更好的解决方案替换它),以便将文本椭圆形地切断?


很好地使用了“椭圆地”这个词 :o) - rustyMagnet
6个回答

9

对字符串进行切片可能会存在问题,因为切片是按字节计算的,而不是按字符计算的。但是,使用range函数可以按字符来处理:

lastSpaceIx:=-1
len:=0
for i,r:=range str {
  if unicode.IsSpace(r) {
     lastSpaceIx=i
  }
  len++
  if len>=max {
    if lastSpaceIx!=-1 {
        return str[:lastSpaceIx]+"..."
    }
    // If here, string is longer than max, but has no spaces
  }
}
// If here, string is shorter than max

6

按照现有写法,范围是完全不必要的;因此,你的整个函数可以简化为:

func truncateText(s string, max int) string {
    return s[:max]
}

这个功能非常简单,甚至不需要成为一个函数;但是它也会截断单词,而你又说你不想要这个。因此,你可以尝试以下方法:

func truncateText(s string, max int) string {
    if max > len(s) {
        return s
    }
    return s[:strings.LastIndex(s[:max]," ")]
}

或者,如果你想要使用多个字符作为单词边界而不仅仅是空格:

func truncateText(s string, max int) string {
    if max > len(s) {
        return s
    }
    return s[:strings.LastIndexAny(s[:max]," .,:;-")]
}

3
这可能不是多字节安全的。 - Burak Serdar
4
字符串切片操作将字符串视为字节序列。如果字符串具有多字节字符,则 s[:max] 不是正确的切割位置。 - Burak Serdar
6
@Adrian,感谢您的提示,但这有一些缺点:1. 当字符串中有换行符时会出错。2. 当文本长度小于“max”时会引发恐慌。 - Smn
这个似乎处理多字节字符串。 - Curtis Mattoon
如果长度小于 max,你的第一个函数s[:max]将会引发错误。 - The 0bserver
显示剩余2条评论

1
我在Burak的答案基础上进行了改进。如果len(text)=maxLen,那么这个实现将返回与输入字符串完全相同的字符串,而不是添加省略号;如果文本中没有空格,则直接在maxLen处进行硬截断。
func EllipticalTruncate(text string, maxLen int) string {
    lastSpaceIx := maxLen
    len := 0
    for i, r := range text {
        if unicode.IsSpace(r) {
            lastSpaceIx = i
        }
        len++
        if len > maxLen {
            return text[:lastSpaceIx] + "..."
        }
    }
    // If here, string is shorter or equal to maxLen
    return text
}

测试用例

func TestEllipticalTruncate(t *testing.T) {
    assert.Equal(t, "...", EllipticalTruncate("1 2 3", 0))
    assert.Equal(t, "1...", EllipticalTruncate("1 2 3", 1))
    assert.Equal(t, "1...", EllipticalTruncate("1 2 3", 2))
    assert.Equal(t, "1 2...", EllipticalTruncate("1 2 3", 3))
    assert.Equal(t, "1 2 3", EllipticalTruncate("1 2 3", 5))
}

0
以下解决方案避免了范围问题,但考虑了多字节符号:
func ellipsis(s string, maxLen int) string {
    runes := []rune(s)
    if len(runes) <= maxLen {
        return s
    }
    if maxLen < 3 {
        maxLen = 3
    }
    return string(runes[0:maxLen-3]) + "..."
}

请查看https://go.dev/play/p/ibj6aK7N0rc


0

我提供非常简单的变体。

https://go.dev/play/p/Pbk5DchjReT

func ShortText(s string, i int) string {
    if len(s) < i {
        return s
    }

    if utf8.ValidString(s[:i]) {
        return s[:i]
    }
    return s[:i+1]

}

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community
不适用于3或4字节的符文。 - undefined

-2

如果要按空格和其他字符进行分割,可以使用正则表达式:

func splitString(str string) []string {
    re := regexp.MustCompile("[\\s\\n\\t\\r ]+") //split according to \s, \t, \r, \t and whitespace. Edit this regex for other 'conditions'

    split := re.Split(str, -1)
    return split
}

func main() {
    var s = "It is a long\nestablished fact that a reader\nwill\nbe distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."
    var maxLen = 40

    arr := splitString(s)

    totalLen := 0
    finalStr := ``
    for _, each := range arr {
        if (totalLen + len(each) > maxLen) {
            fmt.Print(strings.TrimSpace(finalStr) + `...`)
            break
        }
        totalLen += len(each)
        finalStr += each + ` `

    }
}

//旧2

你可以这样做:将你的字符串分割成片段,循环遍历这些片段,直到你的字符串总长度超过允许的最大长度。

    var s = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."
    var maxLen = 30

    arr := strings.SplitAfter(s, ` `)

    totalLen := 0
    finalStr := ``
    for _, each := range arr {
        if (totalLen + len(each) > maxLen) {
            fmt.Print(strings.TrimSpace(finalStr) + `...`)
            break
        }
        totalLen += len(each)
        finalStr += each

    }

这是一个被广泛认可的事实...


//错误的旧答案
你必须处理字符串和切片:

    var s = "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."

    newS := s[:30 - 3] 
    newS += `...`
    fmt.Print(newS)

结果: 这是一个长期存在的事实,当一些字母被排列在一起时,它们会产生有意义的文本,而不仅仅是随机字符的组合。


我想要能够干净地切割一个超过一定字符数的段落,而不会在单词中间切断。 - Adrian
我的错,我更新了我的答案,并提供了可行的解决方案。 - TBouder
易于理解,但不考虑文本中存在换行符的情况。因此不够健壮。 - Smn
如果您想按空格、换行符、制表符等进行分割,您需要在期望的值后拆分原始字符串。我已经更新了我的原始答案。 - TBouder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接