1717// under the License.
1818// </copyright>
1919
20+ using System ;
2021using System . Collections . Generic ;
22+ using System . Diagnostics ;
23+ using System . Linq ;
24+ using System . Numerics ;
25+ using System . Text . Json . Nodes ;
2126using System . Text . Json . Serialization ;
27+ using System . Text . RegularExpressions ;
2228
2329namespace OpenQA . Selenium . BiDi . Modules . Script ;
2430
@@ -38,22 +44,37 @@ namespace OpenQA.Selenium.BiDi.Modules.Script;
3844[ JsonDerivedType ( typeof ( SetLocalValue ) , "set" ) ]
3945public abstract record LocalValue
4046{
41- public static implicit operator LocalValue ( int value ) { return new NumberLocalValue ( value ) ; }
42- public static implicit operator LocalValue ( string ? value ) { return value is null ? new NullLocalValue ( ) : new StringLocalValue ( value ) ; }
47+ public static implicit operator LocalValue ( bool ? value ) { return value is bool b ? ( b ? True : False ) : Null ; }
48+ public static implicit operator LocalValue ( int ? value ) { return value is int i ? Number ( i ) : Null ; }
49+ public static implicit operator LocalValue ( double ? value ) { return value is double d ? Number ( d ) : Null ; }
50+ public static implicit operator LocalValue ( string ? value ) { return value is null ? Null : String ( value ) ; }
4351
4452 // TODO: Extend converting from types
4553 public static LocalValue ConvertFrom ( object ? value )
4654 {
4755 switch ( value )
4856 {
49- case LocalValue :
50- return ( LocalValue ) value ;
57+ case LocalValue localValue :
58+ return localValue ;
59+
5160 case null :
52- return new NullLocalValue ( ) ;
53- case int :
54- return ( int ) value ;
55- case string :
56- return ( string ) value ;
61+ return Null ;
62+
63+ case bool b :
64+ return b ? True : False ;
65+
66+ case int i :
67+ return Number ( i ) ;
68+
69+ case double d :
70+ return Number ( d ) ;
71+
72+ case string str :
73+ return String ( str ) ;
74+
75+ case IEnumerable < object ? > list :
76+ return Array ( list . Select ( ConvertFrom ) . ToList ( ) ) ;
77+
5778 case object :
5879 {
5980 var type = value . GetType ( ) ;
@@ -67,10 +88,198 @@ public static LocalValue ConvertFrom(object? value)
6788 values . Add ( [ property. Name , ConvertFrom ( property . GetValue ( value ) ) ] ) ;
6889 }
6990
70- return new ObjectLocalValue ( values ) ;
91+ return Object ( values ) ;
7192 }
7293 }
7394 }
95+
96+ private static readonly BigInteger MaxDouble = new BigInteger ( double . MaxValue ) ;
97+ private static readonly BigInteger MinDouble = new BigInteger ( double . MinValue ) ;
98+
99+ public static LocalValue ConvertFrom ( JsonNode ? node )
100+ {
101+ if ( node is null )
102+ {
103+ return Null ;
104+ }
105+
106+ switch ( node . GetValueKind ( ) )
107+ {
108+ case System . Text . Json . JsonValueKind . Null :
109+ return Null ;
110+
111+ case System . Text . Json . JsonValueKind . True :
112+ return True ;
113+
114+ case System . Text . Json . JsonValueKind . False :
115+ return False ;
116+
117+ case System . Text . Json . JsonValueKind . String :
118+ return String ( node . ToString ( ) ) ;
119+
120+ case System . Text . Json . JsonValueKind . Number :
121+ {
122+ var numberString = node . ToString ( ) ;
123+
124+ var bigNumber = BigInteger . Parse ( numberString ) ;
125+
126+ if ( bigNumber > MaxDouble || bigNumber < MinDouble )
127+ {
128+ return BigInt ( bigNumber ) ;
129+ }
130+
131+ return Number ( double . Parse ( numberString ) ) ;
132+ }
133+
134+ case System . Text . Json . JsonValueKind . Array :
135+ return Array ( node . AsArray ( ) . Select ( ConvertFrom ) ) ;
136+
137+ case System . Text . Json . JsonValueKind . Object :
138+ var convertedToListForm = node . AsObject ( ) . Select ( property => new LocalValue [ ] { String ( property . Key ) , ConvertFrom ( property . Value ) } ) . ToList ( ) ;
139+ return Object ( convertedToListForm ) ;
140+
141+ default :
142+ throw new InvalidOperationException ( "Invalid JSON node" ) ;
143+ }
144+ }
145+
146+ public static ChannelLocalValue Channel ( ChannelLocalValue . ChannelProperties options )
147+ {
148+ return new ChannelLocalValue ( options ) ;
149+ }
150+
151+ public static ArrayLocalValue Array ( IEnumerable < LocalValue > values )
152+ {
153+ return new ArrayLocalValue ( values ) ;
154+ }
155+
156+ public static SetLocalValue Set ( HashSet < LocalValue > values )
157+ {
158+ return new SetLocalValue ( values ) ;
159+ }
160+
161+ public static ObjectLocalValue Object ( IEnumerable < IEnumerable < LocalValue > > values )
162+ {
163+ return new ObjectLocalValue ( values ) ;
164+ }
165+
166+ public static ObjectLocalValue Object ( IDictionary < string , LocalValue > values )
167+ {
168+ var convertedValues = values . Select ( pair => new LocalValue [ ] { new StringLocalValue ( pair . Key ) , pair . Value } ) . ToList ( ) ;
169+ return new ObjectLocalValue ( convertedValues ) ;
170+ }
171+
172+ public static MapLocalValue Map ( IEnumerable < IEnumerable < LocalValue > > values )
173+ {
174+ return new MapLocalValue ( values ) ;
175+ }
176+
177+ public static MapLocalValue Map ( IDictionary < LocalValue , LocalValue > values )
178+ {
179+ var convertedValues = values . Select ( PairToList ) . ToList ( ) ;
180+ return new MapLocalValue ( convertedValues ) ;
181+ }
182+
183+ private static LocalValue [ ] PairToList ( KeyValuePair < LocalValue , LocalValue > pair )
184+ {
185+ return [ pair . Key , pair . Value ] ;
186+ }
187+
188+ public static BigIntLocalValue BigInt ( BigInteger value )
189+ {
190+ return new BigIntLocalValue ( value . ToString ( ) ) ;
191+ }
192+
193+ public static DateLocalValue Date ( DateTime value )
194+ {
195+ return new DateLocalValue ( value . ToString ( "o" ) ) ;
196+ }
197+
198+ public static StringLocalValue String ( string value )
199+ {
200+ if ( value is null )
201+ {
202+ throw new ArgumentNullException ( nameof ( value ) , $ "string value cannot be null, use a { nameof ( NullLocalValue ) } value instead") ;
203+ }
204+
205+ return new StringLocalValue ( value ) ;
206+ }
207+
208+ public static NumberLocalValue Number ( double value )
209+ {
210+ return new NumberLocalValue ( value ) ;
211+ }
212+
213+ public static BooleanLocalValue True { get ; } = new BooleanLocalValue ( true ) ;
214+
215+ public static BooleanLocalValue False { get ; } = new BooleanLocalValue ( false ) ;
216+
217+ public static NullLocalValue Null { get ; } = new NullLocalValue ( ) ;
218+
219+ public static UndefinedLocalValue Undefined { get ; } = new UndefinedLocalValue ( ) ;
220+
221+ /// <summary>
222+ /// Converts a .NET Regex into a BiDi Regex
223+ /// </summary>
224+ /// <param name="regex">A .NET Regex.</param>
225+ /// <returns>A BiDi Regex.</returns>
226+ /// <remarks>
227+ /// Note that the .NET regular expression engine does not work the same as the JavaScript engine.
228+ /// To minimize the differences between the two engines, it is recommended to enabled the <see cref="RegexOptions.ECMAScript"/> option.
229+ /// </remarks>
230+ public static RegExpLocalValue Regex ( Regex regex )
231+ {
232+ RegexOptions options = regex . Options ;
233+
234+ if ( options == RegexOptions . None )
235+ {
236+ return new RegExpLocalValue ( new RegExpValue ( regex . ToString ( ) ) ) ;
237+ }
238+
239+ string flags = string . Empty ;
240+
241+ const RegexOptions NonBacktracking = ( RegexOptions ) 1024 ;
242+ #if NET8_0_OR_GREATER
243+ Debug . Assert ( NonBacktracking == RegexOptions . NonBacktracking ) ;
244+ #endif
245+
246+ const RegexOptions NonApplicableOptions = RegexOptions . Compiled | NonBacktracking ;
247+
248+ const RegexOptions UnsupportedOptions =
249+ RegexOptions . ExplicitCapture |
250+ RegexOptions . IgnorePatternWhitespace |
251+ RegexOptions . RightToLeft |
252+ RegexOptions . CultureInvariant ;
253+
254+ options &= ~ NonApplicableOptions ;
255+
256+ if ( ( options & UnsupportedOptions ) != 0 )
257+ {
258+ throw new NotSupportedException ( $ "The selected RegEx options are not supported in BiDi: { options & UnsupportedOptions } ") ;
259+ }
260+
261+ if ( ( options & RegexOptions . IgnoreCase ) != 0 )
262+ {
263+ flags += "i" ;
264+ options = options & ~ RegexOptions . IgnoreCase ;
265+ }
266+
267+ if ( ( options & RegexOptions . Multiline ) != 0 )
268+ {
269+ options = options & ~ RegexOptions . Multiline ;
270+ flags += "m" ;
271+ }
272+
273+ if ( ( options & RegexOptions . Singleline ) != 0 )
274+ {
275+ options = options & ~ RegexOptions . Singleline ;
276+ flags += "s" ;
277+ }
278+
279+ Debug . Assert ( options == RegexOptions . None ) ;
280+
281+ return new RegExpLocalValue ( new RegExpValue ( regex . ToString ( ) ) { Flags = flags } ) ;
282+ }
74283}
75284
76285public abstract record PrimitiveProtocolLocalValue : LocalValue ;
0 commit comments