Skip to content

Commit

Permalink
update document, unity binding
Browse files Browse the repository at this point in the history
fix bug that using default argument for should_filter causes crush
  • Loading branch information
umegaya committed Aug 9, 2016
1 parent 9b0c433 commit ae876af
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 65 deletions.
119 changes: 77 additions & 42 deletions README.jp.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ for English readme. go to [here](https://github.com/umegaya/libshutup/README.md)
- "ばっどわーど" (ひらがな)
- "バッドわード" (文字種の混合)
- "baddo wa-do" (ローマ字表記:ヘボン式、日本式)
- 拡張されたPatricia木により、フレキシブルでありながら、メモリおよび実行効率の良いマッチングができます。
- 拡張されたPatricia木により、高いカスタマイズ性を持ちながら、メモリおよび実行効率の良いマッチングができます。
- ゲームなどのメモリ管理が厳しいアプリケーション向けに、C++バージョンのライブラリはすべてのメモリをアロケーター経由で割り当てることができます。
- 適用範囲のことなる3つのレイヤーの組み合わせによって多様な方法で禁止語句のマッチングが可能です。これにより他の言語についても有効なブロック処理を比較的簡単に構築可能です
- 適用範囲のことなる4つのレイヤーの組み合わせによって多様な方法で禁止語句のマッチングが可能です。これにより他の言語についても有効なブロック処理を比較的簡単に構築できます
- normalize (specialなケースとしてのignore)
- alias
- synonym
- context

## プラットフォーム
- iOS
- Android
- OSX (bundle)
- OSX (bundle or static lib)

## ビルド
```
Expand All @@ -33,31 +34,64 @@ make bundle # osx bundle
make lib # osx static library (may work in linux)
```

## 使い方
``` C++
#include "checker.h"

int main(int argc, char *argv[]) {
//スタック上、あるいはクラスのメンバ変数として作成する場合
shutup::Checker c(
"jp", /* 組み込みの日本語向け禁止文字チェックルーチンを利用する。 */
nullptr/* 標準のmalloc,free,reallocを使う */
);

//アロケーターを与える場合
shutup::Allocator a = { my_malloc, my_free, my_realloc };
shutup::Checker *pc = shutup::Checker::create("jp", &a);

//辞書の登録
c.add("アイウエオ");
c.add("abc");

assert(c.should_filter("a-B-c"));
assert(c.should_filter("あいうえお"));
assert(!c.should_filter("bbc"));
assert(!c.should_filter("あいうえこ"));
}
```
## 基本的な使い方

- C++
``` C++
#include "checker.h"

int main(int argc, char *argv[]) {
//スタック上、あるいはクラスのメンバ変数としてチェッカーオブジェクトを作成する場合
shutup::Checker c(
"jp", /* 組み込みの日本語向け禁止文字チェックルーチンを利用する。 */
nullptr/* 標準のmalloc,free,reallocを使う */
);

//アロケーターを与える場合
shutup::Checker::Allocator a = { my_malloc, my_free, my_realloc };
shutup::Checker c2 = shutup::Checker("jp", &a);

//辞書の登録
c.add("アイウエオ");
word_context *pctx = new word_context(OnlyForName);
c.add("abc", pctx); //語句に紐付いたコンテクストを与える場合.

//チェック
int start, count;
void *ctx;
assert(c.should_filter("a-B-c", 5, &start, &count, &ctx) && ctx == pctx);
assert(c.should_filter("あいうえお", 15, &start, &count));
assert(!c.should_filter("bbc", 3, &start, &count));
assert(!c.should_filter("あいうえこ", 15, &start, &count));
}
```
- C
``` C
#include "shutup.h"
int main(int argc, char *argv[]) {
//標準のメモリ割当ルーチンを利用してチェッカーオブジェクトを作成する.
shutter s = shutup_new("jp", NULL);
//アロケーターを与える場合
shutup_allocator a = { my_malloc, my_free, my_realloc };
shutter s2 = shutup_new("jp", &a);
//辞書の登録
shutup_add_word("アイウエオ", NULL);
word_context *pctx = malloc(sizeof(word_context));
pctx->kind = OnlyForName;
shutup_add_word("abc", pctx);//語句に紐付いたコンテクストを与える場合.
//チェック
int start, count;
assert(shutup_should_filter(s, "a-B-c", 5, &start, &count) == pctx);
assert(shutup_should_filter(s, "あいうえお", 15, &start, &count));
assert(!shutup_should_filter(s, "bbc", 3, &start, &count));
assert(!shutup_should_filter(s, "あいうえこ", 15, &start, &count));
}
```

## 禁止語句マッチングの仕組み
- libshutupは文字列の同一性を判定するための幾つかの抽象的な機能を用意し、その組み合わせによって任意の禁止語句のブロックの仕様を実現するようにしています。
Expand All @@ -74,15 +108,15 @@ int main(int argc, char *argv[]) {
- 例えば[badword]に対して[b a d w o r d]のように空白を含んでも人間は意味を解釈することができてしまうため、こういった文字として認識されない文字は取り除いてからマッチングを行わないといくらでもフィルターを抜けられてしまいます。
- そのため、空文字列に同一視される文字のグループを組み込みで設定できるようにしており、これをignoreと呼んでいます。
- JPモードにおいては以下のように設定されています。
```
"-+!\"#$%%&()*/,:;<=>?@[\\]^_{|}~ "
"ー" //half kata hyphen
"、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー‐/\~∥|…‥"
"‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×÷=≠<>≦≧∞∴"
"♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓"
"〓∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬ʼn♯♭"
"♪†‡¶◯〝〟≒≡∫∮√⊥∠∵∩∪ "
```
```
"-+!\"#$%%&()*/,:;<=>?@[\\]^_{|}~ "
"ー" //half kata hyphen
"、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー‐/\~∥|…‥"
"‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×÷=≠<>≦≧∞∴"
"♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓"
"〓∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬ʼn♯♭"
"♪†‡¶◯〝〟≒≡∫∮√⊥∠∵∩∪ "
```

### alias: 別名
- normalizeの同一視は文字列すべてに適用されてしまうので、予測できない誤マッチングが多くなりがちだと考えられます。例えば、ソ(そ)とン(ん)などのように、あらゆる場所で一致させると問題がある一方で、一部の単語を記述する際には頻繁に置き換えて使われる(神聖なgithubには書けませんが)文字もあります。
Expand All @@ -100,6 +134,11 @@ int main(int argc, char *argv[]) {
- 本来は単語がひらがな、カタカナからなる場合にのみ、全体をローマ字に変換したものも一緒にブロックするようにしたいわけです。synonymはいわば単語単位でのaliasを設定することによりそのような要求にこたえます。
- JPモードにおいては、入力文字列がひらがな、カタカナのみからなる場合にそれをローマ字(ヘボン式、日本式)に変換した文字列も同様に禁止対象として追加しています。

### context: コンテクスト
- 上記3つはどのような状況においても、指定された語句が出現する限りマッチします。しかし例えばアプリケーションによっては、特定の語句は特定のシチューエーションで入力された時のみ禁止語句としたい、とか、出現箇所が先頭あるいは末尾の時のみマッチしたい、といったマッチする条件が単純なパターンの存在ではないケースがあります。こう言った要望に1つ1つライブラリ側で応えていくことは、実装の複雑化やコードサイズの肥大化をもたらし、あまり良い考え方とは言えません。そこでlibshutupはcontextと呼ばれるアプリーケーション定義の情報を禁止したい語句に紐付け、さらに禁止語句への単純なマッチが発生した際にコンテクストとマッチ位置を受け取るコールバックを設定できるようにすることでこう言った要望に対応できるようにしています。
- 詳しくはbindings/Unity/Shutup.csで前方一致、後方一致、完全一致などを実装していますのでそれをご覧ください。


## 独自のチェック機構の実装
- shutup::language::WordCheckerを継承して実装を変更することで独自のチェックを追加できます。
- 具体例はjp.cppを参照してください。
Expand All @@ -112,7 +151,7 @@ class MyWordChecker : public shutup::language::WordChecker {
//MyWordCheckerを使うcheckerをスタック上に生成する.
shutup::Checker c(nullptr, &shutup::Checker::new_word_checker<MyWordChecker>);

//アロケーターを使ってpointerを割り当てる.
//MyWordCheckerを使い、さらにアロケーターを使ってpointerを割り当てる.
shutup::Allocator a = { my_malloc, my_free, my_realloc };
shutup::Checker *c = shutup::Checker::create<MyWordChecker>(&a);
```
Expand Down Expand Up @@ -174,7 +213,3 @@ shutup::Checker *pc2 = new shutup::Checker::create<MyWordChecker>(&a);
shutup::Checker::destroy(pc1);
shutup::Checker::destroy(pc2);
```




78 changes: 56 additions & 22 deletions bindings/Unity/Shutup.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
using UnityEngine;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Shutup {
public class Checker {
public enum MatchType {
Front,
Back,
Contain,
Exact,
};
public struct Context {
public MatchType _matchType;
public Context(MatchType matchType) {
_matchType = matchType;
}
};

//dll API
#if UNITY_EDITOR || UNITY_ANDROID
const string DllName = "shutup";
Expand All @@ -11,6 +25,8 @@ public class Checker {
#else
#error "invalid arch"
#endif
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate bool CheckerDelegate(byte *inp, int ilen, int start, int count, void *ctx);

[DllImport (DllName)]
private static extern unsafe void *shutup_new(byte *lang, void *a);
Expand All @@ -25,13 +41,13 @@ public class Checker {
private static extern unsafe void shutup_ignore_glyphs(void *s, byte *glyphs);

[DllImport (DllName)]
private static extern unsafe void shutup_add_word(void *s, byte *word);
private static extern unsafe void shutup_add_word(void *s, byte *word, void *ctx);

[DllImport (DllName)]
private static extern unsafe void *shutup_should_filter(void *s, byte *i, int ilen, byte *o, int *olen);
private static extern unsafe Context *shutup_should_filter(void *s, byte *i, int ilen, int *start, int *count, CheckerDelegate d);

[DllImport (DllName)]
private static extern unsafe void *shutup_filter(void *s, byte *i, int ilen, byte *o, int *olen, byte *mask);
private static extern unsafe void *shutup_filter(void *s, byte *i, int ilen, byte *o, int *olen, byte *mask, CheckerDelegate d);

[DllImport (DllName)]
private static extern void shutup_set_logger(LoggerDelegate logger);
Expand All @@ -49,6 +65,7 @@ public static void InitLogger() {
#endif

System.IntPtr _shutter;
List<Context> _holder = new List<Context>();
public void Init(string lang) {
byte[] b = System.Text.Encoding.UTF8.GetBytes(lang);
unsafe {
Expand All @@ -65,7 +82,6 @@ public void Fin() {
}
}
public Checker SetAlias(string target, string alias) {
//to add null byte at the last of converted byte array
byte[] b1 = System.Text.Encoding.UTF8.GetBytes(target + "\0");
byte[] b2 = System.Text.Encoding.UTF8.GetBytes(alias + "\0");
unsafe {
Expand All @@ -76,7 +92,6 @@ public Checker SetAlias(string target, string alias) {
return this;
}
public Checker IgnoreGlyphs(string glyphs) {
//to add null byte at the last of converted byte array
byte[] b = System.Text.Encoding.UTF8.GetBytes(glyphs + "\0");
unsafe {
fixed (byte* g = b) {
Expand All @@ -85,47 +100,66 @@ public Checker IgnoreGlyphs(string glyphs) {
}
return this;
}
public Checker AddWord(string word) {
//to add null byte at the last of converted byte array
public Checker AddWord(string word, MatchType matchType) {
byte[] b = System.Text.Encoding.UTF8.GetBytes(word + "\0");
Context ctx = new Context(matchType);
_holder.Add(ctx);
int id = _holder.Count;
unsafe {
fixed (byte* w = b) {
//Debug.Log("AddWord: " + string.Format("{0}[{1:X2}...{2:X2}{3:X2}{4:X2}]", (ulong)_shutter.ToPointer(), w[0], w[b.Length - 2], w[b.Length - 1], w[b.Length]));
shutup_add_word(_shutter.ToPointer(), w);
shutup_add_word(_shutter.ToPointer(), w, (void *)id);
}
}
return this;
}
//禁止語句のパターンにマッチした時に、紐づけられたコンテクストを参照して、実際に禁止語句として扱うかを判断するコールバック.
public unsafe bool ContextChecker(byte *inp, int ilen, int start, int count, void *_ctx) {
int id = (int)_ctx;
var ctx = _holder[id - 1];
switch (ctx._matchType) {
case MatchType.Front:
if (start != 0) {
return false;
}
break;
case MatchType.Back:
if ((start + count) != ilen) {
return false;
}
break;
case MatchType.Contain:
break;
case MatchType.Exact:
if (start != 0 || count != ilen) {
return false;
}
break;
}
return true;
}
public string ShouldFilter(string text) {
//b is exactly treated as byte array (with length), so no need to add \0
byte[] b = System.Text.Encoding.UTF8.GetBytes(text);
byte[] outbuf = new byte[b.Length];
int olen = outbuf.Length;
int start = 0, count = 0;
unsafe {
fixed (byte* t = b) {
fixed (byte* o = outbuf) {
//Debug.Log("ShouldFilter: " + string.Format("{0}[{1:X2}{2:X2}]", (ulong)_shutter.ToPointer(), t[b.Length - 1], t[b.Length - 0]));
if (shutup_should_filter(_shutter.ToPointer(), t, b.Length, o, &olen) == null) {
if (olen < 0) {
Debug.Log("ShouldFilter fails: " + olen);
}
return null;
}
//Debug.Log("ShouldFilter: " + string.Format("{0}[{1:X2}{2:X2}]", (ulong)_shutter.ToPointer(), t[b.Length - 1], t[b.Length - 0]));
if (shutup_should_filter(_shutter.ToPointer(), t, b.Length, &start, &count, ContextChecker) != null) {
return System.Text.Encoding.UTF8.GetString(b, start, count);
}
}
}
return System.Text.Encoding.UTF8.GetString(outbuf, 0, olen);
return null;
}
public string Filter(string text, string mask = null) {
//b is exactly treated as byte array (with length), so no need to add \0
byte[] b = System.Text.Encoding.UTF8.GetBytes(text);
byte[] b2 = (mask == null ? null : System.Text.Encoding.UTF8.GetBytes(mask + "\0"));
byte[] outbuf = new byte[b.Length * (mask == null ? 1 : b2.Length)];
int olen = outbuf.Length;
unsafe {
fixed (byte* t = b, m = b2) {
fixed (byte* o = outbuf) {
if (shutup_filter(_shutter.ToPointer(), t, b.Length, o, &olen, m) == null) {
if (shutup_filter(_shutter.ToPointer(), t, b.Length, o, &olen, m, ContextChecker) == null) {
Debug.Log("Filter fails " + olen);
return text;
}
Expand Down
2 changes: 1 addition & 1 deletion src/checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ bool Checker::should_filter(const char *in, int ilen, int *start, int *count, vo
while (iofs < ilen) {
if ((ctx = trie_.get(iptr + iofs, ilen - iofs, count)) != nullptr && checker(in, ilen, iofs, *count, ctx)) {
*start = iofs;
*pctx = ctx;
if (pctx != nullptr) { *pctx = ctx; }
return true;
} else {
tmp = utf8::peek(iptr + iofs, ilen - iofs);
Expand Down

0 comments on commit ae876af

Please sign in to comment.