evalphobia/go-jp-text-ripper

View on GitHub
prefilter/neologd.go

Summary

Maintainability
A
0 mins
Test Coverage
package prefilter

import (
    "bytes"
    "strings"
    "unicode"
    "unicode/utf8"

    "github.com/evalphobia/go-jp-text-ripper/ripper"
)

// Neologd is prefilter to normalize text by neologd recommended format
var Neologd = &ripper.PreFilter{
    Fn: func(rawText string) string {
        return NormalizeNeologd(rawText)
    },
}

// Most of code logic is from https://github.com/ikawaha/x/neologd/neologd.go
const (
    prolongedSoundMark = '\u30FC'
)

var latinSymbols = &unicode.RangeTable{
    R16: []unicode.Range16{
        {0x0021, 0x0040, 1},
        {0x005B, 0x0060, 1},
        {0x007B, 0x007E, 1},
    },
}

var neologdReplacer = strings.NewReplacer(
    "0", "0", "1", "1", "2", "2", "3", "3", "4", "4",
    "5", "5", "6", "6", "7", "7", "8", "8", "9", "9",

    "A", "A", "B", "B", "C", "C", "D", "D", "E", "E",
    "F", "F", "G", "G", "H", "H", "I", "I", "J", "J",
    "K", "K", "L", "L", "M", "M", "N", "N", "O", "O",
    "P", "P", "Q", "Q", "R", "R", "S", "S", "T", "T",
    "U", "U", "V", "V", "W", "W", "X", "X", "Y", "Y",
    "Z", "Z",

    "a", "a", "b", "b", "c", "c", "d", "d", "e", "e",
    "f", "f", "g", "g", "h", "h", "i", "i", "j", "j",
    "k", "k", "l", "l", "m", "m", "n", "n", "o", "o",
    "p", "p", "q", "q", "r", "r", "s", "s", "t", "t",
    "u", "u", "v", "v", "w", "w", "x", "x", "y", "y",
    "z", "z",

    //small case
    "ァ", "ァ", "ィ", "ィ", "ゥ", "ゥ", "ェ", "ェ", "ォ", "ォ",
    "ャ", "ャ", "ュ", "ュ", "ョ", "ョ", "ッ", "ッ",

    "ア", "ア", "イ", "イ", "ウ", "ウ", "エ", "エ", "オ", "オ",
    "ガ", "ガ", "ギ", "ギ", "グ", "グ", "ゲ", "ゲ", "ゴ", "ゴ",
    "カ", "カ", "キ", "キ", "ク", "ク", "ケ", "ケ", "コ", "コ",
    "ザ", "ザ", "ジ", "ジ", "ズ", "ズ", "ゼ", "ゼ", "ゾ", "ゾ",
    "サ", "サ", "シ", "シ", "ス", "ス", "セ", "セ", "ソ", "ソ",
    "ダ", "ダ", "ヂ", "ヂ", "ヅ", "ヅ", "デ", "デ", "ド", "ド",
    "タ", "タ", "チ", "チ", "ツ", "ツ", "テ", "テ", "ト", "ト",
    "ナ", "ナ", "ニ", "ニ", "ヌ", "ヌ", "ネ", "ネ", "ノ", "ノ",
    "バ", "バ", "ビ", "ビ", "ブ", "ブ", "ベ", "ベ", "ボ", "ボ",
    "パ", "パ", "ピ", "ピ", "プ", "プ", "ペ", "ペ", "ポ", "ポ",
    "ハ", "ハ", "ヒ", "ヒ", "フ", "フ", "ヘ", "ヘ", "ホ", "ホ",
    "マ", "マ", "ミ", "ミ", "ム", "ム", "メ", "メ", "モ", "モ",
    "ヤ", "ヤ", "ユ", "ユ", "ヨ", "ヨ",
    "ラ", "ラ", "リ", "リ", "ル", "ル", "レ", "レ", "ロ", "ロ",
    "ワ", "ワ", "ヲ", "ヲ", "ン", "ン",

    // hyphen
    "\u02D7", "-", "\u058A", "-", "\u2010", "-", "\u2011", "-", "\u2012", "-",
    "\u2013", "-", "\u2043", "-", "\u207B", "-", "\u208B", "-", "\u2212", "-",

    // bar
    "\u2014", string(prolongedSoundMark), // エムダッシュ
    "\u2015", string(prolongedSoundMark), // ホリゾンタルバー
    "\u2500", string(prolongedSoundMark), // 横細罫線
    "\u2501", string(prolongedSoundMark), // 横太罫線
    "\uFE63", string(prolongedSoundMark), // SMALL HYPHEN-MINUS
    "\uFF0D", string(prolongedSoundMark), // 全角ハイフンマイナス
    "\uFF70", string(prolongedSoundMark), // 半角長音記号

    // tilde
    "~", "", "\u223C", "", "\u223E", "", "\u301C", "", "\u3030", "", "\uFF5E", "",

    // zen -> han
    "!", "!", "”", `"`, "#", "#", "$", "$", "%", "%",
    "&", "&", `’`, `'`, "(", "(", ")", ")", "*", "*",
    "+", "+", ",", ",", "−", "-", ".", ".", "/", "/",
    ":", ":", ";", ";", "<", "<", ">", ">", "?", "?",
    "@", "@", "[", "[", "¥", "\u00A5", "]", "]", "^", "^",
    "_", "_", "`", "`", "{", "{", "|", "|", "}", "}",
    " ", " ",

    // han -> zen
    "。", "。", "、", "、", "・", "・", "=", "=", "「", "「", "」", "」",

    "\n", " ", `\n`, " ", "\t", " ", `\t`, " ", "\v", " ", `\v`, " ",
)

// NormalizeNeologd normalizes text
func NormalizeNeologd(s string) string {
    s = neologdReplacer.Replace(s)
    s = eliminateSpace(s)
    s = shurinkProlongedSoundMark(s)
    return s
}

func eliminateSpace(s string) string {
    var (
        b    bytes.Buffer
        prev rune
    )
    for p := 0; p < len(s); {
        c, w := utf8.DecodeRuneInString(s[p:])
        p += w
        if !unicode.IsSpace(c) {
            _, err := b.WriteRune(c)
            if err == nil {
                prev = c
            }
            continue
        }
        for p < len(s) {
            c0, w0 := utf8.DecodeRuneInString(s[p:])
            p += w0
            if !unicode.IsSpace(c0) {
                if unicode.In(prev, unicode.Latin, latinSymbols) &&
                    unicode.In(c0, unicode.Latin, latinSymbols) {
                    _, _ = b.WriteRune(' ')
                }
                _, _ = b.WriteRune(c0)
                prev = c0
                break
            }
        }

    }
    return b.String()
}

func shurinkProlongedSoundMark(s string) string {
    var b bytes.Buffer
    for p := 0; p < len(s); {
        c, w := utf8.DecodeRuneInString(s[p:])
        p += w
        _, err := b.WriteRune(c)
        if err != nil || c != prolongedSoundMark {
            continue
        }
        for p < len(s) {
            c0, w0 := utf8.DecodeRuneInString(s[p:])
            p += w0
            if c0 != prolongedSoundMark {
                _, _ = b.WriteRune(c0)
                break
            }
        }

    }
    return b.String()
}