@@ -2,41 +2,54 @@ use std::sync::Arc;
2
2
3
3
use nom:: {
4
4
branch:: alt,
5
- bytes:: complete:: { tag , tag_no_case , take_while1 } ,
5
+ bytes:: complete:: { is_not , tag , tag_no_case } ,
6
6
character:: complete:: { alpha1, char} ,
7
- combinator:: map,
8
- multi:: many0,
7
+ combinator:: { map, opt, value, verify} ,
8
+ error:: ParseError ,
9
+ multi:: { fold_many1, many0} ,
9
10
sequence:: { delimited, preceded} ,
10
- IResult ,
11
+ IResult , Parser ,
11
12
} ;
12
13
13
14
use super :: { BbcodeNode , BbcodeTag } ;
14
15
16
+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
17
+ enum StringFragment < ' a > {
18
+ Literal ( & ' a str ) ,
19
+ EscapedChar ( char ) ,
20
+ }
21
+
15
22
pub fn parse_bbcode ( input : & str ) -> IResult < & str , Vec < Arc < BbcodeNode > > > {
23
+ parse_bbcode_internal ( input)
24
+ }
25
+
26
+ fn parse_bbcode_internal < ' a , E : ParseError < & ' a str > > (
27
+ input : & ' a str ,
28
+ ) -> IResult < & ' a str , Vec < Arc < BbcodeNode > > , E > {
16
29
many0 ( map ( parse_node, |element| element. into ( ) ) ) ( input)
17
30
}
18
31
19
- fn parse_node ( input : & str ) -> IResult < & str , BbcodeNode > {
32
+ fn parse_node < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , BbcodeNode , E > {
20
33
alt ( (
21
- map ( parse_text, |text| BbcodeNode :: Text ( text . into ( ) ) ) ,
34
+ map ( parse_text, BbcodeNode :: Text ) ,
22
35
map ( parse_tag, BbcodeNode :: Tag ) ,
23
36
) ) ( input)
24
37
}
25
38
26
- fn parse_tag ( input : & str ) -> IResult < & str , BbcodeTag > {
39
+ fn parse_tag < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , BbcodeTag , E > {
27
40
let ( input, mut tag) = parse_opening_tag ( input) ?;
28
- let ( input, children) = parse_bbcode ( input) ?;
41
+ let ( input, children) = parse_bbcode_internal ( input) ?;
29
42
let ( input, _) = parse_closing_tag ( input, & tag. name ) ?;
30
43
31
44
tag. children = children;
32
45
33
46
Ok ( ( input, tag) )
34
47
}
35
48
36
- fn parse_opening_tag ( input : & str ) -> IResult < & str , BbcodeTag > {
49
+ fn parse_opening_tag < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , BbcodeTag , E > {
37
50
let ( mut input, mut tag) = map ( preceded ( char ( '[' ) , alpha1) , BbcodeTag :: new) ( input) ?;
38
51
39
- if let Ok ( ( new_input, simple_param) ) = preceded ( char ( '=' ) , parse_param) ( input) {
52
+ if let Ok ( ( new_input, simple_param) ) = preceded ( char ( '=' ) , parse_param :: < E > ) ( input) {
40
53
tag. add_simple_param ( simple_param) ;
41
54
input = new_input;
42
55
}
@@ -46,20 +59,91 @@ fn parse_opening_tag(input: &str) -> IResult<&str, BbcodeTag> {
46
59
Ok ( ( input, tag) )
47
60
}
48
61
49
- fn parse_closing_tag < ' a > ( input : & ' a str , tag_name : & str ) -> IResult < & ' a str , ( ) > {
62
+ fn parse_closing_tag < ' a , E : ParseError < & ' a str > > (
63
+ input : & ' a str ,
64
+ tag_name : & str ,
65
+ ) -> IResult < & ' a str , ( ) , E > {
50
66
map (
51
67
delimited ( tag ( "[/" ) , tag_no_case ( tag_name) , char ( ']' ) ) ,
52
68
|_| ( ) ,
53
69
) ( input)
54
70
}
55
71
56
- fn parse_text ( input : & str ) -> IResult < & str , & str > {
57
- take_while1 ( |ch| ![ '[' , ']' ] . contains ( & ch) ) ( input)
72
+ fn parse_text < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , String , E > {
73
+ parse_inner_string ( "[]\\ " ) . parse ( input)
74
+ }
75
+
76
+ fn parse_param < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , String , E > {
77
+ alt ( (
78
+ parse_quoted_string,
79
+ map ( parse_literal ( "\" \\ []" ) , |literal| literal. to_string ( ) ) ,
80
+ ) )
81
+ . parse ( input)
82
+ }
83
+
84
+ fn parse_quoted_string < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , String , E > {
85
+ delimited (
86
+ char ( '"' ) ,
87
+ map ( opt ( parse_inner_string ( "\" \\ " ) ) , |string| {
88
+ string. unwrap_or_default ( )
89
+ } ) ,
90
+ char ( '"' ) ,
91
+ )
92
+ . parse ( input)
93
+ }
94
+
95
+ fn parse_inner_string < ' a , E : ParseError < & ' a str > > (
96
+ exclude : & ' a str ,
97
+ ) -> impl Parser < & ' a str , String , E > {
98
+ move |input| {
99
+ fold_many1 (
100
+ parse_fragment ( exclude) ,
101
+ String :: new,
102
+ |mut string, fragment| {
103
+ match fragment {
104
+ StringFragment :: Literal ( s) => string. push_str ( s) ,
105
+ StringFragment :: EscapedChar ( c) => string. push ( c) ,
106
+ }
107
+ string
108
+ } ,
109
+ )
110
+ . parse ( input)
111
+ }
112
+ }
113
+
114
+ fn parse_fragment < ' a , E : ParseError < & ' a str > > (
115
+ exclude : & ' a str ,
116
+ ) -> impl Parser < & ' a str , StringFragment < ' a > , E > {
117
+ move |input| {
118
+ alt ( (
119
+ map ( parse_literal ( exclude) , StringFragment :: Literal ) ,
120
+ map ( parse_escaped_char, StringFragment :: EscapedChar ) ,
121
+ ) )
122
+ . parse ( input)
123
+ }
124
+ }
125
+
126
+ fn parse_literal < ' a , E : ParseError < & ' a str > > ( exclude : & ' a str ) -> impl Parser < & ' a str , & ' a str , E > {
127
+ move |input| verify ( is_not ( exclude) , |s : & str | !s. is_empty ( ) ) . parse ( input)
58
128
}
59
129
60
- fn parse_param ( input : & str ) -> IResult < & str , & str > {
61
- // TODO: Quote delimited params
62
- take_while1 ( |ch| ![ '[' , ']' , ' ' , '=' ] . contains ( & ch) ) ( input)
130
+ fn parse_escaped_char < ' a , E : ParseError < & ' a str > > ( input : & ' a str ) -> IResult < & ' a str , char , E > {
131
+ preceded (
132
+ char ( '\\' ) ,
133
+ alt ( (
134
+ value ( '"' , char ( '"' ) ) ,
135
+ value ( '/' , char ( '/' ) ) ,
136
+ value ( '[' , char ( '[' ) ) ,
137
+ value ( ']' , char ( ']' ) ) ,
138
+ value ( '\\' , char ( '\\' ) ) ,
139
+ value ( '\n' , char ( 'n' ) ) ,
140
+ value ( '\r' , char ( 'r' ) ) ,
141
+ value ( '\t' , char ( 't' ) ) ,
142
+ value ( '\u{08}' , char ( 'b' ) ) ,
143
+ value ( '\u{0C}' , char ( 'f' ) ) ,
144
+ ) ) ,
145
+ )
146
+ . parse ( input)
63
147
}
64
148
65
149
#[ cfg( test) ]
@@ -77,6 +161,17 @@ mod tests {
77
161
)
78
162
}
79
163
164
+ #[ test]
165
+ fn test_parse_escaped_text ( ) {
166
+ let input = r#"[b]\[\]\\\"\t\n[/b]"# ;
167
+ let expected_tag = BbcodeTag :: new ( "b" ) . with_text ( "[]\\ \" \t \n " ) ;
168
+
169
+ assert_eq ! (
170
+ parse_bbcode( input) ,
171
+ Ok ( ( "" , vec![ BbcodeNode :: Tag ( expected_tag) . into( ) ] ) )
172
+ )
173
+ }
174
+
80
175
#[ test]
81
176
fn test_parse_simple_param ( ) {
82
177
let input = "[c=#ff00ff]test[/c]" ;
@@ -90,6 +185,19 @@ mod tests {
90
185
)
91
186
}
92
187
188
+ #[ test]
189
+ fn test_parse_quoted_param ( ) {
190
+ let input = r#"[c="dark \"blue\" with yellow"]test[/c]"# ;
191
+ let expected_tag = BbcodeTag :: new ( "c" )
192
+ . with_simple_param ( r#"dark "blue" with yellow"# )
193
+ . with_text ( "test" ) ;
194
+
195
+ assert_eq ! (
196
+ parse_bbcode( input) ,
197
+ Ok ( ( "" , vec![ BbcodeNode :: Tag ( expected_tag) . into( ) ] ) )
198
+ )
199
+ }
200
+
93
201
#[ test]
94
202
fn test_parse_nested ( ) {
95
203
let input = "[b]test [i]nested[/i][/b]" ;
0 commit comments