pseudohikiparser 0.0.4 → 0.0.5.develop
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.ja.md +13 -11
- data/README.md +14 -12
- data/bin/{pseudohiki2html.rb → pseudohiki2html} +1 -1
- data/lib/htmlelement/utils.rb +132 -0
- data/lib/htmlelement.rb +1 -1
- data/lib/pseudohiki/blockparser.rb +115 -15
- data/lib/pseudohiki/converter.rb +204 -135
- data/lib/pseudohiki/htmlformat.rb +65 -0
- data/lib/pseudohiki/markdownformat.rb +8 -1
- data/lib/pseudohiki/sinatra_helpers.rb +23 -0
- data/lib/pseudohiki/utils.rb +34 -0
- data/lib/pseudohiki/version.rb +1 -1
- data/lib/pseudohikiparser.rb +2 -0
- data/test/test_blockparser.rb +67 -0
- data/test/test_data/css/test.css +3 -0
- data/test/test_htmlelement_utils.rb +200 -0
- data/test/test_htmlformat.rb +419 -0
- data/test/test_markdownformat.rb +153 -0
- data/test/test_plaintextformat.rb +85 -0
- data/test/test_pseudohiki2html.rb +111 -26
- data/test/test_utils.rb +22 -0
- metadata +14 -10
- data/test/test_latexformat.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5be7e5cb75ef3ae2d601b8dbf03aa768c5d0e1c1
|
4
|
+
data.tar.gz: 6d9de4c42fa1339e0922903764c07cd8edc13c5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d5e5d27c733773551dc372df645933aa14d3db2c5c212d73646010db2fc59c6756ba13c37c662b9f5e56f48ebba4e98f014c853d8ab9c7e9cb53c628f645dbc
|
7
|
+
data.tar.gz: 9bb79366e51c3ca3f0c6d51abdcdd7f399d905130113ae86d0311f16734258da159f3824727c436ca6551f3a233fb2cf50bd861f65233ec76d617e4e426a6445
|
data/README.ja.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PseudoHikiParser
|
2
2
|
================
|
3
3
|
|
4
|
-
PseudoHikiParserは[Hiki](https://github.com/hiki/hikidoc)
|
4
|
+
PseudoHikiParserは[Hiki](https://github.com/hiki/hikidoc)に似た記法で書かれたテキストをパースし、HTML・Markdownその他のファイル形式に変換します。
|
5
5
|
|
6
6
|
このツールは次の目的を念頭において作成中です。
|
7
7
|
|
@@ -26,7 +26,7 @@ gem install pseudohikiparser
|
|
26
26
|
もし実験的な機能も試してみたければ、
|
27
27
|
|
28
28
|
```
|
29
|
-
gem install pseudohikiparser --
|
29
|
+
gem install pseudohikiparser --version 0.0.5.develop
|
30
30
|
```
|
31
31
|
|
32
32
|
## 使い方
|
@@ -44,16 +44,16 @@ gem install pseudohikiparser --pre
|
|
44
44
|
|
45
45
|
このサンプルは[develop branch](https://github.com/nico-hn/PseudoHikiParser/tree/develop/samples)に置いてあります。
|
46
46
|
|
47
|
-
### pseudohiki2html
|
47
|
+
### pseudohiki2html
|
48
48
|
|
49
|
-
_(pseudohiki2html
|
49
|
+
_(pseudohiki2htmlは現時点ではPseudoHikiParserの利用例として提供されており、現段階ではオプションも継続的に変更されることにご注意ください。)_
|
50
50
|
|
51
|
-
インストールが終わると**pseudohiki2html
|
51
|
+
インストールが終わると**pseudohiki2html**というコマンドが使えるようになります。
|
52
52
|
|
53
53
|
コマンドラインで次のように入力すると
|
54
54
|
|
55
55
|
```
|
56
|
-
pseudohiki2html
|
56
|
+
pseudohiki2html <<TEXT
|
57
57
|
!! The first heading
|
58
58
|
The first paragraph
|
59
59
|
TEXT
|
@@ -88,7 +88,7 @@ The first paragraph
|
|
88
88
|
`--output`オプションを使ってファイル名を指定した場合は
|
89
89
|
|
90
90
|
```
|
91
|
-
pseudohiki2html
|
91
|
+
pseudohiki2html --output first_example.html <<TEXT
|
92
92
|
!! The first heading
|
93
93
|
The first paragraph
|
94
94
|
TEXT
|
@@ -96,10 +96,12 @@ TEXT
|
|
96
96
|
|
97
97
|
で、結果がfirst\_example.htmlに保存されます。
|
98
98
|
|
99
|
-
その他のオプションについては`pseudohiki2html
|
99
|
+
その他のオプションについては`pseudohiki2html --help`でご参照ください。
|
100
100
|
|
101
101
|
#### 非互換な変更
|
102
102
|
|
103
|
+
version 0.0.4まで、コマンド名は`pseudohiki2html.rb`でした。
|
104
|
+
|
103
105
|
version 0.0.0.9.developから、コマンドラインのオプション名を以下の通り変更しています。
|
104
106
|
|
105
107
|
|元の名前 |新しい名前 |注記 |
|
@@ -406,7 +408,7 @@ paragraph 3
|
|
406
408
|
|
407
409
|
以下のクラスのうちの一部は、部分的にしか実装されていないかテストが十分ではないことにご注意ください。
|
408
410
|
|
409
|
-
### [HtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L8), [XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#
|
411
|
+
### [HtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L8), [XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L320)
|
410
412
|
|
411
413
|
これらのクラスのクラスメソッド(HtmlFormat|XhtmlFormat).formatは[HtmlElement](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/htmlelement.rb)オブジェクトを木にしたものを返すため、以下の例のように後から手を加えることができます。
|
412
414
|
|
@@ -450,11 +452,11 @@ paragraph 2 that contains <a class="pdf" href="http://www.example.org/example.pd
|
|
450
452
|
</div>
|
451
453
|
```
|
452
454
|
|
453
|
-
### [Xhtml5Format](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#
|
455
|
+
### [Xhtml5Format](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L326)
|
454
456
|
|
455
457
|
HTML5への変換用のVisitorクラスです。
|
456
458
|
|
457
|
-
現時点では\<section\>要素の扱い以外に[XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#
|
459
|
+
現時点では\<section\>要素の扱い以外に[XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L320)との違いは余りありません。
|
458
460
|
|
459
461
|
### [PlainTextFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/plaintextformat.rb)
|
460
462
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PseudoHikiParser
|
2
2
|
================
|
3
3
|
|
4
|
-
PseudoHikiParser
|
4
|
+
PseudoHikiParser parses texts written in a [Hiki](https://github.com/hiki/hikidoc) like notation, and converts them into HTML, Markdown or other formats.
|
5
5
|
|
6
6
|
I am writing this tool with following objectives in mind,
|
7
7
|
|
@@ -26,7 +26,7 @@ gem install pseudohikiparser
|
|
26
26
|
or if you also want to try out experimental features,
|
27
27
|
|
28
28
|
```
|
29
|
-
gem install pseudohikiparser --
|
29
|
+
gem install pseudohikiparser --version 0.0.5.develop
|
30
30
|
```
|
31
31
|
|
32
32
|
## Usage
|
@@ -44,16 +44,16 @@ And results of conversion
|
|
44
44
|
|
45
45
|
You will find these samples in [develop branch](https://github.com/nico-hn/PseudoHikiParser/tree/develop/samples).
|
46
46
|
|
47
|
-
### pseudohiki2html
|
47
|
+
### pseudohiki2html
|
48
48
|
|
49
|
-
_(Please note that pseudohiki2html
|
49
|
+
_(Please note that pseudohiki2html is currently provided as a showcase of PseudoHikiParser, and the options will be continuously changed at this stage of development.)_
|
50
50
|
|
51
|
-
After the installation of PseudoHikiParser, you can use a command: **pseudohiki2html
|
51
|
+
After the installation of PseudoHikiParser, you can use a command: **pseudohiki2html**.
|
52
52
|
|
53
53
|
Type the following lines at the command prompt:
|
54
54
|
|
55
55
|
```
|
56
|
-
pseudohiki2html
|
56
|
+
pseudohiki2html <<TEXT
|
57
57
|
!! The first heading
|
58
58
|
The first paragraph
|
59
59
|
TEXT
|
@@ -88,7 +88,7 @@ The first paragraph
|
|
88
88
|
And if you specify a file name with `--output` option:
|
89
89
|
|
90
90
|
```
|
91
|
-
pseudohiki2html
|
91
|
+
pseudohiki2html --output first_example.html <<TEXT
|
92
92
|
!! The first heading
|
93
93
|
The first paragraph
|
94
94
|
TEXT
|
@@ -96,10 +96,12 @@ TEXT
|
|
96
96
|
|
97
97
|
the result will be saved in first\_example.html.
|
98
98
|
|
99
|
-
For more options, please try `pseudohiki2html
|
99
|
+
For more options, please try `pseudohiki2html --help`
|
100
100
|
|
101
101
|
#### Incompatible changes
|
102
102
|
|
103
|
+
Until version 0.0.4, the name of the command was `pseudohiki2html.rb`.
|
104
|
+
|
103
105
|
From version 0.0.0.9.develop, command line options are renamed as follows:
|
104
106
|
|
105
107
|
|old name |new name |note |
|
@@ -406,7 +408,7 @@ paragraph 3
|
|
406
408
|
|
407
409
|
Please note that some of the following classes are implemented partly or not tested well.
|
408
410
|
|
409
|
-
### [HtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L8), [XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#
|
411
|
+
### [HtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L8), [XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L320)
|
410
412
|
|
411
413
|
Their class method (HtmlFormat|XhtmlFormat).format returns a tree of [HtmlElement](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/htmlelement.rb) objects, and you can traverse the tree as in the following example.
|
412
414
|
|
@@ -450,11 +452,11 @@ paragraph 2 that contains <a class="pdf" href="http://www.example.org/example.pd
|
|
450
452
|
</div>
|
451
453
|
```
|
452
454
|
|
453
|
-
### [Xhtml5Format](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#
|
455
|
+
### [Xhtml5Format](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L326)
|
454
456
|
|
455
457
|
This visitor is for HTML5.
|
456
458
|
|
457
|
-
Currently there aren't many differences with [XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#
|
459
|
+
Currently there aren't many differences with [XhtmlFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/htmlformat.rb#L320) except for the handling of \<section\> elements.
|
458
460
|
|
459
461
|
### [PlainTextFormat](https://github.com/nico-hn/PseudoHikiParser/blob/develop/lib/pseudohiki/plaintextformat.rb)
|
460
462
|
|
@@ -548,7 +550,7 @@ The first paragraph
|
|
548
550
|
|
549
551
|
#### Limitations
|
550
552
|
|
551
|
-
You
|
553
|
+
You cannot convert malformed lists with this visitor class. That means list items must be nested hierarchically and if you skip a level in the sequence of items, the result of coversions will be corrupted.
|
552
554
|
|
553
555
|
The following is an example of malformed list in which the first level is skipped:
|
554
556
|
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'htmlelement'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
class HtmlElement
|
7
|
+
module Utils
|
8
|
+
def self.collect_elements(tree)
|
9
|
+
[].tap do |elms|
|
10
|
+
tree.traverse do |elm|
|
11
|
+
matched = yield elm
|
12
|
+
elms.push elm if matched
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.collect_elements_by_name(tree, name)
|
18
|
+
collect_elements(tree) do |elm|
|
19
|
+
elm.kind_of? HtmlElement and elm.tagname == name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class LinkManager
|
24
|
+
SEP = "/".freeze
|
25
|
+
SCHEME_RE = /^(https?|ftp):\/\//
|
26
|
+
DEFAULT_SCHEME = 'http://'
|
27
|
+
|
28
|
+
def initialize(domain_name, from_host_names=[], scheme=DEFAULT_SCHEME)
|
29
|
+
domain_name += SEP unless domain_name.end_with?(SEP)
|
30
|
+
domain_name = scheme + domain_name unless SCHEME_RE =~ domain_name
|
31
|
+
@domain_name = URI.parse(domain_name)
|
32
|
+
@domain_name_re = Regexp.compile(Regexp.escape(domain_name))
|
33
|
+
unless from_host_names.empty?
|
34
|
+
@from_host_names_re = compile_from_names_re(from_host_names)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def unify_host_names(url)
|
39
|
+
return url unless @from_host_names_re
|
40
|
+
url.sub(@from_host_names_re, @domain_name.host)
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert_to_relative_path(url)
|
44
|
+
return url unless SCHEME_RE =~ url
|
45
|
+
return "./".freeze if default_domain?(url)
|
46
|
+
(URI.parse(url) - @domain_name).to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def use_relative_path_for_in_domain_links(html)
|
50
|
+
links = Utils.collect_elements_by_name(html, "a".freeze)
|
51
|
+
links.each do |a|
|
52
|
+
href = a["href"]
|
53
|
+
href = unify_host_names(href)
|
54
|
+
href = convert_to_relative_path(href)
|
55
|
+
a["href"] = href
|
56
|
+
end
|
57
|
+
html
|
58
|
+
end
|
59
|
+
|
60
|
+
def external_link?(url)
|
61
|
+
if SCHEME_RE.match(url)
|
62
|
+
URI.parse(url).host != @domain_name.host
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def compile_from_names_re(from_host_names)
|
69
|
+
escaped_names = from_host_names.map {|name| Regexp.escape(name) }
|
70
|
+
Regexp.compile(escaped_names.join("|"))
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_domain?(url)
|
74
|
+
url += SEP unless url.end_with?(SEP)
|
75
|
+
(URI.parse(url) - @domain_name).to_s.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class TableManager
|
80
|
+
TH, TD, CAPTION = %w(th td caption)
|
81
|
+
ROWSPAN, COLSPAN = %w(rowspan colspan)
|
82
|
+
SCOPE, COL, ROW = %w(scope col row)
|
83
|
+
|
84
|
+
def self.assign_scope(table)
|
85
|
+
@manager.assign_scope(table)
|
86
|
+
end
|
87
|
+
|
88
|
+
def guess_header_scope(table)
|
89
|
+
col_scope = COL
|
90
|
+
row_scope = ROW
|
91
|
+
|
92
|
+
cell_with_index(table) do |cell, i, j|
|
93
|
+
return if span_set?(cell, ROWSPAN) or span_set?(cell, COLSPAN)
|
94
|
+
col_scope = nil unless (i == 0) == (cell.tagname == TH)
|
95
|
+
row_scope = nil unless (j == 0) == (cell.tagname == TH)
|
96
|
+
end
|
97
|
+
|
98
|
+
col_scope or row_scope
|
99
|
+
end
|
100
|
+
|
101
|
+
def assign_scope(table)
|
102
|
+
scope = guess_header_scope(table)
|
103
|
+
return table unless scope
|
104
|
+
Utils.collect_elements_by_name(table, TH).each do |th|
|
105
|
+
th[SCOPE] = scope
|
106
|
+
end
|
107
|
+
table
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def cell_with_index(table)
|
113
|
+
children_except_caption(table).each_with_index do |tr, i|
|
114
|
+
tr.children.each_with_index do |cell, j|
|
115
|
+
yield cell, i, j
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def children_except_caption(table)
|
121
|
+
children = table.children
|
122
|
+
children[0].tagname == CAPTION ? children[1..-1] : children
|
123
|
+
end
|
124
|
+
|
125
|
+
def span_set?(cell, span)
|
126
|
+
cell[span] && cell[span] > 1
|
127
|
+
end
|
128
|
+
|
129
|
+
@manager = new
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/htmlelement.rb
CHANGED
@@ -41,7 +41,7 @@ class HtmlElement
|
|
41
41
|
|
42
42
|
ELEMENT_TYPES = {
|
43
43
|
:BLOCK => %w(html body div table colgroup thead tbody ul ol dl head p pre blockquote style),
|
44
|
-
:HEADING_TYPE_BLOCK => %w(dt dd tr title h1 h2 h3 h4 h5 h6),
|
44
|
+
:HEADING_TYPE_BLOCK => %w(dt dd tr title caption h1 h2 h3 h4 h5 h6),
|
45
45
|
:LIST_ITEM_TYPE_BLOCK => %w(li col),
|
46
46
|
:EMPTY_BLOCK => %w(img meta link base input hr)
|
47
47
|
}
|
@@ -38,14 +38,21 @@ module PseudoHiki
|
|
38
38
|
end
|
39
39
|
|
40
40
|
class BlockStack < TreeStack
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
attr_reader :stack
|
42
|
+
|
43
|
+
def pop_with_breaker(breaker=nil)
|
44
|
+
current_node.parse_leafs(breaker)
|
45
|
+
pop
|
46
|
+
end
|
47
|
+
|
48
|
+
def current_heading_level
|
49
|
+
i = @stack.rindex {|node| node.kind_of? BlockElement::HeadingNode }
|
50
|
+
@stack[i].level || 0
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
47
54
|
class BlockLeaf < BlockStack::Leaf
|
48
|
-
attr_accessor :level, :node_id
|
55
|
+
attr_accessor :level, :node_id, :decorator
|
49
56
|
|
50
57
|
class << self
|
51
58
|
attr_accessor :head_re
|
@@ -81,7 +88,7 @@ module PseudoHiki
|
|
81
88
|
super(stack)
|
82
89
|
end
|
83
90
|
|
84
|
-
def parse_leafs
|
91
|
+
def parse_leafs(breaker)
|
85
92
|
parsed = InlineParser.parse(join)
|
86
93
|
clear
|
87
94
|
concat(parsed)
|
@@ -131,6 +138,10 @@ module PseudoHiki
|
|
131
138
|
first.level if first # @cached_level ||= (first.level if first)
|
132
139
|
end
|
133
140
|
|
141
|
+
def decorator
|
142
|
+
first.decorator if first
|
143
|
+
end
|
144
|
+
|
134
145
|
def push_self(stack)
|
135
146
|
@stack = stack
|
136
147
|
super(stack)
|
@@ -140,11 +151,11 @@ module PseudoHiki
|
|
140
151
|
not (kind_of? breaker.block and level == breaker.level)
|
141
152
|
end
|
142
153
|
|
143
|
-
def parse_leafs; end
|
154
|
+
def parse_leafs(breaker); end
|
144
155
|
|
145
156
|
def add_leaf(line, blockparser)
|
146
157
|
leaf = create_leaf(line, blockparser)
|
147
|
-
blockparser.stack.
|
158
|
+
blockparser.stack.pop_with_breaker(leaf) while blockparser.breakable?(leaf)
|
148
159
|
blockparser.stack.push leaf
|
149
160
|
end
|
150
161
|
|
@@ -156,8 +167,8 @@ module PseudoHiki
|
|
156
167
|
end
|
157
168
|
|
158
169
|
class NonNestedBlockNode < BlockNode
|
159
|
-
def parse_leafs
|
160
|
-
each {|leaf| leaf.parse_leafs }
|
170
|
+
def parse_leafs(breaker)
|
171
|
+
each {|leaf| leaf.parse_leafs(breaker) }
|
161
172
|
end
|
162
173
|
end
|
163
174
|
|
@@ -175,13 +186,15 @@ module PseudoHiki
|
|
175
186
|
end
|
176
187
|
end
|
177
188
|
|
189
|
+
class UnmatchedSectioningTagError < StandardError; end
|
190
|
+
|
178
191
|
module BlockElement
|
179
192
|
{
|
180
|
-
BlockLeaf => %w(DescLeaf VerbatimLeaf TableLeaf CommentOutLeaf BlockNodeEnd HrLeaf),
|
193
|
+
BlockLeaf => %w(DescLeaf VerbatimLeaf TableLeaf CommentOutLeaf BlockNodeEnd HrLeaf DecoratorLeaf SectioningNodeEnd),
|
181
194
|
NonNestedBlockLeaf => %w(QuoteLeaf ParagraphLeaf),
|
182
195
|
NestedBlockLeaf => %w(HeadingLeaf),
|
183
196
|
ListTypeLeaf => %w(ListLeaf EnumLeaf),
|
184
|
-
BlockNode => %w(DescNode VerbatimNode TableNode CommentOutNode HrNode),
|
197
|
+
BlockNode => %w(DescNode VerbatimNode TableNode CommentOutNode HrNode DecoratorNode SectioningNode),
|
185
198
|
NonNestedBlockNode => %w(QuoteNode ParagraphNode),
|
186
199
|
NestedBlockNode => %w(HeadingNode),
|
187
200
|
ListTypeBlockNode => %w(ListNode EnumNode),
|
@@ -204,15 +217,100 @@ module PseudoHiki
|
|
204
217
|
attr_accessor :in_block_tag
|
205
218
|
|
206
219
|
def add_leaf(line, blockparser)
|
207
|
-
return @stack.
|
220
|
+
return @stack.pop_with_breaker if VERBATIM_END =~ line
|
208
221
|
return super(line, blockparser) unless @in_block_tag
|
209
222
|
line = " #{line}" if BlockNodeEnd.head_re =~ line and not @in_block_tag
|
210
223
|
@stack.push VerbatimLeaf.create(line, @in_block_tag)
|
211
224
|
end
|
212
225
|
end
|
213
226
|
|
227
|
+
class DecoratorNode
|
228
|
+
DECORATOR_PAT = /\A(?:([^\[\]:]+))?(?:\[([^\[\]]+)\])?(?::\s*(\S.*))?/o
|
229
|
+
LABEL_SEP = [":"]
|
230
|
+
|
231
|
+
class DecoratorItem < Struct.new(:string, :type, :id, :value)
|
232
|
+
def self.create(leaf)
|
233
|
+
m = DECORATOR_PAT.match(leaf.join)
|
234
|
+
return nil unless m
|
235
|
+
args = m.to_a
|
236
|
+
if i = leaf.index(LABEL_SEP)
|
237
|
+
value = leaf.dup
|
238
|
+
value.shift(i + 1)
|
239
|
+
args[-1] = lstrip_value!(value)
|
240
|
+
end
|
241
|
+
self.new(*args)
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.lstrip_value!(value)
|
245
|
+
head_val = value[0][0]
|
246
|
+
if head_val.kind_of? String and head_val.start_with? " ".freeze
|
247
|
+
value[0][0] = head_val.lstrip
|
248
|
+
end
|
249
|
+
value
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def parse_leafs(breaker)
|
254
|
+
decorator = {}
|
255
|
+
breaker.decorator = decorator
|
256
|
+
@stack.remove_current_node.each do |leaf|
|
257
|
+
if item = DecoratorItem.create(leaf)
|
258
|
+
decorator[item.type || :id] = item
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def breakable?(breaker)
|
264
|
+
return super if breaker.kind_of? DecoratorLeaf
|
265
|
+
parse_leafs(breaker)
|
266
|
+
@stack.current_node.breakable?(breaker)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
class DecoratorLeaf
|
271
|
+
def push_sectioning_node(stack, node_class)
|
272
|
+
node = node_class.new
|
273
|
+
m = DecoratorNode::DECORATOR_PAT.match(join)
|
274
|
+
node.node_id = m[2]
|
275
|
+
node.section_level = stack.current_heading_level if node.kind_of? SectioningNode
|
276
|
+
stack.push(node)
|
277
|
+
end
|
278
|
+
|
279
|
+
def push_self(stack)
|
280
|
+
decorator_type = self[0][0]
|
281
|
+
if decorator_type.start_with? "begin[".freeze
|
282
|
+
push_sectioning_node(stack, SectioningNode)
|
283
|
+
elsif decorator_type.start_with? "end[".freeze
|
284
|
+
push_sectioning_node(stack, SectioningNodeEnd)
|
285
|
+
else
|
286
|
+
super
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class SectioningNode
|
292
|
+
attr_accessor :section_level
|
293
|
+
|
294
|
+
def breakable?(breaker)
|
295
|
+
breaker.kind_of? HeadingLeaf and @section_level >= breaker.level
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
class SectioningNodeEnd
|
300
|
+
def push_self(stack)
|
301
|
+
n = stack.stack.rindex do |node|
|
302
|
+
node.kind_of? SectioningNode and node.node_id == node_id
|
303
|
+
end
|
304
|
+
raise UnmatchedSectioningTagError unless n
|
305
|
+
stack.pop until stack.stack.length == n
|
306
|
+
rescue UnmatchedSectioningTagError => e
|
307
|
+
STDERR.puts "#{e}: The start tag for '#{node_id}' is not found."
|
308
|
+
# FIXME: The handling of this error should be changed appropriately.
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
214
312
|
class QuoteNode
|
215
|
-
def parse_leafs
|
313
|
+
def parse_leafs(breaker)
|
216
314
|
self[0] = BlockParser.parse(self[0], AutoLink::Off)
|
217
315
|
end
|
218
316
|
end
|
@@ -273,7 +371,8 @@ module PseudoHiki
|
|
273
371
|
[HrLeaf, HrNode],
|
274
372
|
[ListLeaf, ListNode],
|
275
373
|
[EnumLeaf, EnumNode],
|
276
|
-
[BlockNodeEnd, BlockNodeEnd] # special case
|
374
|
+
[BlockNodeEnd, BlockNodeEnd], # special case
|
375
|
+
[DecoratorLeaf, DecoratorNode]
|
277
376
|
].each do |leaf, node|
|
278
377
|
PARENT_NODE[leaf] = node
|
279
378
|
end
|
@@ -286,6 +385,7 @@ module PseudoHiki
|
|
286
385
|
['!', HeadingLeaf],
|
287
386
|
['""', QuoteLeaf],
|
288
387
|
['||', TableLeaf],
|
388
|
+
['//@', DecoratorLeaf],
|
289
389
|
['//', CommentOutLeaf],
|
290
390
|
['----\s*$', HrLeaf]]
|
291
391
|
|
@@ -331,7 +431,7 @@ module PseudoHiki
|
|
331
431
|
def read_lines(lines)
|
332
432
|
each_line = lines.respond_to?(:each_line) ? :each_line : :each
|
333
433
|
lines.send(each_line) {|line| @stack.current_node.add_leaf(line, self) }
|
334
|
-
@stack.
|
434
|
+
@stack.pop_with_breaker
|
335
435
|
end
|
336
436
|
end
|
337
437
|
|