Skip to content

Instantly share code, notes, and snippets.

@kyu999
Last active August 29, 2015 14:05
Show Gist options
  • Save kyu999/5bb18b231494c3ded7cc to your computer and use it in GitHub Desktop.
Save kyu999/5bb18b231494c3ded7cc to your computer and use it in GitHub Desktop.
改行ルールほぼ最終
separate編:
1. 助詞もしくは副詞の後は切る
2. 記号のあとは切る
3. 長いと切る
regulate編:
1. 左括弧の前で切れてたらくっつける
2. 記号の連続、助詞の連続の場合もくっつける
3. 右括弧以外の記号の前ではくっつける
@kyu999
Copy link
Author

kyu999 commented Aug 10, 2014

-- coding:utf-8 --

import re
import unicodedata
import MeCab
import os
import title_cleaner

class SpeechTitleSplitter(object):
"""This class split a given title and produces split points for beatiful rendering of that by the simple way"""

def __init__(self):
    self.hiragana_pattern = re.compile(u'[ぁ-ん]+')
    self.kanji_pattern = re.compile(u'[一-龠]+')
    self.katakana_pattern = re.compile(u'[ァ-ヴ]+')
    self.alphanumeric_pattern = re.compile(u'[a-zA-Z0-9]+')
    #開閉の区分けがある全角クォーテーションをleft_kakko, right_kakkoにいれているが、これはテキストエディタの自動変換によって生じたものなのでもし入り込まないと断定できるならこのパターン系には一切入れる必要がなくregulateの中で対処する。逆に必ず入り込むようなことがあるのならregulateの方を変更する。
    self.kigou_pattern = re.compile(u'[!?:;/〜。、!\#$%&\*+,-./・:;<==>?]+')
    self.left_kakko_pattern = re.compile(u'([\[\{{[「『((⦅〈《〔〘【〖<])')
    self.right_kakko_pattern = re.compile(u'[}]」』))⦆〉》〕〙】〗>]+')

def is_kind_base(self, pattern, letters):
    """
    文字種かどうかを調べるための関数のベース関数。判定は1文字でも含まれてたらTrue, else False
    1文字ごとの判定にはこれを活用できる。
    """
    if pattern.match(letters):
        return True
    else:
        return False

def is_hiragana(self, letters):
    return self.is_kind_base(self.hiragana_pattern, letters)

def is_kanji(self, letters):
    return self.is_kind_base(self.kanji_pattern, letters)

def is_katakana(self, letters):
    return self.is_kind_base(self.katakana_pattern, letters)

def is_alphanumeric(self, letters):
    return self.is_kind_base(self.alphanumeric_pattern, letters)

def is_kigou(self, letters):
    return self.is_kind_base(self.kigou_pattern, letters)

def is_left_kakko(self, letters):
    return self.is_kind_base(self.left_kakko_pattern, letters)

def is_right_kakko(self, letters):
    return self.is_kind_base(self.right_kakko_pattern, letters)

def is_quote(self, letter):
    quotes = [u"\"",u"\'",u"”",u"’"]
    if letter in quotes:
        return True
    else:
        return False

def get_feature(self, node):
    """
    品詞等の情報をnodeから取り出してunicode化する
    """
    features = node.feature.split(",")
    unicoded_features = []

    for each_feature in features:
        unicoded_features.append(unicode(each_feature, "utf-8"))

    return unicoded_features    

