@@ -27,22 +27,30 @@ public class ConformanceIdnaTestResult
2727 /// </summary>
2828 public string Value { get ; private set ; }
2929
30+ public string ? Source { get ; private set ; }
31+
3032 public IdnaTestResultType ResultType { get ; private set ; }
3133
3234 public string StatusValue { get ; private set ; }
3335
3436 public ConformanceIdnaTestResult ( string entry , string fallbackValue , IdnaTestResultType resultType = IdnaTestResultType . ToAscii )
35- : this ( entry , fallbackValue , null , null , useValueForStatus : true , resultType )
37+ : this ( entry , fallbackValue , null , null , useValueForStatus : true , resultType , null )
3638 {
3739 }
3840
3941 public ConformanceIdnaTestResult ( string entry , string fallbackValue , string statusValue , string statusFallbackValue , IdnaTestResultType resultType = IdnaTestResultType . ToAscii )
40- : this ( entry , fallbackValue , statusValue , statusFallbackValue , useValueForStatus : false , resultType )
42+ : this ( entry , fallbackValue , statusValue , statusFallbackValue , useValueForStatus : false , resultType , null )
43+ {
44+ }
45+
46+ public ConformanceIdnaTestResult ( string entry , string fallbackValue , string statusValue , string statusFallbackValue , string ? source , IdnaTestResultType resultType = IdnaTestResultType . ToAscii )
47+ : this ( entry , fallbackValue , statusValue , statusFallbackValue , useValueForStatus : false , resultType , source )
4148 {
4249 }
4350
44- private ConformanceIdnaTestResult ( string entry , string fallbackValue , string statusValue , string statusFallbackValue , bool useValueForStatus , IdnaTestResultType resultType )
51+ private ConformanceIdnaTestResult ( string entry , string fallbackValue , string statusValue , string statusFallbackValue , bool useValueForStatus , IdnaTestResultType resultType , string ? source )
4552 {
53+ Source = source ;
4654 ResultType = resultType ;
4755 SetValue ( string . IsNullOrEmpty ( entry . Trim ( ) ) ? fallbackValue : entry ) ;
4856 SetSuccess ( useValueForStatus ?
@@ -81,11 +89,120 @@ private void SetSuccess(string statusValue)
8189 }
8290 }
8391
92+ // Fullwidth Full Stop, Ideographic Full Stop, and Halfwidth Ideographic Full Stop
93+ private static char [ ] AllDots = [ '.' , '\uFF0E ' , '\u3002 ' , '\uFF61 ' ] ;
94+
95+ private const char SoftHyphen = '\u00AD ' ;
96+
97+ private bool IsIgnorableA4_2Rule ( )
98+ {
99+ if ( Source is null )
100+ {
101+ return false ;
102+ }
103+
104+ // Check the label lengths for the ASCII
105+ int lastIndex = 0 ;
106+ int index = Value . IndexOfAny ( AllDots ) ;
107+ while ( index >= 0 )
108+ {
109+ if ( index - lastIndex > 63 ) // 63 max label length
110+ {
111+ return false ;
112+ }
113+
114+ lastIndex = index + 1 ;
115+ index = Value . IndexOfAny ( AllDots , lastIndex ) ;
116+ }
117+
118+ if ( Value . Length - lastIndex > 63 )
119+ {
120+ return false ;
121+ }
122+
123+ // Remove Hyphen as it is ignored
124+ if ( Source . IndexOf ( SoftHyphen ) >= 0 )
125+ {
126+ Span < char > span = stackalloc char [ Source . Length ] ;
127+ int spanIndex = 0 ;
128+
129+ for ( int i = 0 ; i < Source . Length ; i ++ )
130+ {
131+ if ( Source [ i ] != SoftHyphen )
132+ {
133+ span [ spanIndex ++ ] = Source [ i ] ;
134+ }
135+ }
136+
137+ Source = span . Slice ( 0 , spanIndex ) . ToString ( ) ;
138+ }
139+
140+ // Check the label lengths for the Source
141+ lastIndex = 0 ;
142+ index = Source . IndexOfAny ( AllDots ) ;
143+ while ( index >= 0 )
144+ {
145+ if ( index - lastIndex > 63 ) // 63 max label length
146+ {
147+ return false ;
148+ }
149+
150+ lastIndex = index + 1 ;
151+ index = Source . IndexOfAny ( AllDots , lastIndex ) ;
152+ }
153+
154+ if ( Source . Length - lastIndex > 63 )
155+ {
156+ return false ;
157+ }
158+
159+ if ( Source [ 0 ] is '.' ) // Leading dot
160+ {
161+ return false ;
162+ }
163+
164+ for ( int i = 0 ; i < Source . Length - 1 ; i ++ )
165+ {
166+ // Consequence dots
167+ if ( ( Source [ i ] is '.' or '\uFF0E ' or '\u3002 ' or '\uFF61 ' ) && ( Source [ i + 1 ] is '.' or '\uFF0E ' or '\u3002 ' or '\uFF61 ' ) )
168+ {
169+ return false ;
170+ }
171+
172+ // Check Historical Ranges
173+ if ( Source [ i ] >= 0x2C00 && Source [ i ] <= 0x2C5F ) // Glagolitic (U+2C00–U+2C5F)
174+ return false ;
175+
176+ switch ( Source [ i ] )
177+ {
178+ case '\uD800 ' :
179+ if ( Source [ i + 1 ] >= 0xDFA0 && Source [ i + 1 ] <= 0xDFDF ) return false ; // Old Persian (U+103A0–U+103DF)
180+ if ( Source [ i + 1 ] >= 0xDF30 && Source [ i + 1 ] <= 0xDF4F ) return false ; // Gothic (U+10330–U+1034F)
181+ if ( Source [ i + 1 ] >= 0xDC00 && Source [ i + 1 ] <= 0xDC7F ) return false ; // Linear B (U+10000–U+1007F)
182+ break ;
183+ case '\uD802 ' :
184+ if ( Source [ i + 1 ] >= 0xDD00 && Source [ i + 1 ] <= 0xDD1F ) return false ; // Phoenician (U+10900–U+1091F)
185+ break ;
186+ case '\uD803 ' :
187+ if ( Source [ i + 1 ] >= 0xDEA0 && Source [ i + 1 ] <= 0xDEAF ) return false ; // Elymaic (U+10EA0–U+10EAF)
188+ break ;
189+ case '\uD808 ' :
190+ if ( Source [ i + 1 ] >= 0xDC00 && Source [ i + 1 ] <= 0xDFFF ) return false ; // Cuneiform (U+12000–U+123FF)
191+ break ;
192+ case '\uD838 ' :
193+ if ( Source [ i + 1 ] >= 0xDC00 && Source [ i + 1 ] <= 0xDCDF ) return false ; // Indic Siyaq Numbers (U+1E800–U+1E8DF)
194+ break ;
195+ }
196+ }
197+
198+ return true ;
199+ }
200+
84201 private bool IsIgnoredError ( string statusCode )
85202 {
86203 // We don't validate for BIDI rule so we can ignore BIDI codes
87204 // If we're validating ToAscii we ignore rule V2 (UIDNA_ERROR_HYPHEN_3_4) for compatibility with windows.
88- return statusCode . StartsWith ( 'B' ) || ( ResultType == IdnaTestResultType . ToAscii && statusCode == "V2" ) ;
205+ return statusCode . StartsWith ( 'B' ) || ( ResultType == IdnaTestResultType . ToAscii && statusCode == "V2" ) || ( statusCode . StartsWith ( "A4_2" ) && IsIgnorableA4_2Rule ( ) ) ;
89206 }
90207 }
91208}
0 commit comments