1+ using System . Buffers ;
2+ using System . Diagnostics ;
3+ using System . Text . Encodings . Web ;
4+ using DotNext . Buffers ;
5+
6+ namespace DotNext . IO ;
7+
8+ /// <summary>
9+ /// Represents operations to work with <c>file://</c> scheme.
10+ /// </summary>
11+ public static class FileUri
12+ {
13+ private const string FileScheme = "file://" ;
14+
15+ /// <summary>
16+ /// Encodes file name as URI.
17+ /// </summary>
18+ /// <param name="fileName">The fully-qualified file name.</param>
19+ /// <param name="settings">The encoding settings.</param>
20+ /// <returns><paramref name="fileName"/> as <see cref="Uri"/>.</returns>
21+ /// <exception cref="ArgumentException"><paramref name="fileName"/> is not fully-qualified.</exception>
22+ public static Uri Encode ( ReadOnlySpan < char > fileName , TextEncoderSettings ? settings = null )
23+ {
24+ ThrowIfNotFullyQualified ( fileName ) ;
25+ var encoder = settings is null ? UrlEncoder . Default : UrlEncoder . Create ( settings ) ;
26+ var maxLength = FileScheme . Length + encoder . MaxOutputCharactersPerInputCharacter * fileName . Length ;
27+ using var buffer = ( uint ) maxLength <= ( uint ) SpanOwner < char > . StackallocThreshold
28+ ? stackalloc char [ maxLength ]
29+ : new SpanOwner < char > ( maxLength ) ;
30+
31+ TryEncodeCore ( fileName , encoder , buffer . Span , out var writtenCount ) ;
32+ return new ( buffer . Span . Slice ( 0 , writtenCount ) . ToString ( ) , UriKind . Absolute ) ;
33+ }
34+
35+ /// <summary>
36+ /// Tries to encode file name as URI.
37+ /// </summary>
38+ /// <param name="fileName">The fully-qualified file name.</param>
39+ /// <param name="encoder">The encoder that is used to encode the file name.</param>
40+ /// <param name="output">The output buffer.</param>
41+ /// <param name="charsWritten">The number of characters written to <paramref name="output"/>.</param>
42+ /// <returns><see langword="true"/> if <paramref name="fileName"/> is encoded successfully; otherwise, <see langword="false"/>.</returns>
43+ /// <exception cref="ArgumentException"><paramref name="fileName"/> is not fully-qualified.</exception>
44+ public static bool TryEncode ( ReadOnlySpan < char > fileName , UrlEncoder ? encoder , Span < char > output , out int charsWritten )
45+ {
46+ ThrowIfNotFullyQualified ( fileName ) ;
47+
48+ return TryEncodeCore ( fileName , encoder ?? UrlEncoder . Default , output , out charsWritten ) ;
49+ }
50+
51+ [ StackTraceHidden ]
52+ private static void ThrowIfNotFullyQualified ( ReadOnlySpan < char > fileName )
53+ {
54+ if ( ! Path . IsPathFullyQualified ( fileName ) )
55+ throw new ArgumentException ( ExceptionMessages . FullyQualifiedPathExpected , nameof ( fileName ) ) ;
56+ }
57+
58+ private static bool TryEncodeCore ( ReadOnlySpan < char > fileName , UrlEncoder encoder , Span < char > output , out int charsWritten )
59+ {
60+ var result = false ;
61+ var writer = new SpanWriter < char > ( output ) ;
62+ writer . Write ( FileScheme ) ;
63+ while ( ! fileName . IsEmpty )
64+ {
65+ var index = fileName . IndexOf ( Path . DirectorySeparatorChar ) ;
66+ ReadOnlySpan < char > component ;
67+ if ( index >= 0 )
68+ {
69+ component = fileName . Slice ( 0 , index ) ;
70+ fileName = fileName . Slice ( index + 1 ) ;
71+ }
72+ else
73+ {
74+ component = fileName ;
75+ fileName = default ;
76+ }
77+
78+ result = encoder . Encode ( component , writer . RemainingSpan , out _ , out charsWritten ) is OperationStatus . Done ;
79+ if ( ! result )
80+ break ;
81+
82+ writer . Advance ( charsWritten ) ;
83+ if ( index >= 0 )
84+ writer . Add ( Path . DirectorySeparatorChar ) ;
85+ }
86+
87+ charsWritten = writer . WrittenCount ;
88+ return result ;
89+ }
90+ }
0 commit comments