Skip to content

Commit

Permalink
Convert punctuation into Fullwidth near the CJK (#6)
Browse files Browse the repository at this point in the history
* Auto correct punctuation into fullwidth.
* Update document
  • Loading branch information
huacnlee authored Oct 27, 2020
1 parent c7dc365 commit f5c5e96
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 34 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
0.10.0
--------

- Auto correct punctuation into fullwidth.

0.9.0
--------

Expand All @@ -22,12 +27,12 @@
0.6.1
--------

- Fix Halfwidth to correct fullwidth spaces.
- Fix haftwidth to correct fullwidth spaces.

0.6.0
--------

- Auto correct FullWidth -> HalfWidth for Letters, Numbers, and Colon in time.
- Auto correct FullWidth -> haftwidth for Letters, Numbers, and Colon in time.

0.4.1
--------
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Automatically add whitespace between CJK (Chinese, Japanese, Korean) and half-wi

- Auto add spacings between CJK (Chinese, Japanese, Korean) and English words.
- HTML content support.
- FullWidth -> HalfWidth (only for [a-zA-Z0-9], and `` in time).
- Fullwidth -> Haftwidth (only for [a-zA-Z0-9], and `` in time).
- Correct punctuations into Fullwidth near the CJK.
- Cleanup spacings.

## Usage
Expand Down Expand Up @@ -54,8 +55,8 @@ func main() {
autocorrect.Format("프로덕션환경에서Go사용")
# => "프로덕션환경에서 Go 사용"

autocorrect.Format("自动转换全角字符、数字我们将在16:32分出发去CBD中心")
# => "自动转换全角字符、数字:我们将在 16:32 分出发去 CBD 中心。"
autocorrect.Format("需要符号?自动转换全角字符、数字:我们将在16:32分出发去CBD中心.")
# => "需要符号?自动转换全角字符、数字:我们将在 16:32 分出发去 CBD 中心。"
}
```

Expand Down Expand Up @@ -98,7 +99,7 @@ pkg: github.com/huacnlee/go-auto-correct
BenchmarkFormat50-12 19671 60175 ns/op
BenchmarkFormat100-12 10000 119076 ns/op
BenchmarkFormat400-12 2847 424984 ns/op
Benchmark_halfWidth-12 289411 4150 ns/op
Benchmark_haftwidth-12 289411 4150 ns/op
BenchmarkFormatHTML-12 1100 1097027 ns/op
```

Expand Down
24 changes: 13 additions & 11 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import (
)

const (
cjkRe = `\p{Han}|\p{Hangul}|\p{Hanunoo}|\p{Katakana}|\p{Hiragana}|\p{Bopomofo}`
cjk = `\p{Han}|\p{Hangul}|\p{Hanunoo}|\p{Katakana}|\p{Hiragana}|\p{Bopomofo}`
spaceRe = `[ ]`
)

var (
// Strategies all rules
strategies []*strategery
cjkRe = regexp.MustCompile("[" + cjk + "]")
fullDateRe = regexp.MustCompile(spaceRe + `{0,}\d+` + spaceRe + `{0,}年` + spaceRe + `{0,}\d+` + spaceRe + `{0,}月` + spaceRe + `{0,}\d+` + spaceRe + `{0,}[日号]` + spaceRe + `{0,}`)
dashHansRe = regexp.MustCompile(`([` + cjkRe + `)】」》”’])([\-]+)([` + cjkRe + `(【「《“‘])`)
dashHansRe = regexp.MustCompile(`([` + cjk + `)】」》”’])([\-]+)([` + cjk + `(【「《“‘])`)
leftQuoteRe = regexp.MustCompile(spaceRe + `([(【「《])`)
rightQuoteRe = regexp.MustCompile(`([)】」》])` + spaceRe)
)
Expand All @@ -25,21 +26,21 @@ func registerStrategery(one, other string, space, reverse bool) {

func init() {
// EnglishLetter
registerStrategery(cjkRe, `[a-zA-Z]`, true, true)
registerStrategery(cjk, `[a-zA-Z]`, true, true)

// Number
registerStrategery(cjkRe, `[0-9]`, true, true)
registerStrategery(cjk, `[0-9]`, true, true)

// SpecialSymbol
registerStrategery(cjkRe, `[\|+*]`, true, true)
registerStrategery(cjkRe, `[@]`, true, false)
registerStrategery(cjkRe, `[\[\(‘“]`, true, false)
registerStrategery(`[’”\]\)!%]`, cjkRe, true, false)
registerStrategery(cjk, `[\|+*]`, true, true)
registerStrategery(cjk, `[@]`, true, false)
registerStrategery(cjk, `[\[\(‘“]`, true, false)
registerStrategery(`[’”\]\)!%]`, cjk, true, false)
registerStrategery(`[”\]\)!]`, `[a-zA-Z0-9]+`, true, false)

// FullwidthPunctuation
registerStrategery(`[\w`+cjkRe+`]`, `[,。!?:;)」》】”’]`, false, true)
registerStrategery(`[‘“【「《(]`, `[\w`+cjkRe+`]`, false, true)
registerStrategery(`[\w`+cjk+`]`, `[,。!?:;)」》】”’]`, false, true)
registerStrategery(`[‘“【「《(]`, `[\w`+cjk+`]`, false, true)
}

// removeFullDateSpacing
Expand All @@ -64,7 +65,8 @@ func spaceDashWithHans(in string) (out string) {
func Format(in string) (out string) {
out = in

out = halfWidth(out)
out = haftwidth(out)
out = fullwidth(out)

for _, s := range strategies {
out = s.format(out)
Expand Down
16 changes: 8 additions & 8 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestFormat(t *testing.T) {
"[北京]美企聘site/web大型应用开发高手-Ruby": "[北京] 美企聘 site/web 大型应用开发高手-Ruby",
"[成都](团800)招聘Rails工程师": "[成都](团 800) 招聘 Rails 工程师",
"Teahour.fm第18期发布": "Teahour.fm 第 18 期发布",
"Yes!升级到了Rails 4": "Yes! 升级到了 Rails 4",
"Yes!升级到了Rails 4": "Yes升级到了 Rails 4",
"WWDC上讲到的Objective C/LLVM改进": "WWDC 上讲到的 Objective C/LLVM 改进",
"在Ubuntu11.10 64位系统安装newrelic出错": "在 Ubuntu11.10 64 位系统安装 newrelic 出错",
"升级了macOS 10.9 附遇到的Bug概率有0.1%或更少": "升级了 macOS 10.9 附遇到的 Bug 概率有 0.1% 或更少",
Expand All @@ -63,7 +63,7 @@ func TestFormat(t *testing.T) {

func TestFormatForSpecialChars(t *testing.T) {
cases := map[string]string{
"记事本,记事本显示阅读次数#149": "记事本,记事本显示阅读次数#149",
"记事本,记事本显示阅读次数#149": "记事本记事本显示阅读次数#149",
"HashTag的演示 #标签": "HashTag 的演示 #标签",
"HashTag 的演示 #标签# 演示": "HashTag 的演示 #标签# 演示",
"Mention里面有关于中文的@某某人": "Mention 里面有关于中文的 @某某人",
Expand Down Expand Up @@ -113,7 +113,7 @@ func TestFormatForNumber(t *testing.T) {

func TestFormatForSpecialSymbols(t *testing.T) {
cases := map[string]string{
"公告:(美股)阿里巴巴[BABA.US]发布2019下半年财报!": "公告:(美股) 阿里巴巴 [BABA.US] 发布 2019 下半年财报!",
"公告:(美股)阿里巴巴[BABA.US]发布2019下半年财报!": "公告:(美股) 阿里巴巴 [BABA.US] 发布 2019 下半年财报",
"消息http://github.com解禁了": "消息 http://github.com 解禁了",
"美股异动|阿帕奇石油(APA.US)盘前涨超15% 在苏里南近海发现大量石油": "美股异动 | 阿帕奇石油 (APA.US) 盘前涨超 15% 在苏里南近海发现大量石油",
"美国统计局:美国11月原油出口下降至302.3万桶/日,10月为338.3万桶/日。": "美国统计局:美国 11 月原油出口下降至 302.3 万桶/日,10 月为 338.3 万桶/日。",
Expand Down Expand Up @@ -145,15 +145,15 @@ func TestFormat_spaceDashWithHans(t *testing.T) {

func TestFormat_CJK(t *testing.T) {
cases := map[string]string{
"全世界已有数百家公司在生产环境中使用Rust,以达到快速、跨平台、低资源占用的目的。很多著名且受欢迎的软件,例如Firefox、 Dropbox和Cloudflare都在使用Rust。": "全世界已有数百家公司在生产环境中使用 Rust,以达到快速、跨平台、低资源占用的目的。很多著名且受欢迎的软件,例如 Firefox、 Dropbox 和 Cloudflare 都在使用 Rust。",
"現今全世界上百家公司企業為了尋求快速、節約資源而且能跨平台的解決辦法,都已在正式環境中使用Rust。許多耳熟能詳且受歡迎的軟體,諸如Firefox、Dropbox以及Cloudflare都在使用Rust。": "現今全世界上百家公司企業為了尋求快速、節約資源而且能跨平台的解決辦法,都已在正式環境中使用 Rust。許多耳熟能詳且受歡迎的軟體,諸如 Firefox、Dropbox 以及 Cloudflare 都在使用 Rust。",
"既に、世界中の数百という企業がRustを採用し、高速で低リソースのクロスプラットフォームソリューションを実現しています。皆さんがご存じで愛用しているソフトウェア、例えばFirefox、DropboxやCloudflareも、Rustを採用しています。": "既に、世界中の数百という企業が Rust を採用し、高速で低リソースのクロスプラットフォームソリューションを実現しています。皆さんがご存じで愛用しているソフトウェア、例えば Firefox、Dropbox や Cloudflare も、Rust を採用しています。",
"전 세계 수백 개의 회사가 프로덕션 환경에서 Rust를 사용하여 빠르고, 크로스 플랫폼 및 낮은 리소스 사용량을 달성했습니다. Firefox, Dropbox 및 Cloudflare와 같이 잘 알려져 있고 널리 사용되는 많은 소프트웨어가 Rust를 사용하고 있습니다.": "전 세계 수백 개의 회사가 프로덕션 환경에서 Rust 를 사용하여 빠르고, 크로스 플랫폼 및 낮은 리소스 사용량을 달성했습니다. Firefox, Dropbox 및 Cloudflare 와 같이 잘 알려져 있고 널리 사용되는 많은 소프트웨어가 Rust 를 사용하고 있습니다.",
"全世界已有数百家公司在生产环境中使用Rust,以达到快速、跨平台、低资源占用的目的。很多著名且受欢迎的软件,例如Firefox、 Dropbox和Cloudflare都在使用Rust。": "全世界已有数百家公司在生产环境中使用 Rust,以达到快速、跨平台、低资源占用的目的。很多著名且受欢迎的软件,例如 Firefox、 Dropbox 和 Cloudflare 都在使用 Rust。",
"現今全世界上百家公司企業為了尋求快速、節約資源而且能跨平台的解決辦法,都已在正式環境中使用Rust。許多耳熟能詳且受歡迎的軟體,諸如Firefox、Dropbox以及Cloudflare都在使用Rust。": "現今全世界上百家公司企業為了尋求快速、節約資源而且能跨平台的解決辦法,都已在正式環境中使用 Rust。許多耳熟能詳且受歡迎的軟體,諸如 Firefox、Dropbox 以及 Cloudflare 都在使用 Rust。",
"既に、世界中の数百という企業がRustを採用し、高速で低リソースのクロスプラットフォームソリューションを実現しています。皆さんがご存じで愛用しているソフトウェア、例えばFirefox、DropboxやCloudflareも、Rustを採用しています。": "既に、世界中の数百という企業が Rust を採用し、高速で低リソースのクロスプラットフォームソリューションを実現しています。皆さんがご存じで愛用しているソフトウェア、例えば Firefox、Dropbox や Cloudflare も、Rust を採用しています。",
"전 세계 수백 개의 회사가 프로덕션 환경에서 Rust를 사용하여 빠르고,크로스 플랫폼 및 낮은 리소스 사용량을 달성했습니다.다Firefox,Dropbox 및 Cloudflare와 같이 잘 알려져 있고 널리 사용되는 많은 소프트웨어가 Rust를 사용하고 있습니다.": "전 세계 수백 개의 회사가 프로덕션 환경에서 Rust 를 사용하여 빠르고크로스 플랫폼 및 낮은 리소스 사용량을 달성했습니다。다 Firefox,Dropbox 및 Cloudflare 와 같이 잘 알려져 있고 널리 사용되는 많은 소프트웨어가 Rust 를 사용하고 있습니다",
}
assertCases(t, cases)
}

func TestFormatWithHalfWidth(t *testing.T) {
func TestFormatWithhaftwidth(t *testing.T) {
text := `自动转换全角“字符、数字”:我们将在(16:32)出发去CBD中心。`
out := Format(text)
assertEqual(t, "自动转换全角 “字符、数字”:我们将在(16:32)出发去 CBD 中心。", out)
Expand Down
50 changes: 50 additions & 0 deletions fullwidth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package autocorrect

import (
"regexp"
)

var (
fullwidthMaps = map[string]string{
",": ",",
".": "。",
";": ";",
":": ":",
"!": "!",
"?": "?",
"~": "~",
// "(": "(",
// ")": ")",
}

spcialPunctuations = `[.:]`
normalPunctuations = `[,;\!\?~]`

punctuationWithLeftCJKRe = regexp.MustCompile(normalPunctuations + `[` + cjk + `]+`)
punctuationWithRightCJKRe = regexp.MustCompile(`[` + cjk + `]+` + normalPunctuations)
punctuationWithSpeicalCJKRe = regexp.MustCompile(`[` + cjk + `]+` + spcialPunctuations + `[` + cjk + `]+`)
punctuationWithSpeicalLastCJKRe = regexp.MustCompile(`[` + cjk + `]+` + spcialPunctuations + "$")
punctuationsRe = regexp.MustCompile(`(` + spcialPunctuations + `|` + normalPunctuations + `)`)
)

// fullwidth correct punctuations near the CJK chars
func fullwidth(text string) (out string) {
out = text

out = punctuationWithLeftCJKRe.ReplaceAllStringFunc(out, fullwidthReplacePart)
out = punctuationWithRightCJKRe.ReplaceAllStringFunc(out, fullwidthReplacePart)
out = punctuationWithSpeicalCJKRe.ReplaceAllStringFunc(out, fullwidthReplacePart)
out = punctuationWithSpeicalLastCJKRe.ReplaceAllStringFunc(out, fullwidthReplacePart)

return
}

func fullwidthReplacePart(part string) string {
part = punctuationsRe.ReplaceAllStringFunc(part, func(str string) string {
str = fullwidthMaps[str]
return str
})

return part

}
22 changes: 22 additions & 0 deletions fullwidth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package autocorrect

import (
"testing"
)

func Test_fullwidth(t *testing.T) {
cases := map[string]string{
"你好,这是一个句子.": "你好,这是一个句子。",
"刚刚买了一部iPhone,好开心!": "刚刚买了一部 iPhone,好开心!",
"蚂蚁集团上市后有多大的上涨空间?": "蚂蚁集团上市后有多大的上涨空间?",
"我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架(如 Backbone.js、AngularJS、React 等)的前端开发者.": "我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架 (如 Backbone.js、AngularJS、React 等) 的前端开发者。",
"蚂蚁疾奔:蚂蚁集团两地上市~全速推进!": "蚂蚁疾奔:蚂蚁集团两地上市~全速推进!",
"蚂蚁集团是阿里巴巴(BABA.N)旗下金融科技子公司": "蚂蚁集团是阿里巴巴 (BABA.N) 旗下金融科技子公司",
"Dollar的演示 $阿里巴巴.US$ 股票标签": "Dollar 的演示 $阿里巴巴.US$ 股票标签",
}

for source, exptected := range cases {
actual := Format(source)
assertEqual(t, exptected, actual)
}
}
2 changes: 1 addition & 1 deletion halfwidth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var (
halfTimeRe = regexp.MustCompile(`(\d)(:)(\d)`)
)

func halfWidth(text string) string {
func haftwidth(text string) string {
runes := []rune{}
for _, char := range text {
newChar := charWidthMap[char]
Expand Down
12 changes: 6 additions & 6 deletions halfwidth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import (
"github.com/stretchr/testify/assert"
)

func Test_halfWidth(t *testing.T) {
func Test_haftwidth(t *testing.T) {
source := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
assertEqual(t, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", halfWidth(source))
assertEqual(t, "他说:我们将在16:32分出发去CBD中心。", halfWidth("他说:我们将在16:32分出发去CBD中心。"))
assertEqual(t, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", haftwidth(source))
assertEqual(t, "他说:我们将在16:32分出发去CBD中心。", haftwidth("他说:我们将在16:32分出发去CBD中心。"))
// Fullwidth space
assert.Equal(t, "ジョイフル-後場売り気配 200 店舗を閉鎖へ 7 月以降、不採算店中心に", halfWidth("ジョイフル-後場売り気配 200 店舗を閉鎖へ 7 月以降、不採算店中心に"))
assert.Equal(t, "ジョイフル-後場売り気配 200 店舗を閉鎖へ 7 月以降、不採算店中心に", haftwidth("ジョイフル-後場売り気配 200 店舗を閉鎖へ 7 月以降、不採算店中心に"))
}

func Benchmark_halfWidth(b *testing.B) {
func Benchmark_haftwidth(b *testing.B) {
source := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
for i := 0; i < b.N; i++ {
// about 0.003ms/op
halfWidth(source)
haftwidth(source)
}
}
2 changes: 1 addition & 1 deletion html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestFormatHTMLWithEscapedHTML(t *testing.T) {
assertHTMLEqual(t, expected, out)
}

func TestFormatHTML_HalfWidth(t *testing.T) {
func TestFormatHTML_haftwidth(t *testing.T) {
html := `<p>自动转换全角“字符、数字”:我们将在(16:32)出发去CBD中心。</p>`

out, err := FormatHTML(html)
Expand Down
2 changes: 1 addition & 1 deletion unformat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package autocorrect
import "regexp"

var (
removeSpaceRe = regexp.MustCompile(`(` + spaceRe + `+)?(` + cjkRe + `)(` + spaceRe + `+)?`)
removeSpaceRe = regexp.MustCompile(`(` + spaceRe + `+)?(` + cjk + `)(` + spaceRe + `+)?`)
)

// Unformat to remove all spaces
Expand Down

0 comments on commit f5c5e96

Please sign in to comment.