8
8
// distribute, sublicense, and/or sell copies of the Software, and to
9
9
// permit persons to whom the Software is furnished to do so, subject to
10
10
// the following conditions:
11
- //
11
+ //
12
12
// The above copyright notice and this permission notice shall be
13
13
// included in all copies or substantial portions of the Software.
14
- //
14
+ //
15
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
16
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
17
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -32,28 +32,6 @@ namespace Uno.Xaml
32
32
{
33
33
internal class ParsedMarkupExtensionInfo
34
34
{
35
- /// <summary>
36
- /// This regex returns the members of a binding expression which are separated
37
- /// by commas but keeps the commas inside the member value.
38
- /// e.g. [Property], ConverterParameter='A', TargetNullValue='B', FallbackValue='C,D,E,F' returns
39
- /// - [Property]
40
- /// - ConverterParameter='A'
41
- /// - TargetNullValue='B'
42
- /// - FallbackValue='C,D,E,F'
43
- /// </summary>
44
- private static Regex BindingMembersRegex = new Regex ( "[^'\" ,]+'[^^']+'|[^'\" ,]+\" [^\" ]+\" |[^,]+" ) ;
45
- private static Regex BalancedMarkupBlockRegex = new Regex ( @"
46
- # modified from: https://stackoverflow.com/a/7899205
47
- { # First '{'
48
- (?:
49
- [^{}]| # Match all non-braces
50
- (?<open>{)| # Match '{', and capture into 'open'
51
- (?<-open>}) # Match '}', and delete the 'open' capture
52
- )+?
53
- (?(open)(?!)) # Fails if 'open' stack isn't empty!
54
- } # Last '}'
55
- " , RegexOptions . IgnorePatternWhitespace ) ;
56
-
57
35
Dictionary < XamlMember , object > args = new Dictionary < XamlMember , object > ( ) ;
58
36
public Dictionary < XamlMember , object > Arguments
59
37
{
@@ -74,55 +52,40 @@ public static ParsedMarkupExtensionInfo Parse(string raw, IXamlNamespaceResolver
74
52
throw Error ( "Invalid markup extension attribute. It should begin with '{{', but was {0}" , raw ) ;
75
53
}
76
54
77
- var ret = new ParsedMarkupExtensionInfo ( ) ;
78
- int idx = raw . LastIndexOf ( '}' ) ;
55
+ if ( raw . Length >= 2 && raw [ 1 ] == '}' )
56
+ {
57
+ throw Error ( "Markup extension can not begin with an '{}' escape: '{0}'" , raw ) ;
58
+ }
79
59
80
- if ( idx < 0 )
60
+ var ret = new ParsedMarkupExtensionInfo ( ) ;
61
+ if ( raw [ raw . Length - 1 ] != '}' )
81
62
{
63
+ // Any character after the final closing bracket is not accepted. Therefore, the last character should be '}'.
64
+ // Ideally, we should still ran the entire markup through the parser to get a more meaningful error.
82
65
throw Error ( "Expected '}}' in the markup extension attribute: '{0}'" , raw ) ;
83
66
}
84
-
85
- raw = raw . Substring ( 1 , idx - 1 ) ;
86
- idx = raw . IndexOf ( ' ' ) ;
87
- string name = idx < 0 ? raw : raw . Substring ( 0 , idx ) ;
88
67
89
- XamlTypeName xtn ;
90
- if ( ! XamlTypeName . TryParse ( name , nsResolver , out xtn ) )
68
+ var nameSeparatorIndex = raw . IndexOf ( ' ' ) ;
69
+ var name = nameSeparatorIndex != - 1 ? raw . Substring ( 1 , nameSeparatorIndex - 1 ) : raw . Substring ( 1 , raw . Length - 2 ) ;
70
+ if ( ! XamlTypeName . TryParse ( name , nsResolver , out var xtn ) )
91
71
{
92
72
throw Error ( "Failed to parse type name '{0}'" , name ) ;
93
73
}
94
74
95
75
var xt = sctx . GetXamlType ( xtn ) ?? new XamlType ( xtn . Namespace , xtn . Name , null , sctx ) ;
96
76
ret . Type = xt ;
97
77
98
- if ( idx < 0 )
78
+ if ( nameSeparatorIndex < 0 )
99
79
return ret ;
100
80
101
- var valueWithoutBinding = raw . Substring ( idx + 1 , raw . Length - idx - 1 ) ;
102
-
103
- //var vpairs = BindingMembersRegex.Matches(valueWithoutBinding)
104
- // .Cast<Match>()
105
- // .Select(m => m.Value.Trim())
106
- // .ToList();
107
- //if (vpairs.Count == 0)
108
- //{
109
- // vpairs.Add(valueWithoutBinding);
110
- //}
111
-
112
- var innerMarkups = BalancedMarkupBlockRegex . Matches ( valueWithoutBinding )
113
- . OfType < Match > ( ) ; // needed for net461, netstandard2.0
114
- var indexes = IndexOfAll ( valueWithoutBinding , ',' )
115
- // ignore those separators used within inner markups
116
- . Where ( x => ! innerMarkups . Any ( y => y . Index <= x && x <= y . Index + y . Length - 1 ) ) ;
117
- var vpairs = SplitByIndex ( valueWithoutBinding , indexes )
118
- . Select ( x => x . Trim ( ) )
119
- . ToList ( ) ;
81
+ var valueWithoutBinding = raw . Substring ( nameSeparatorIndex + 1 , raw . Length - 1 - ( nameSeparatorIndex + 1 ) ) ;
82
+ var vpairs = SliceParameters ( valueWithoutBinding , raw ) ;
120
83
121
84
List < string > posPrms = null ;
122
85
XamlMember lastMember = null ;
123
86
foreach ( var vpair in vpairs )
124
87
{
125
- idx = vpair . IndexOf ( '=' ) ;
88
+ var idx = vpair . IndexOf ( '=' ) ;
126
89
127
90
// FIXME: unescape string (e.g. comma)
128
91
if ( idx < 0 )
@@ -223,21 +186,51 @@ static Exception Error(string format, params object[] args)
223
186
return new XamlParseException ( String . Format ( format , args ) ) ;
224
187
}
225
188
226
- static IEnumerable < int > IndexOfAll ( string x , char value ) => x
227
- . Select ( Tuple . Create < char , int > )
228
- . Where ( x => x . Item1 == value )
229
- . Select ( x => x . Item2 ) ;
230
-
231
- static IEnumerable < string > SplitByIndex ( string x , IEnumerable < int > indexes )
189
+ internal static IEnumerable < string > SliceParameters ( string vargs , string raw )
232
190
{
233
- var previousIndex = 0 ;
234
- foreach ( var index in indexes . OrderBy ( i => i ) )
191
+ vargs = vargs . Trim ( ) ;
192
+
193
+ // We need to split the parameters by the commas, but with two catches:
194
+ // 1. Nested markup extension can also contains multiple parameters, but they are a single parameter to the current context
195
+ // 2. Comma can appear within a single-quoted string.
196
+ // 3. a little bit of #1 and a little bit #2...
197
+ // While we can use regex to match #1 and #2, #3 cannot be solved with regex.
198
+
199
+ // It seems that single-quot(`'`) can't be escaped when used in the parameters.
200
+ // So we don't have to worry about escaping it.
201
+
202
+ var isInQuot = false ;
203
+ var bracketDepth = 0 ;
204
+ var lastSliceIndex = - 1 ;
205
+
206
+ for ( int i = 0 ; i < vargs . Length ; i ++ )
207
+ {
208
+ var c = vargs [ i ] ;
209
+ if ( false ) { }
210
+ else if ( c == '\' ' ) isInQuot = ! isInQuot ;
211
+ else if ( isInQuot ) { }
212
+ else if ( c == '{' ) bracketDepth ++ ;
213
+ else if ( c == '}' )
214
+ {
215
+ bracketDepth -- ;
216
+ if ( bracketDepth > 0 )
217
+ {
218
+ throw Error ( "Unexpected '}}' in markup extension: '{0}'" , raw ) ;
219
+ }
220
+ }
221
+ else if ( c == ',' && bracketDepth == 0 )
222
+ {
223
+ yield return vargs . Substring ( lastSliceIndex + 1 , i - lastSliceIndex - 1 ) . Trim ( ) ;
224
+ lastSliceIndex = i ;
225
+ }
226
+ }
227
+
228
+ if ( bracketDepth > 0 )
235
229
{
236
- yield return x . Substring ( previousIndex , index - previousIndex ) ;
237
- previousIndex = index + 1 ;
230
+ throw Error ( "Expected '}}' in markup extension:" , raw ) ;
238
231
}
239
232
240
- yield return x . Substring ( previousIndex ) ;
233
+ yield return vargs . Substring ( lastSliceIndex + 1 ) . Trim ( ) ;
241
234
}
242
235
}
243
236
}
0 commit comments