cc 1.2.1 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +29 -3
- data/cc.rb +1 -0
- data/monkey-patch.rb +375 -1
- data/text-client.rb +297 -0
- data/text-server.rb +403 -0
- data/text-tools.rb +418 -0
- data/tree.rb +184 -184
- metadata +5 -2
data/text-tools.rb
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#coding:utf-8
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
############################################################################################################
|
|
5
|
+
# HOW TO USE #
|
|
6
|
+
############################################################################################################
|
|
7
|
+
|
|
8
|
+
=begin
|
|
9
|
+
require 'cc'
|
|
10
|
+
CC.use 'text-tools' # immediately when loading this file
|
|
11
|
+
|
|
12
|
+
# 类型操作
|
|
13
|
+
p "100".int(2), "3.14159".float(3), "0.86".precent, "true".bool
|
|
14
|
+
|
|
15
|
+
# 文本操作
|
|
16
|
+
p "aaabbb".head? "aa"
|
|
17
|
+
p "cccddd".tail? "dd"
|
|
18
|
+
p "abcdef" - "c"
|
|
19
|
+
p "abcdef" / "c"
|
|
20
|
+
p "abedef".exchange "e", 'z'
|
|
21
|
+
p "a11111a2222b3333b4444a5555b".match_paragraph('a','b')
|
|
22
|
+
p "<a>1<a><a>2</a>3</a>".match_xml('a')
|
|
23
|
+
|
|
24
|
+
# 列表操作
|
|
25
|
+
p ['a','b','c'].match(1){|c| c >= 'b'}
|
|
26
|
+
p ['a','b','c'].catch('d')
|
|
27
|
+
|
|
28
|
+
# 树操作
|
|
29
|
+
p ({a: 1, b: 2} + [1, 3, 5]).keys
|
|
30
|
+
p ({a: 1}).get :a
|
|
31
|
+
=end
|
|
32
|
+
|
|
33
|
+
############################################################################################################
|
|
34
|
+
# text-tools —— 文本/列表/树的人类直觉操作扩展(移植自 imdoc/TextUtils/text_mind + text_abstract) #
|
|
35
|
+
# #
|
|
36
|
+
# 组成(单一文件自带全部依赖,零第三方依赖): #
|
|
37
|
+
# TextAbstract(模块函数):draw_fragments / match_paragraph / match_cascade / match_html_tag #
|
|
38
|
+
# —— 复杂文本的特征字段摘取与采集(段落/级联/标签结构匹配) #
|
|
39
|
+
# TextAbstract::Type -> String 类型转换(int / float / precent / text / bool) #
|
|
40
|
+
# TextAbstract::TextOperator -> String 文本操作(head? / tail? / matches / join / cut / - / / …) #
|
|
41
|
+
# TextAbstract::ListOperator -> Array 列表操作(catch / match / sub) #
|
|
42
|
+
# TextAbstract::TreeOperator -> Hash 树操作(get / set / +) #
|
|
43
|
+
# #
|
|
44
|
+
# 配套工具: #
|
|
45
|
+
# text-server.rb —— S 端:命令行启动 HTTP 解析服务(本文件的 HTTP 包装) #
|
|
46
|
+
# text-client.rb —— C 端:命令行传入解析(本地执行本文件,或 --remote 走 S 端服务) #
|
|
47
|
+
############################################################################################################
|
|
48
|
+
|
|
49
|
+
module TextAbstract
|
|
50
|
+
module_function
|
|
51
|
+
|
|
52
|
+
# 按前置/后置标记抽取文本片段(特定环境遗留方法,不推荐常用)。
|
|
53
|
+
#
|
|
54
|
+
# 遇到 pre_flag 匹配行时开始收集,遇到 post_flag 匹配行时结束;
|
|
55
|
+
# 标记行本身包含在片段内,未闭合的片段追加到上下文。
|
|
56
|
+
#
|
|
57
|
+
# @param text [String] 原始文本
|
|
58
|
+
# @param pre_flag [Regexp] 开始标记(行级正则)
|
|
59
|
+
# @param post_flag [Regexp] 结束标记(行级正则)
|
|
60
|
+
# @return [Array<String>] 二元组 [上下文文本, 片段数组]
|
|
61
|
+
def draw_fragments text,pre_flag,post_flag
|
|
62
|
+
context,fragments = [],[]
|
|
63
|
+
temp,flag = "",false
|
|
64
|
+
text.each_line do|line|
|
|
65
|
+
if pre_flag.match(line)
|
|
66
|
+
temp = String.new if flag == false
|
|
67
|
+
flag = true
|
|
68
|
+
end
|
|
69
|
+
if flag == true
|
|
70
|
+
temp << line # base on line!
|
|
71
|
+
else
|
|
72
|
+
context << line
|
|
73
|
+
end
|
|
74
|
+
if post_flag.match(line)
|
|
75
|
+
flag = false
|
|
76
|
+
fragments << temp
|
|
77
|
+
temp = String.new
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
context << temp if flag == true # exist pre_flag but no post_flag
|
|
81
|
+
fragments.delete("")
|
|
82
|
+
return context.join(),fragments
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# 严格顺序匹配开始/结束标记之间的段落。
|
|
86
|
+
#
|
|
87
|
+
# start 和 finish 有严格先后顺序:一段未结束不开启另一段。
|
|
88
|
+
# 返回匹配到的段落正文数组(不含首尾标记本身)。
|
|
89
|
+
#
|
|
90
|
+
# @param text [String] 完整文档文本
|
|
91
|
+
# @param start [String] 开始标记(严格字符串包含匹配)
|
|
92
|
+
# @param finish [String] 结束标记(严格字符串包含匹配)
|
|
93
|
+
# @return [Array<String>] 段落正文列表
|
|
94
|
+
def match_paragraph text,start,finish
|
|
95
|
+
paragraphs,current_text = [],text.clone
|
|
96
|
+
until current_text==""
|
|
97
|
+
return paragraphs unless current_text.include?(start) # 严格匹配开始
|
|
98
|
+
start_split = current_text.split(start)
|
|
99
|
+
current_text = start_split[1..-1].join(start)
|
|
100
|
+
return paragraphs unless current_text.include?(finish) # 严格匹配结束
|
|
101
|
+
finish_split = current_text.split(finish)
|
|
102
|
+
context = finish_split[0]
|
|
103
|
+
current_text = finish_split[1..-1].join(finish)+(text[-1*finish.size..-1]==finish ? finish : "")
|
|
104
|
+
paragraphs << context
|
|
105
|
+
end
|
|
106
|
+
return paragraphs
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# 可嵌套匹配开始/结束标记,返回树状段落结构。
|
|
110
|
+
#
|
|
111
|
+
# start 和 finish 可以嵌套(一个 start/finish 包含在另一个内部),
|
|
112
|
+
# 但全局上仍保持顺序。返回嵌套数组:字符串为内容,数组为下一层级。
|
|
113
|
+
#
|
|
114
|
+
# @param text [String] 完整文档文本
|
|
115
|
+
# @param start [String] 开始标记
|
|
116
|
+
# @param finish [String] 结束标记
|
|
117
|
+
# @return [Array] 树状段落结构
|
|
118
|
+
def match_cascade text,start,finish
|
|
119
|
+
paragraphs,current_text = [],text
|
|
120
|
+
return paragraphs unless text.include?(start) or text.include?(finish)
|
|
121
|
+
current_pos,parent_pos = paragraphs,[paragraphs]
|
|
122
|
+
until current_text==""
|
|
123
|
+
st_pos = (current_text.include?(start) ? current_text.split(start)[0] : current_text).size
|
|
124
|
+
fn_pos = (current_text.include?(finish) ? current_text.split(finish)[0] : current_text).size
|
|
125
|
+
if fn_pos==st_pos # 无标志或标志互包含
|
|
126
|
+
current_pos << current_text
|
|
127
|
+
current_text = ""
|
|
128
|
+
end
|
|
129
|
+
if fn_pos < st_pos # 结束在前
|
|
130
|
+
csp = current_text.split(finish)
|
|
131
|
+
current_pos << csp[0]
|
|
132
|
+
current_pos << finish # 本级完成
|
|
133
|
+
cur_parent = parent_pos.pop # 取回上一级
|
|
134
|
+
current_pos = cur_parent # 返回上一级
|
|
135
|
+
current_text = current_text.sub(csp[0],"").sub(finish,"")
|
|
136
|
+
end
|
|
137
|
+
if st_pos < fn_pos # 开始在前
|
|
138
|
+
csp = current_text.split(start)
|
|
139
|
+
current_pos << csp[0] unless csp[0]=="" # 本级内容加入
|
|
140
|
+
current_pos << [start] # 下一级内容加入
|
|
141
|
+
parent_pos << current_pos # 本级加入上下文
|
|
142
|
+
current_pos = current_pos[-1]
|
|
143
|
+
current_text = current_text.sub(csp[0],"").sub(start,"")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
return paragraphs
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# 匹配 HTML 标签的嵌套结构(<tag ...> ... </tag>)。
|
|
150
|
+
#
|
|
151
|
+
# @param html [String] HTML 文本
|
|
152
|
+
# @param tag [String] 标签名(如 "table"、"div")
|
|
153
|
+
# @return [Array] 树状段落结构
|
|
154
|
+
def match_html_tag html,tag
|
|
155
|
+
paragraphs = self.match_cascade(html,"<#{tag}","</#{tag}>")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
module TextAbstract
|
|
161
|
+
|
|
162
|
+
# 类型操作模块:把字符串按目标类型转换/格式化。
|
|
163
|
+
module Type
|
|
164
|
+
# 按进制字符串转整数。
|
|
165
|
+
#
|
|
166
|
+
# @param radix [Integer] 进制,默认 10
|
|
167
|
+
# @return [Integer]
|
|
168
|
+
def int radix=10
|
|
169
|
+
self.to_i(radix)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# 转浮点并保留 tail 位小数。
|
|
173
|
+
#
|
|
174
|
+
# @param tail [Integer] 保留位数,默认 2
|
|
175
|
+
# @return [Float]
|
|
176
|
+
def float tail=2
|
|
177
|
+
self.to_f.round(tail)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# 转百分比字符串(乘 100 保留 tail 位 + "%")。
|
|
181
|
+
#
|
|
182
|
+
# @param tail [Integer] 保留位数,默认 2
|
|
183
|
+
# @return [String]
|
|
184
|
+
def precent tail=2
|
|
185
|
+
(self.to_f*100).round(tail).to_s+"%"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# 原样或 JSON 化输出。
|
|
189
|
+
#
|
|
190
|
+
# @param format [Symbol] :raw 原样 / :meta JSON 编码
|
|
191
|
+
# @return [String]
|
|
192
|
+
def text format=:raw
|
|
193
|
+
return self if format==:raw
|
|
194
|
+
return self.to_json if format==:meta
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# 转布尔:'true'/'false' 按字面,其余按是否为空。
|
|
198
|
+
#
|
|
199
|
+
# @return [Boolean]
|
|
200
|
+
def bool
|
|
201
|
+
return true if self.downcase=='true'
|
|
202
|
+
return false if self.downcase=='false'
|
|
203
|
+
return self.empty?
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# 文本操作模块:常见文本加工的直觉操作符。
|
|
208
|
+
module TextOperator
|
|
209
|
+
# 判断是否以 prefix 开头。
|
|
210
|
+
#
|
|
211
|
+
# @param prefix [String] 前缀
|
|
212
|
+
# @param strip [Boolean] 是否先去除两端空白
|
|
213
|
+
# @return [Boolean]
|
|
214
|
+
def head? prefix, strip=false #=> bool
|
|
215
|
+
left = strip ? self.strip : self
|
|
216
|
+
left[0..(prefix.length-1)]==prefix
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# 判断是否以 postfix 结尾。
|
|
220
|
+
#
|
|
221
|
+
# @param postfix [String] 后缀
|
|
222
|
+
# @param strip [Boolean] 是否先去除两端空白
|
|
223
|
+
# @return [Boolean]
|
|
224
|
+
def tail? postfix, strip=false #=> bool
|
|
225
|
+
left = strip ? self.strip : self
|
|
226
|
+
left[(-1*postfix.length)..-1]==postfix
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# include? / empty? 由 Ruby 核心提供,不再重复定义。
|
|
230
|
+
|
|
231
|
+
# 按模式匹配文本。
|
|
232
|
+
#
|
|
233
|
+
# @param pattern [String, Array<String>] 模式:单模式传字符串,多模式传数组
|
|
234
|
+
# @param mode [Symbol] :one 取首个匹配 / :all 全部匹配 / :union 多模式并集
|
|
235
|
+
# @return [MatchData, Array]
|
|
236
|
+
def matches pattern, mode=:one #=> match
|
|
237
|
+
return self.match(Regexp.new pattern) if mode==:one
|
|
238
|
+
return self.scan(Regexp.new pattern) if mode==:all
|
|
239
|
+
return self.scan(Regexp.union *pattern.map{|pat|Regexp.new(pat)}) if mode==:union
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# 按前置/后置标记抽行片段(draw_fragments 的字符串包装)。
|
|
243
|
+
#
|
|
244
|
+
# @param preflag [Regexp] 开始标记
|
|
245
|
+
# @param postflag [Regexp] 结束标记
|
|
246
|
+
# @return [Array<String>] 二元组 [上下文文本, 片段数组]
|
|
247
|
+
def draw_lines preflag, postflag #=> text, list
|
|
248
|
+
restext, contexts = TextAbstract.draw_fragments self, preflag, postflag
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# 严格顺序匹配段落(match_paragraph 的字符串包装)。
|
|
252
|
+
#
|
|
253
|
+
# @param start [String] 开始标记
|
|
254
|
+
# @param finish [String] 结束标记
|
|
255
|
+
# @return [Array<String>] 段落正文列表
|
|
256
|
+
def match_paragraph start, finish #=> list
|
|
257
|
+
paragraphs = TextAbstract.match_paragraph self, start, finish
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# 可嵌套匹配(match_cascade 的字符串包装)。
|
|
261
|
+
#
|
|
262
|
+
# @param start [String] 开始标记
|
|
263
|
+
# @param finish [String] 结束标记
|
|
264
|
+
# @return [Array] 树状段落结构
|
|
265
|
+
def match_cascade start, finish #=> list
|
|
266
|
+
paragraphs = TextAbstract.match_cascade self, start, finish
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# 匹配 HTML 标签嵌套结构(match_html_tag 的字符串包装)。
|
|
270
|
+
#
|
|
271
|
+
# @param tag [String] 标签名
|
|
272
|
+
# @return [Array] 树状段落结构
|
|
273
|
+
def match_xml tag #=> tree
|
|
274
|
+
document = TextAbstract.match_html_tag self,tag
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# length / size / strip / lstrip / rstrip 由 Ruby 核心提供。
|
|
278
|
+
|
|
279
|
+
# 把后续文本依次拼接在自身后面。
|
|
280
|
+
#
|
|
281
|
+
# @param texts [String...] 待拼接文本
|
|
282
|
+
# @return [String]
|
|
283
|
+
def join *texts #=> text
|
|
284
|
+
self+texts.map{|t|t}.join
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# 按人类直觉截取(1 起数的闭区间,ridx=0 表示取到末尾)。
|
|
288
|
+
#
|
|
289
|
+
# @param index1 [Integer] 起始序号(1 起)
|
|
290
|
+
# @param index2 [Integer] 结束序号,0 表示末尾
|
|
291
|
+
# @return [String]
|
|
292
|
+
def cut index1, index2=0 #=> text
|
|
293
|
+
self[(index1-1)..(index2-1)]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# +(拼接)与 *(重复)由 Ruby 核心提供。
|
|
297
|
+
|
|
298
|
+
# 从首次出现 text 的位置截断(含 text),未出现则原样返回。
|
|
299
|
+
#
|
|
300
|
+
# @param text [String] 截断标记
|
|
301
|
+
# @return [String]
|
|
302
|
+
def - text #=> text
|
|
303
|
+
self.include?(text) ? self.split(text).first : self
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# 按分隔符切分为列表(即 split 的直觉别名)。
|
|
307
|
+
#
|
|
308
|
+
# @param text [String] 分隔符
|
|
309
|
+
# @return [Array<String>]
|
|
310
|
+
def / text #=> list
|
|
311
|
+
self.split(text)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# 按模式替换:oldtext 匹配处替换为 newtext,最多 num 次。
|
|
315
|
+
#
|
|
316
|
+
# @param text [String] 匹配模式(正则源文本)
|
|
317
|
+
# @param newtext [String] 替换文本
|
|
318
|
+
# @param num [Integer, Symbol] 次数上限,:all 表示全部
|
|
319
|
+
# @return [String]
|
|
320
|
+
def exchange text, newtext, num=:all #=> text
|
|
321
|
+
num = 65535 if num==:all
|
|
322
|
+
count = 0
|
|
323
|
+
self.gsub(Regexp.new(text)) do |match|
|
|
324
|
+
count += 1
|
|
325
|
+
count <= num ? newtext : text
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# 列表操作模块:列表加工的直觉操作符。
|
|
331
|
+
#
|
|
332
|
+
# pop/push/shift/unshift/+/-/find/select/filter/map/collect/reduce/inject
|
|
333
|
+
# 均由 Ruby 核心提供,不再重复定义。
|
|
334
|
+
module ListOperator
|
|
335
|
+
# 把自身挂到列表头部,返回新列表。
|
|
336
|
+
#
|
|
337
|
+
# @param list [Array] 待挂接的元素序列
|
|
338
|
+
# @return [Array] 新列表
|
|
339
|
+
def catch *list #=> list(list)
|
|
340
|
+
list.unshift(self)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# 按条件块检索。
|
|
344
|
+
#
|
|
345
|
+
# @param num [Integer, Symbol] 0 只判存在 / 1 取首个 / :all 取全部 / n>1 取前 n 个
|
|
346
|
+
# @yield [item] 匹配条件
|
|
347
|
+
# @return [Boolean, Object, Array]
|
|
348
|
+
def match num=1, &block #=> bool | item | list
|
|
349
|
+
return self.find(&block).nil? if num ==0
|
|
350
|
+
return self.find(&block) if num ==1
|
|
351
|
+
return self.select(&block) if num ==:all
|
|
352
|
+
return self.select(&block)[0..(num-1)] if num > 1
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# 按人类直觉截取(1 起数的闭区间,index2=0 表示取到末尾)。
|
|
356
|
+
#
|
|
357
|
+
# @param index1 [Integer] 起始序号(1 起)
|
|
358
|
+
# @param index2 [Integer] 结束序号,0 表示末尾
|
|
359
|
+
# @return [Array]
|
|
360
|
+
def sub index1, index2=0 #=> list
|
|
361
|
+
self[(index1-1)..(index2-1)]
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# 树操作模块:散列表的直觉操作符。
|
|
366
|
+
#
|
|
367
|
+
# keys/values/[]/[]= 由 Ruby 核心提供,不再重复定义。
|
|
368
|
+
module TreeOperator
|
|
369
|
+
# 取键值([] 的直觉别名)。
|
|
370
|
+
#
|
|
371
|
+
# @param key [Object] 键
|
|
372
|
+
# @return [Object]
|
|
373
|
+
def get key #=> item
|
|
374
|
+
self[key]
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# 写键值([]= 的直觉别名)。
|
|
378
|
+
#
|
|
379
|
+
# @param key [Object] 键
|
|
380
|
+
# @param value [Object] 值
|
|
381
|
+
# @return [Object] 写入的值
|
|
382
|
+
def set key, value #=> item
|
|
383
|
+
self[key] = value
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# 合并树/列表/单值为新树。
|
|
387
|
+
#
|
|
388
|
+
# Hash 深合并;Array 按现有最大整数键续排;单值追加为下一个整数键。
|
|
389
|
+
#
|
|
390
|
+
# @param oprand [Hash, Array, Object] 待合并对象
|
|
391
|
+
# @return [Hash] 合并结果新树
|
|
392
|
+
def + oprand #=> tree
|
|
393
|
+
index = self.keys.filter{|k|k.instance_of?(Integer)}.max
|
|
394
|
+
index = index ? index+1 : 0
|
|
395
|
+
if oprand.instance_of?(Hash)
|
|
396
|
+
self.merge(oprand)
|
|
397
|
+
elsif oprand.instance_of?(Array)
|
|
398
|
+
self.merge oprand.reduce({}){|tr,op|index+=1;tr.merge({(index-1)=>op})}
|
|
399
|
+
else
|
|
400
|
+
self.merge({index=>oprand})
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
class String
|
|
408
|
+
include TextAbstract::Type
|
|
409
|
+
include TextAbstract::TextOperator
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
class Array
|
|
413
|
+
include TextAbstract::ListOperator
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
class Hash
|
|
417
|
+
include TextAbstract::TreeOperator
|
|
418
|
+
end
|