def separate(self, sentence): 
    """
    token単位でiterateして分割
    """

    #MeCabは半角空白を認識しないので全角空白に置き換える
    encoded_sentence = sentence.replace(u" ", u" ").encode("utf-8")

    if len(encoded_sentence) <= 0:
        return None

    else:

        tagger = MeCab.Tagger("-O wakati")
        node = tagger.parseToNode(encoded_sentence)

        break_points = []            
        current_position = 0

        #現在つながっている長さ
        chunk_len = 0

        pre_node = node
        current_node = node.next

        while current_node:

            if not node.surface:
                node = node.next
                continue 

            else:
                breakable = False                    

                unicoded_surface = unicode(current_node.surface, "utf-8")
                current_position += len(unicoded_surface)

                this_len = len(unicoded_surface)
                chunk_len += this_len

                pre_feature = self.get_feature(pre_node) 
                current_feature = self.get_feature(current_node)

                #助詞もしくは副詞の後は切る
                if current_feature[0] == u"助詞" or current_feature[0] == u"副詞":
                    breakable = True  

                #記号の後は切る    
                elif current_feature[0] == u"記号":
                    breakable = True

                #長い名詞は切る。ただし数以外
                elif chunk_len > 5 and pre_feature[0] == u"名詞" and current_feature[0] == u"名詞" and  not current_feature[1] == u"数":
                    breakable = True

                #whether break or not    
                if breakable:
                    break_points.append(current_position)
                    chunk_len = 0

                #update cursor    
                pre_node = current_node
                current_node = current_node.next

        return break_points

def regulate(self, breaks, sentence):
    """
    文字単位でiterateする。必要以上に区切られていたらその修正もする。
    """

    pre = sentence[0]
    now = ""
    found_quotes = []
    last_quote = None

    for i, char in enumerate(sentence):

            now = sentence[i]        

            pre_was_broken = not i == 0 and i in breaks

            now_was_broken = i + 1 in breaks

            #今までで最後に出現したクォーテーション取得
            if found_quotes:
                last_quote = found_quotes[-1]

            #print(last_quote)
            #print(found_quotes)

            #前の文字後にて区切られていたら
            if pre_was_broken:

                #もし右括弧のまえでbreakしてたら取り除く
                if self.is_right_kakko(char):
                    breaks.remove(i)

                #記号の前で区切られてたら取り除く    
                elif self.is_kigou(char):
                    breaks.remove(i)

                #空白は前につける
                elif char == u" ":
                    breaks.remove(i)

                #閉じクォーテーション前で区切られてたら
                elif last_quote == char:
                    breaks.remove(i)


            #前の文字列後が区切られてないけど追加したい場合
            else:

                #もし左括弧系の前が区切られたなかったら区切る
                if self.is_left_kakko(char):
                    breaks.append(i)


            #今の文字後にて区切られていたら    
            if now_was_broken:

                #もし左括弧系の後にbreakしてたら取り除く
                if self.is_left_kakko(char):
                    breaks.remove(i + 1)

                #もし開きクォーテーションなら    
                elif not last_quote == char and self.is_quote(char):
                    breaks.remove(i + 1)

            #今の文字後で区切られてないけど追加したい場合    
            else:

                #もし右括弧系の後が区切られてなかったら区切る
                if self.is_right_kakko(char):
                    breaks.append(i + 1)

            #update
            #閉じクォーテーションならそのセットを取り除く
            if last_quote == char:
                found_quotes.pop()
                last_quote = None
            elif self.is_quote(char):
                found_quotes.append(char)

            pre = now

    return breaks

def get_break_points(self, sentence): 
    frequent_breaks = self.separate(sentence)
    return self.regulate(frequent_breaks, sentence)

def visualize(self, break_points, sentence):
    """
    タイトル分割の視覚化
    """
    formatted_sentence = []

    for i, char in enumerate(sentence):
        formatted_sentence.append(char)

        if i + 1 in break_points:
            formatted_sentence.append("|")

    return "".join(formatted_sentence)

def break_and_visualize(self, sentence):
    """
    単体動作確認用
    """
    breaks = self.get_break_points(sentence)
    print(self.visualize(breaks, sentence))

def checking(self):
    """
    複数動作確認用
    """

    f = open("sample_title.txt", "r")

    lines = f.readlines()
    cleaner = title_cleaner.TitleCleaner()

    for line in lines:
        unicoded_line = cleaner.normalize(unicode(line, "utf-8"))
        self.break_and_visualize(unicoded_line)

    f.close()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment