Files
gf/text/gstr/gstr_convert.go

311 lines
7.1 KiB
Go
Raw Permalink Normal View History

2021-01-17 21:46:25 +08:00
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2020-02-12 10:48:15 +08:00
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gstr
import (
2021-12-21 23:24:38 +08:00
"bytes"
"fmt"
2020-02-12 10:48:15 +08:00
"regexp"
"strconv"
2021-12-21 23:24:38 +08:00
"strings"
"unicode"
2022-05-13 14:18:51 +08:00
"github.com/gogf/gf/v2/util/grand"
)
var (
// octReg is the regular expression object for checks octal string.
octReg = regexp.MustCompile(`\\[0-7]{3}`)
2020-02-12 10:48:15 +08:00
)
2021-12-21 23:24:38 +08:00
// Chr return the ascii string of a number(0-255).
//
// Example:
// Chr(65) -> "A"
2021-12-21 23:24:38 +08:00
func Chr(ascii int) string {
return string([]byte{byte(ascii % 256)})
}
// Ord converts the first byte of a string to a value between 0 and 255.
//
// Example:
// Chr("A") -> 65
2021-12-21 23:24:38 +08:00
func Ord(char string) int {
return int(char[0])
}
2020-02-12 10:48:15 +08:00
// OctStr converts string container octal string to its original string,
// for example, to Chinese string.
//
// Example:
// OctStr("\346\200\241") -> 怡
2020-02-12 10:48:15 +08:00
func OctStr(str string) string {
return octReg.ReplaceAllStringFunc(
str,
func(s string) string {
i, _ := strconv.ParseInt(s[1:], 8, 0)
return string([]byte{byte(i)})
},
)
}
2021-12-21 23:24:38 +08:00
// Reverse returns a string which is the reverse of `str`.
//
// Example:
// Reverse("123456") -> "654321"
2021-12-21 23:24:38 +08:00
func Reverse(str string) string {
runes := []rune(str)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
// NumberFormat formats a number with grouped thousands.
// Parameter `decimals`: Sets the number of decimal points.
// Parameter `decPoint`: Sets the separator for the decimal point.
// Parameter `thousandsSep`: Sets the thousands' separator.
2021-12-21 23:24:38 +08:00
// See http://php.net/manual/en/function.number-format.php.
//
// Example:
// NumberFormat(1234.56, 2, ".", "") -> 1234,56
// NumberFormat(1234.56, 2, ",", " ") -> 1 234,56
2021-12-21 23:24:38 +08:00
func NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string {
neg := false
if number < 0 {
number = -number
neg = true
}
// Will round off
str := fmt.Sprintf("%."+strconv.Itoa(decimals)+"F", number)
prefix, suffix := "", ""
if decimals > 0 {
prefix = str[:len(str)-(decimals+1)]
suffix = str[len(str)-decimals:]
} else {
prefix = str
}
sep := []byte(thousandsSep)
n, l1, l2 := 0, len(prefix), len(sep)
// thousands sep num
c := (l1 - 1) / 3
tmp := make([]byte, l2*c+l1)
pos := len(tmp) - 1
for i := l1 - 1; i >= 0; i, n, pos = i-1, n+1, pos-1 {
if l2 > 0 && n > 0 && n%3 == 0 {
for j := range sep {
tmp[pos] = sep[l2-j-1]
pos--
}
}
tmp[pos] = prefix[i]
}
s := string(tmp)
if decimals > 0 {
s += decPoint + suffix
}
if neg {
s = "-" + s
}
return s
}
// Shuffle randomly shuffles a string.
// It considers parameter `str` as unicode string.
//
// Example:
// Shuffle("123456") -> "325164"
// Shuffle("123456") -> "231546"
// ...
2021-12-21 23:24:38 +08:00
func Shuffle(str string) string {
runes := []rune(str)
s := make([]rune, len(runes))
for i, v := range grand.Perm(len(runes)) {
s[i] = runes[v]
}
return string(s)
}
// HideStr replaces part of the string `str` to `hide` by `percentage` from the `middle`.
// It considers parameter `str` as unicode string.
func HideStr(str string, percent int, hide string) string {
// Handle email case
var suffix string
if idx := strings.IndexByte(str, '@'); idx >= 0 {
suffix = str[idx:]
str = str[:idx]
2021-12-21 23:24:38 +08:00
}
// Early return for edge cases
if str == "" || percent <= 0 {
return str + suffix
}
if percent >= 100 {
return strings.Repeat(hide, len([]rune(str))) + suffix
}
rs := []rune(str)
length := len(rs)
if length == 0 {
return str + suffix
}
// Calculate hideLen using the same logic as original (with floor)
hideLen := (length * percent) / 100
if hideLen == 0 {
return str + suffix
2021-12-21 23:24:38 +08:00
}
// Calculate start position: mid - hideLen/2
// This matches the original algorithm behavior
mid := length / 2
start := max(mid-hideLen/2, 0)
end := start + hideLen
if end > length {
end = length
start = max(length-hideLen, 0)
2021-12-21 23:24:38 +08:00
}
// Pre-calculate capacity to avoid reallocations
var builder strings.Builder
builder.Grow(len(str) + len(hide)*hideLen + len(suffix))
// Build result string efficiently
builder.WriteString(string(rs[:start]))
if hide != "" {
builder.WriteString(strings.Repeat(hide, hideLen))
}
builder.WriteString(string(rs[end:]))
builder.WriteString(suffix)
return builder.String()
2021-12-21 23:24:38 +08:00
}
// Nl2Br inserts HTML line breaks(`br`|<br />) before all newlines in a string:
// \n\r, \r\n, \r, \n.
// It considers parameter `str` as unicode string.
func Nl2Br(str string, isXhtml ...bool) string {
r, n, runes := '\r', '\n', []rune(str)
var br []byte
if len(isXhtml) > 0 && isXhtml[0] {
br = []byte("<br />")
} else {
br = []byte("<br>")
}
skip := false
length := len(runes)
var buf bytes.Buffer
for i, v := range runes {
if skip {
skip = false
continue
}
switch v {
case n, r:
2022-07-25 20:54:42 +08:00
if (i+1 < length) && ((v == r && runes[i+1] == n) || (v == n && runes[i+1] == r)) {
2021-12-21 23:24:38 +08:00
buf.Write(br)
skip = true
continue
}
buf.Write(br)
default:
buf.WriteRune(v)
}
}
return buf.String()
}
// WordWrap wraps a string to a given number of characters.
2022-05-13 14:18:51 +08:00
// This function supports cut parameters of both english and chinese punctuations.
// TODO: Enable custom cut parameter, see http://php.net/manual/en/function.wordwrap.php.
2021-12-21 23:24:38 +08:00
func WordWrap(str string, width int, br string) string {
if br == "" {
br = "\n"
}
var (
current int
wordBuf, spaceBuf bytes.Buffer
init = make([]byte, 0, len(str))
buf = bytes.NewBuffer(init)
)
for _, char := range str {
2022-05-13 14:18:51 +08:00
switch {
case char == '\n':
2021-12-21 23:24:38 +08:00
if wordBuf.Len() == 0 {
if current+spaceBuf.Len() > width {
current = 0
} else {
current += spaceBuf.Len()
_, _ = spaceBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
}
spaceBuf.Reset()
} else {
current += spaceBuf.Len() + wordBuf.Len()
_, _ = spaceBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
spaceBuf.Reset()
_, _ = wordBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
wordBuf.Reset()
}
buf.WriteRune(char)
current = 0
2022-05-13 14:18:51 +08:00
case unicode.IsSpace(char):
2021-12-21 23:24:38 +08:00
if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
current += spaceBuf.Len() + wordBuf.Len()
_, _ = spaceBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
spaceBuf.Reset()
_, _ = wordBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
wordBuf.Reset()
}
spaceBuf.WriteRune(char)
2022-05-13 14:18:51 +08:00
case isPunctuation(char):
wordBuf.WriteRune(char)
if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
current += spaceBuf.Len() + wordBuf.Len()
_, _ = spaceBuf.WriteTo(buf)
2022-05-13 14:18:51 +08:00
spaceBuf.Reset()
_, _ = wordBuf.WriteTo(buf)
2022-05-13 14:18:51 +08:00
wordBuf.Reset()
}
default:
2021-12-21 23:24:38 +08:00
wordBuf.WriteRune(char)
if current+spaceBuf.Len()+wordBuf.Len() > width && wordBuf.Len() < width {
buf.WriteString(br)
current = 0
spaceBuf.Reset()
}
}
}
if wordBuf.Len() == 0 {
if current+spaceBuf.Len() <= width {
_, _ = spaceBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
}
} else {
_, _ = spaceBuf.WriteTo(buf)
_, _ = wordBuf.WriteTo(buf)
2021-12-21 23:24:38 +08:00
}
return buf.String()
}
2022-05-13 14:18:51 +08:00
func isPunctuation(char int32) bool {
switch char {
// English Punctuations.
case ';', '.', ',', ':', '~':
return true
// Chinese Punctuations.
case '', '', '。', '', '', '', '…', '、':
return true
default:
return false
}
}