me2text-ruby 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +105 -0
- data/Rakefile +1 -0
- data/lib/me2text/me2text.rb +108 -0
- data/lib/me2text/string_ext.rb +26 -0
- data/lib/me2text/token.rb +345 -0
- data/lib/me2text/version.rb +3 -0
- data/lib/me2text.rb +20 -0
- data/me2text-ruby.gemspec +24 -0
- data/test/test.rb +64 -0
- data/test/test_helper.rb +9 -0
- metadata +82 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006-2012 NHN Corp.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
me2text-ruby, me2text parser for ruby
|
2
|
+
======================================
|
3
|
+
|
4
|
+
me2text-ruby는 me2text 형식의 문자열을 HTML 또는 평문(plain text)로 변환해주는
|
5
|
+
ruby 라이브러리이다.
|
6
|
+
|
7
|
+
사용
|
8
|
+
----
|
9
|
+
|
10
|
+
### HTML로 변환
|
11
|
+
|
12
|
+
require 'me2text'
|
13
|
+
|
14
|
+
str = 'me2text는 "미투데이(TM)":http://me2day.net 의 글/댓글 작성 규칙이다.'
|
15
|
+
Me2Text.me2text(str)
|
16
|
+
# => me2text는 <a href='http://me2day.net'>미투데이™"</a>의 글/댓글 작성 규칙이다.
|
17
|
+
|
18
|
+
### 평문으로 변환
|
19
|
+
|
20
|
+
require 'me2text'
|
21
|
+
|
22
|
+
str = 'me2text는 "미투데이(TM)":http://me2day.net 의 글/댓글 작성 규칙이다.'
|
23
|
+
Me2Text.me2text(str, :text)
|
24
|
+
# => me2text는 미투데이™"의 글/댓글 작성 규칙이다.
|
25
|
+
|
26
|
+
### String 클래스 확장
|
27
|
+
|
28
|
+
require 'me2text/string_ext'
|
29
|
+
|
30
|
+
str = 'me2text는 "미투데이(TM)":http://me2day.net 의 글/댓글 작성 규칙이다.'
|
31
|
+
str.me2text
|
32
|
+
# => me2text는 <a href='http://me2day.net'>미투데이™"</a>의 글/댓글 작성 규칙이다.
|
33
|
+
|
34
|
+
### 주의 사항
|
35
|
+
|
36
|
+
me2text-ruby 사용을 위해서는 $KCODE 변수를 'UTF8' 또는 'u'로 지정해야 한다.
|
37
|
+
|
38
|
+
$KCODE = 'UTF8'
|
39
|
+
|
40
|
+
me2text-ruby는 ruby 1.8.7-p302에서 테스트되었으며 아직 ruby 1.9.X를 지원하지 않는다.
|
41
|
+
|
42
|
+
설치
|
43
|
+
----
|
44
|
+
me2text-ruby 라이브러리는 RubyGems를 통해 설치할 수 있다.
|
45
|
+
|
46
|
+
gem install me2text-ruby
|
47
|
+
|
48
|
+
|
49
|
+
me2text란?
|
50
|
+
----------
|
51
|
+
me2text는 미투데이 글/댓글등의 글 작성 규칙을 일컫는다.
|
52
|
+
|
53
|
+
### 링크
|
54
|
+
|
55
|
+
링크를 표현하는 형식은 다음과 같다
|
56
|
+
|
57
|
+
"링크를 걸 문구":URL
|
58
|
+
|
59
|
+
예를 들면 다음과 같다.
|
60
|
+
|
61
|
+
미투텍스트는 "미투데이":http://me2day.net 의 글/댓글 작성 규칙을 일컫는다.
|
62
|
+
=> 미투텍스트는 <a href='http://me2day.net'>미투데이"</a>의 글/댓글 작성 규칙을 일컫는다.
|
63
|
+
|
64
|
+
또, 이와 같은 링크 형식으로 작성되지 않더라도 단순히 URL만 지정한 경우에 대해서도 링크로
|
65
|
+
변환한다.
|
66
|
+
|
67
|
+
`자세한 내용은 이 주소로. http://me2day.net`
|
68
|
+
=> `자세한 내용은 이 주소로. <a href='http://me2day.net'>http://me2day.net</a>`
|
69
|
+
|
70
|
+
백슬래시를 사용해 링크를 걸 문구에 따옴표를 포함할 수 있다. 즉, 아래와 같은 링크를 지정한
|
71
|
+
문자열에서 링크를 걸 문구에 포함된 따옴표에는 백슬래시를 붙여 전체 문자열에 링크를 적용할
|
72
|
+
수 있다.
|
73
|
+
|
74
|
+
"레이디가가 소감, \"한국의 환대 따뜻하고 신나, 그리웠다\"":http://me2.do/FnlH8u3
|
75
|
+
=> <a href='http://me2.do/FnlH8u3'>레이디가가 소감, “한국의 환대 따뜻하고 신나, 그리웠다”</a>
|
76
|
+
|
77
|
+
### 따옴표
|
78
|
+
|
79
|
+
다음과 같이 따옴표 문자를 LEFT/RIGHT DOUBLE QUOTATION MARK(“”) 문자로 변환한다.
|
80
|
+
|
81
|
+
"안녕하세요" -> “안녕하세요”
|
82
|
+
|
83
|
+
### 심볼 문자의 표현
|
84
|
+
|
85
|
+
다음과 같은 특정 순서의 문자열을 심볼 문자로 변환한다.
|
86
|
+
|
87
|
+
* ... => … (HORIZONTAL ELLIPSIS)
|
88
|
+
* (TM) => ™ (TRADE MARK SIGN)
|
89
|
+
* (R) => ® (REGISTERED SIGN)
|
90
|
+
* (C) => © (COPYRIGHT SIGN)
|
91
|
+
* -- => — (EN DASH)
|
92
|
+
|
93
|
+
TODO
|
94
|
+
----
|
95
|
+
|
96
|
+
* Ruby 1.9 지원
|
97
|
+
|
98
|
+
Copyright and License
|
99
|
+
---------------------
|
100
|
+
|
101
|
+
Copyright 2012 NHN Corp.
|
102
|
+
|
103
|
+
me2text is released under the MIT license:
|
104
|
+
|
105
|
+
* www.opensource.org/licenses/MIT
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'me2text/token'
|
4
|
+
|
5
|
+
# Public: me2text 변환 메소드 제공한다.
|
6
|
+
#
|
7
|
+
# Examples
|
8
|
+
#
|
9
|
+
# require 'me2text'
|
10
|
+
#
|
11
|
+
# str = 'me2text는 "미투데이(TM)":http://me2day.net 의 글/댓글 작성 규칙이다.'
|
12
|
+
# Me2Text.me2text(str)
|
13
|
+
# # => me2text는 <a href='http://me2day.net'>미투데이™"</a>의 글/댓글 작성 규칙이다.
|
14
|
+
module Me2Text
|
15
|
+
class << self
|
16
|
+
# me2text(`text`)를 HTML로 변환한다.
|
17
|
+
#
|
18
|
+
# text - 변환할 me2text 형식의 문자열
|
19
|
+
# options - 다음과 같은 옵션을 지원한다. :
|
20
|
+
# :symbolize - 특정 순서의 문자열을 UNICODE의 해당 심볼로 변환한다.
|
21
|
+
# 지정하지 않는 경우 `true`.
|
22
|
+
# :open_new_window - +:html+ 형식으로 변환시 link에 대해 클릭하면 새 창으로 열릴
|
23
|
+
# 수 있도록 `<a>` 태그에 `target='_blank'`를 삽입한다.
|
24
|
+
# 지정하지 않는 경우 `false`.
|
25
|
+
# :allow_line_break - 라인브레이크를 허용한다. `:html` 로 변환시 라인브레이크는
|
26
|
+
# `<br />` 태그로 변환된다. 지정하지 않는 경우 `false`.
|
27
|
+
# :limit - 변환 결과의 글자수를 제한한다. `:limit`에는 숫자를 지정하며 변환 결과는
|
28
|
+
# 지정된 글자 수 까지만 변환된다.
|
29
|
+
# :link_handler - 문자열에 나타나는 각 링크에 대해 지정된 block이 호출된다.
|
30
|
+
def to_html(text, options = {})
|
31
|
+
me2text(text, :html, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
# me2text(+text+)를 평문(plain text)로 변환한다.
|
35
|
+
#
|
36
|
+
# text - 변환할 me2text 형식의 문자열
|
37
|
+
# options - 다음과 같은 옵션을 지원한다. :
|
38
|
+
# :symbolize - 특정 순서의 문자열을 UNICODE의 해당 심볼로 변환한다.
|
39
|
+
# 지정하지 않는 경우 `true`.
|
40
|
+
# :allow_line_break - 라인브레이크를 허용한다. `:html` 로 변환시 라인브레이크는
|
41
|
+
# `<br />` 태그로 변환된다. 지정하지 않는 경우 `false`.
|
42
|
+
# :limit - 변환 결과의 글자수를 제한한다. `:limit`에는 숫자를 지정하며 변환 결과는
|
43
|
+
# 지정된 글자 수 까지만 변환된다.
|
44
|
+
# :link_handler - 문자열에 나타나는 각 링크에 대해 지정된 block이 호출된다.
|
45
|
+
def to_text(text, options = {})
|
46
|
+
me2text(text, :text, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# me2text(`text`)를 지정한 형식(`format`)으로 변환한다.
|
50
|
+
#
|
51
|
+
# text - 변환할 me2text 형식의 문자열
|
52
|
+
# format - 변환 형식을 지정한다. `:html`, `:text` 중 하나를 지정하며
|
53
|
+
# 지정하지 않은 경우 +:html+ 이 지정된다. `:html`은 HTML 형식으로 변환하며
|
54
|
+
# +:text+는 평문(plain text)으로 변환한다.
|
55
|
+
# options - 다음과 같은 옵션을 지원한다. :
|
56
|
+
# :symbolize - 특정 순서의 문자열을 UNICODE의 해당 심볼로 변환한다.
|
57
|
+
# 지정하지 않는 경우 `true`.
|
58
|
+
# :open_new_window - +:html+ 형식으로 변환시 link에 대해 클릭하면 새 창으로 열릴
|
59
|
+
# 수 있도록 `<a>` 태그에 `target='_blank'`를 삽입한다.
|
60
|
+
# 지정하지 않는 경우 `false`.
|
61
|
+
# :allow_line_break - 라인브레이크를 허용한다. `:html` 로 변환시 라인브레이크는
|
62
|
+
# `<br />` 태그로 변환된다. 지정하지 않는 경우 `false`.
|
63
|
+
# :limit - 변환 결과의 글자수를 제한한다. `:limit`에는 숫자를 지정하며 변환 결과는
|
64
|
+
# 지정된 글자 수 까지만 변환된다.
|
65
|
+
# :link_handler - 문자열에 나타나는 각 링크에 대해 지정된 block이 호출된다.
|
66
|
+
def me2text(text, format = :html, options = {})
|
67
|
+
options = {
|
68
|
+
:symbolize => true,
|
69
|
+
:open_new_window => false,
|
70
|
+
:allow_line_break => false,
|
71
|
+
:limit => nil,
|
72
|
+
:link_handler => nil
|
73
|
+
}.merge(options)
|
74
|
+
text = strip_linebreak(text) unless options[:allow_line_break]
|
75
|
+
|
76
|
+
text = Token.join_tokens(Token.tokenize(text), format, options)
|
77
|
+
|
78
|
+
# 키워드 핸들러 등록시, doublequote 가 결과에 포함되어 있는 경우
|
79
|
+
# doublequotize 메소드가 마지막에 호출 됨으로 에러 발생한다.
|
80
|
+
text = doublequotize(text) if options[:symbolize]
|
81
|
+
|
82
|
+
if format == :html
|
83
|
+
text = htmlize_linebreak(text) if options[:allow_line_break]
|
84
|
+
end
|
85
|
+
|
86
|
+
strip_control_chars(text)
|
87
|
+
end
|
88
|
+
|
89
|
+
def doublequotize(text) #:nodoc:
|
90
|
+
text.gsub(/\"([^"]*)\"/) { |s| "“#{$1}”" }
|
91
|
+
end
|
92
|
+
|
93
|
+
# 라인브레이크를 <br /> 태그로 대체한다.
|
94
|
+
def htmlize_linebreak(text)
|
95
|
+
text.gsub(/\r\n/, "<br />").gsub(/\n/, "<br />").gsub(/\r/, "<br />")
|
96
|
+
end
|
97
|
+
|
98
|
+
# 컨트롤 문자를 제거한다.
|
99
|
+
def strip_control_chars(text)
|
100
|
+
text.gsub(/[[:cntrl:]]/, "")
|
101
|
+
end
|
102
|
+
|
103
|
+
# 라인브래이크를 공백으로 변환한다
|
104
|
+
def strip_linebreak(text)
|
105
|
+
text.gsub(/\s\r\n/, "").gsub(/\r\n/, " ").gsub(/[\r\n]/, " ")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'me2text'
|
2
|
+
|
3
|
+
module Me2Text
|
4
|
+
module StringExtension
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def me2text(format = :html, options = {})
|
12
|
+
Me2Text.me2text(self, format, options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def me2text(text, format = :html, options = {})
|
18
|
+
Me2Text.me2text(text, format, options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class String
|
25
|
+
include Me2Text::StringExtension
|
26
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Me2Text
|
4
|
+
class Token
|
5
|
+
REGEX_ME2LINK = begin
|
6
|
+
dquota_s = '\\xe2\\x80\\x9c|\\xe2\\x80\\x9f|\\xe2\\x9d\\x9d|\\xe2\\x80\\xb6|\\xe2\\x80\\x9d|\\x22|\\xef\\xbc\\x82'
|
7
|
+
dquota_e = '\\xe2\\x80\\x9d|\\xe2\\x80\\x9e|\\xe2\\x9d\\x9e|\\xcb\\x9d|\\xe2\\x80\\xb3|\\xe2\\x80\\x9e|\\xe2\\x80\\x9f|\\x22|\\xef\\xbc\\x82'
|
8
|
+
dquota_ne = '\\xe2\\x80\\x9d\\xe2\\x80\\x9e\\xe2\\x9d\\x9e\\xcb\\x9d\\xe2\\x80\\xb3\\xe2\\x80\\x9e\\xe2\\x80\\x9f\\x22\\xef\\xbc\\x82'
|
9
|
+
/(?:#{dquota_s})([^#{dquota_ne}]*)(?:#{dquota_e}):(http[s]?:\/\/[^\s]*)(\s|$)/
|
10
|
+
end
|
11
|
+
REGEX_URL = /(http[s]?:\/\/[^\s|^\'|^\"]*)([\'|\"|\s]|$)/
|
12
|
+
ESCAPE_CHAR = "\xc2\xa0"
|
13
|
+
|
14
|
+
attr_accessor :text
|
15
|
+
|
16
|
+
def to_s(format, options = {})
|
17
|
+
unless [:html, :text].include?(format)
|
18
|
+
raise ArgumentError.new("Unknown format: #{format.inspect}")
|
19
|
+
end
|
20
|
+
self.send('to_' + format.to_s, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_html(options ={})
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_text(options ={})
|
27
|
+
end
|
28
|
+
|
29
|
+
def length(options ={})
|
30
|
+
1
|
31
|
+
end
|
32
|
+
|
33
|
+
def truncate(nos, format, options = {})
|
34
|
+
options = {
|
35
|
+
:ellipsis => "…"
|
36
|
+
}.merge(options)
|
37
|
+
|
38
|
+
[options[:ellipsis], 1]
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def htmlize(text, options = {})
|
44
|
+
options = {
|
45
|
+
:symbolize => true,
|
46
|
+
:linklize_class => "write_link"
|
47
|
+
}.merge(options)
|
48
|
+
|
49
|
+
remain = text
|
50
|
+
result = ""
|
51
|
+
if options[:linklize]
|
52
|
+
while (m = remain.match(REGEX_URL))
|
53
|
+
url = m[1]
|
54
|
+
after_url = m[2]
|
55
|
+
|
56
|
+
result << htmlize_chars(m.pre_match, options) if !m.pre_match.empty?
|
57
|
+
result << "<a href='#{url.to_s.strip}'"
|
58
|
+
result << " class='#{options[:linklize_class]}'" if options[:linklize_class]
|
59
|
+
result << " target='_blank'" if options[:open_new_window]
|
60
|
+
result << ">"
|
61
|
+
result << url
|
62
|
+
result << "</a>"
|
63
|
+
result << after_url
|
64
|
+
|
65
|
+
remain = m.post_match
|
66
|
+
end
|
67
|
+
end
|
68
|
+
result << htmlize_chars(remain, options)
|
69
|
+
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def htmlize_chars(text, options = {})
|
74
|
+
html_result = text.to_s.gsub(/&/, "&").gsub(/</, "<").gsub(/>/, ">")
|
75
|
+
html_result = textize(html_result, options)
|
76
|
+
html_result
|
77
|
+
end
|
78
|
+
|
79
|
+
def textize(text, options = {})
|
80
|
+
options = {
|
81
|
+
:symbolize => true
|
82
|
+
}.merge(options)
|
83
|
+
|
84
|
+
if options[:symbolize]
|
85
|
+
text = text.gsub(/\.\.\./, "…").
|
86
|
+
gsub(/\(TM\)/, "™").
|
87
|
+
gsub(/\(R\)/, "®").
|
88
|
+
gsub(/\(C\)/, "©").
|
89
|
+
gsub(/--/, "—")
|
90
|
+
end
|
91
|
+
text
|
92
|
+
end
|
93
|
+
|
94
|
+
class << self
|
95
|
+
def tokenize(text, options = {})
|
96
|
+
tokenize_me2link(text.gsub(/\\\"/, ESCAPE_CHAR), options)
|
97
|
+
end
|
98
|
+
|
99
|
+
def tokenize_me2link(text, options = {})
|
100
|
+
tokens = []
|
101
|
+
while (m = text.match(REGEX_ME2LINK))
|
102
|
+
# 매치 이전 텍스트 처리
|
103
|
+
tokens += tokenize_keyword(m.pre_match, options) if !m.pre_match.empty?
|
104
|
+
|
105
|
+
url = m[2]
|
106
|
+
anchor_text = m[1]
|
107
|
+
if url
|
108
|
+
if !options[:link_handler].nil?
|
109
|
+
url = options[:link_handler].call(url, options)
|
110
|
+
end
|
111
|
+
tokens << Link.new(anchor_text, url, m[3].length > 0)
|
112
|
+
else
|
113
|
+
tokens += tokenize_keyword(anchor_text, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
text = m.post_match
|
117
|
+
end
|
118
|
+
if !text.empty?
|
119
|
+
tokens += tokenize_keyword(text, options)
|
120
|
+
end
|
121
|
+
|
122
|
+
tokens
|
123
|
+
end
|
124
|
+
|
125
|
+
def tokenize_keyword(text, options = {})
|
126
|
+
tokens = []
|
127
|
+
while (m = text.match(Keyword::KEYWORD_REGEX))
|
128
|
+
tokens += tokenize_plaintext(m.pre_match, options) if !m.pre_match.empty?
|
129
|
+
tokens << Keyword.new(m.to_s, options)
|
130
|
+
text = m.post_match
|
131
|
+
end
|
132
|
+
if !text.empty?
|
133
|
+
tokens += tokenize_plaintext(text, options)
|
134
|
+
end
|
135
|
+
|
136
|
+
tokens
|
137
|
+
end
|
138
|
+
|
139
|
+
def tokenize_plaintext(text, options = {})
|
140
|
+
[Plain.new(text)]
|
141
|
+
end
|
142
|
+
|
143
|
+
def join_tokens(tokens, format, options)
|
144
|
+
if options[:limit].nil?
|
145
|
+
return tokens.map { |token| token.to_s(format, options) }.join
|
146
|
+
end
|
147
|
+
|
148
|
+
result = ""
|
149
|
+
process_length = 0
|
150
|
+
tokens.each_with_index do |tk, idx|
|
151
|
+
n_len = process_length + tk.length
|
152
|
+
r_len = length - n_len
|
153
|
+
|
154
|
+
# 길이가 남았다.
|
155
|
+
if r_len > 0
|
156
|
+
process_length = process_length + tk.length
|
157
|
+
result << tk.to_s(format, options)
|
158
|
+
|
159
|
+
# 길이가 딱 맞다.
|
160
|
+
elsif r_len == 0
|
161
|
+
# 이번 토큰이 마지막 토큰(더이상 추가할 게 없음) 인가?
|
162
|
+
if (idx + 1) == tokens.size
|
163
|
+
process_length = process_length + tk.length
|
164
|
+
result << tk.to_s(format, options)
|
165
|
+
else
|
166
|
+
# 남은 문자열이 있다.
|
167
|
+
val = tk.truncate(0, format, options)
|
168
|
+
process_length = process_length + val[1]
|
169
|
+
result << val.first
|
170
|
+
end
|
171
|
+
break
|
172
|
+
|
173
|
+
# 길이가 r_len만큼 모자르다
|
174
|
+
else
|
175
|
+
val = tk.truncate(-r_len, format, options)
|
176
|
+
process_length = process_length + val[1]
|
177
|
+
result << val.first
|
178
|
+
break
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
result
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class Plain < Token
|
188
|
+
def initialize(tv)
|
189
|
+
@text = tv.gsub(ESCAPE_CHAR, "\"")
|
190
|
+
end
|
191
|
+
|
192
|
+
def to_html(options = {})
|
193
|
+
htmlize(@text, options)
|
194
|
+
end
|
195
|
+
|
196
|
+
def to_text(options = {})
|
197
|
+
textize(@text, options)
|
198
|
+
end
|
199
|
+
|
200
|
+
def length
|
201
|
+
@text.split(//u).length
|
202
|
+
end
|
203
|
+
|
204
|
+
def truncate(nos, format, options = {})
|
205
|
+
options = {
|
206
|
+
:ellipsis => "…"
|
207
|
+
}.merge(options)
|
208
|
+
|
209
|
+
val = self.to_s(format, options)
|
210
|
+
if (val.length - nos) <= 0
|
211
|
+
#빼면 아무것도 없다면..
|
212
|
+
##이 링크는 버린다
|
213
|
+
options[:ellipsis]
|
214
|
+
else
|
215
|
+
val = val.split(//u)[0...(val.length - nos) - 1]
|
216
|
+
val << options[:ellipsis]
|
217
|
+
end
|
218
|
+
|
219
|
+
@text = val
|
220
|
+
|
221
|
+
[val, length]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class Link < Token
|
226
|
+
attr_accessor :link
|
227
|
+
attr_accessor :include_ws
|
228
|
+
|
229
|
+
def initialize(label, link, include_ws = false)
|
230
|
+
@text = label.gsub(ESCAPE_CHAR, "\"")
|
231
|
+
@link = link.gsub("'","%27").gsub("\"","%22").gsub("<", "%3C").gsub(">", "%3E")
|
232
|
+
@include_ws = include_ws
|
233
|
+
end
|
234
|
+
|
235
|
+
def to_label(format,options = {})
|
236
|
+
options = options.merge({
|
237
|
+
:linklize => false
|
238
|
+
})
|
239
|
+
|
240
|
+
result = if format == :html
|
241
|
+
htmlize(@text, options)
|
242
|
+
elsif format == :text
|
243
|
+
textize(@text, options)
|
244
|
+
else
|
245
|
+
@text
|
246
|
+
end
|
247
|
+
|
248
|
+
if result.strip.length == 0
|
249
|
+
if format == :html
|
250
|
+
result = htmlize(" " + link + " ",options)
|
251
|
+
elsif format == :text
|
252
|
+
result = textize(" " + link + " " ,options)
|
253
|
+
else
|
254
|
+
@text
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
return result
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_html(options = {})
|
262
|
+
if options[:open_new_window]
|
263
|
+
"<a href='#{link}' target='_blank'>#{to_label(:html,options)}</a>"
|
264
|
+
else
|
265
|
+
"<a href='#{link}'>#{to_label(:html,options)}</a>"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def to_text(options = {})
|
270
|
+
to_label(:text, options)
|
271
|
+
end
|
272
|
+
|
273
|
+
def length(options = {})
|
274
|
+
textize(@text,options).length
|
275
|
+
end
|
276
|
+
|
277
|
+
def truncate(nos, format, options = {})
|
278
|
+
options = {
|
279
|
+
:ellipsis => "…"
|
280
|
+
}.merge(options)
|
281
|
+
|
282
|
+
val = self.to_label(format,options)
|
283
|
+
if (val.length - nos) <= 0
|
284
|
+
#빼면 아무것도 없다면..
|
285
|
+
##이 링크는 버린다
|
286
|
+
options[:ellipsis]
|
287
|
+
else
|
288
|
+
val = val.split(//u)[0...(val.length-nos) - 1]
|
289
|
+
val << options[:ellipsis]
|
290
|
+
end
|
291
|
+
|
292
|
+
@text= val
|
293
|
+
[to_s(format,options), length(options)]
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class Keyword < Token
|
298
|
+
KEYWORD_REGEX = /(\[([^\[\]]+)\])/
|
299
|
+
attr_accessor :link
|
300
|
+
|
301
|
+
def initialize(keyword, options)
|
302
|
+
# keyword는 "[키워드]" 같은 형태
|
303
|
+
_keyword = keyword.to_s.strip.scan(KEYWORD_REGEX)
|
304
|
+
_keyword = _keyword.flatten[1]
|
305
|
+
|
306
|
+
raise "키워드가 없습니다." if _keyword.nil?
|
307
|
+
|
308
|
+
@text = _keyword.gsub(ESCAPE_CHAR, "\"")
|
309
|
+
end
|
310
|
+
|
311
|
+
def to_html(options = {})
|
312
|
+
if options[:keyword_handler] && options[:keyword_handler].is_a?(Proc)
|
313
|
+
options[:keyword_handler].call(@text, options)
|
314
|
+
else
|
315
|
+
"[#{htmlize(@text, options)}]"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def to_text(options = {})
|
320
|
+
"[#{textize(@text, options)}]"
|
321
|
+
end
|
322
|
+
|
323
|
+
def length(options = {})
|
324
|
+
textize(@text, options).length
|
325
|
+
end
|
326
|
+
|
327
|
+
def truncate(nos, format, options = {})
|
328
|
+
options = {
|
329
|
+
:ellipsis => "…"
|
330
|
+
}.merge(options)
|
331
|
+
|
332
|
+
val = self.to_text(options)
|
333
|
+
if (val.length - nos) <= 0
|
334
|
+
# 빼면 아무것도 없다면 이 링크는 버린다
|
335
|
+
options[:ellipsis]
|
336
|
+
else
|
337
|
+
val = val.split(//u)[0...(val.length - nos) - 1]
|
338
|
+
val << options[:ellipsis]
|
339
|
+
end
|
340
|
+
|
341
|
+
@text = val
|
342
|
+
[to_s(format, options), length(options)]
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
data/lib/me2text.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
major, minor, patch = RUBY_VERSION.split('.')
|
4
|
+
if major.to_i == 1 && minor.to_i > 8
|
5
|
+
raise("me2text는 ruby 1.9 이상은 현재 지원하지 않습니다.")
|
6
|
+
else
|
7
|
+
# Ruby 1.8 $KCODE check.
|
8
|
+
unless $KCODE[0].chr =~ /u/i
|
9
|
+
raise("me2text를 사용하기 위해서는 $KCODE 변수를 'UTF8' 또는 'u'로 지정해야 합니다.")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
$:.push(File.expand_path("..", __FILE__))
|
14
|
+
|
15
|
+
module Me2Text
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'me2text/version'
|
19
|
+
require 'me2text/me2text'
|
20
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "me2text/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "me2text-ruby"
|
7
|
+
s.version = Me2Text::VERSION
|
8
|
+
s.authors = ["codian"]
|
9
|
+
s.email = ["codian@gmail.com"]
|
10
|
+
s.homepage = "http://me2day.net"
|
11
|
+
s.summary = %q{me2text parser for ruby}
|
12
|
+
s.description = %q{me2text is text format for me2day posting.
|
13
|
+
me2text-ruby is ruby library to me2text to HTML or plain text}
|
14
|
+
s.rubyforge_project = "me2text-ruby"
|
15
|
+
s.files = Dir['{lib/**/*,test/**/*}'] +
|
16
|
+
%w(.gitignore me2text-ruby.gemspec Gemfile MIT-LICENSE Rakefile README.md)
|
17
|
+
s.test_files = Dir['test/**/*']
|
18
|
+
s.executables = ''
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
data/test/test.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
class Me2TextTest < Test::Unit::TestCase
|
6
|
+
def test_to_html
|
7
|
+
cases = [
|
8
|
+
# 일반 텍스트
|
9
|
+
['안녕하세요. 미투데이입니다.', '안녕하세요. 미투데이입니다.'],
|
10
|
+
['"안녕하세요." 미투데이입니다.', '“안녕하세요.” 미투데이입니다.'],
|
11
|
+
['안녕하세요...(C)(R)(TM)-- 미투데이입니다', "안녕하세요…©®™— 미투데이입니다"],
|
12
|
+
|
13
|
+
# html 이스케이프
|
14
|
+
['안녕하세요. <br /> 미투데이입니다.', '안녕하세요. <br /> 미투데이입니다.'],
|
15
|
+
['안녕하세요. 미투데이입니다.', '안녕하세요. &nbsp;미투데이입니다.'],
|
16
|
+
|
17
|
+
# 링크 문법
|
18
|
+
['"안녕하세요":http://me2day.net . 미투데이입니다.', '<a href=\'http://me2day.net\'>안녕하세요</a>. 미투데이입니다.'],
|
19
|
+
['안녕"하세요":http://me2day.net . 미투데이입니다.', '안녕<a href=\'http://me2day.net\'>하세요</a>. 미투데이입니다.'],
|
20
|
+
['안녕하세요. "미투데이입니다.":http://me2day.net', '안녕하세요. <a href=\'http://me2day.net\'>미투데이입니다.</a>'],
|
21
|
+
|
22
|
+
# 키워드 문법
|
23
|
+
['[안녕하세요]. 미투데이입니다.', '[안녕하세요]. 미투데이입니다.'],
|
24
|
+
|
25
|
+
# 따옴표 이스케이프한 경우
|
26
|
+
['"\\"안녕하세요\\"":http://me2day.net . 미투데이입니다.', '<a href=\'http://me2day.net\'>“안녕하세요”</a>. 미투데이입니다.'],
|
27
|
+
|
28
|
+
# 컨트롤 문자 strip
|
29
|
+
["\000안녕하세요. 미투데이입니다.", '안녕하세요. 미투데이입니다.']
|
30
|
+
]
|
31
|
+
|
32
|
+
cases.each_with_index do |test, index|
|
33
|
+
text = test[0]
|
34
|
+
expect = test[1]
|
35
|
+
result = Me2Text.me2text(text, :html)
|
36
|
+
if expect != result
|
37
|
+
flunk "TEXT: #{text}\n" +
|
38
|
+
"EXPECT: #{expect}\n" +
|
39
|
+
"RESULT: #{result}\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_strip_linebreak
|
45
|
+
cases = [
|
46
|
+
["안녕하세요.\n미투데이입니다.", '안녕하세요.<br />미투데이입니다.'],
|
47
|
+
["안녕하세요.\r미투데이입니다.", '안녕하세요.<br />미투데이입니다.'],
|
48
|
+
["안녕하세요.\r\n미투데이입니다.", '안녕하세요.<br />미투데이입니다.'],
|
49
|
+
["안녕하세요.\n\r미투데이입니다.", '안녕하세요.<br /><br />미투데이입니다.'],
|
50
|
+
]
|
51
|
+
|
52
|
+
cases.each_with_index do |test, index|
|
53
|
+
text = test[0]
|
54
|
+
expect = test[1]
|
55
|
+
result = Me2Text.me2text(text, :html, :allow_line_break => true)
|
56
|
+
if expect != result
|
57
|
+
flunk "TEXT: #{text.inspect}\n" +
|
58
|
+
"EXPECT: #{expect}\n" +
|
59
|
+
"RESULT: #{result}\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: me2text-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- codian
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-05-21 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |-
|
22
|
+
me2text is text format for me2day posting.
|
23
|
+
me2text-ruby is ruby library to me2text to HTML or plain text
|
24
|
+
email:
|
25
|
+
- codian@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- lib/me2text/me2text.rb
|
34
|
+
- lib/me2text/string_ext.rb
|
35
|
+
- lib/me2text/token.rb
|
36
|
+
- lib/me2text/version.rb
|
37
|
+
- lib/me2text.rb
|
38
|
+
- test/test.rb
|
39
|
+
- test/test_helper.rb
|
40
|
+
- .gitignore
|
41
|
+
- me2text-ruby.gemspec
|
42
|
+
- Gemfile
|
43
|
+
- MIT-LICENSE
|
44
|
+
- Rakefile
|
45
|
+
- README.md
|
46
|
+
homepage: http://me2day.net
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: me2text-ruby
|
75
|
+
rubygems_version: 1.8.23
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: me2text parser for ruby
|
79
|
+
test_files:
|
80
|
+
- test/test.rb
|
81
|
+
- test/test_helper.rb
|
82
|
+
has_rdoc:
|