2014-10-27 21:12:57 +01:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
using System.Net.Http ;
using System.Text ;
using System.Threading.Tasks ;
2014-11-03 00:05:54 +01:00
namespace System.Unicode.Builder
2014-10-27 21:12:57 +01:00
{
2014-11-08 01:43:17 +01:00
internal class UnicodeDataProcessor
2014-10-27 21:12:57 +01:00
{
public const string UnicodeDataFileName = "UnicodeData.txt" ;
public const string PropListFileName = "PropList.txt" ;
2014-11-08 22:00:12 +01:00
public const string DerivedCorePropertiesFileName = "DerivedCoreProperties.txt" ;
2014-11-08 01:43:17 +01:00
public const string BlocksFileName = "Blocks.txt" ;
2014-11-15 11:50:43 +01:00
public const string UnihanReadingsFileName = "Unihan_Readings.txt" ;
public const string UnihanVariantsFileName = "Unihan_Variants.txt" ;
public const string UnihanNumericValuesFileName = "Unihan_NumericValues.txt" ;
2014-10-27 21:12:57 +01:00
2014-11-02 21:56:24 +01:00
private static string ParseSimpleCaseMapping ( string mapping )
{
if ( string . IsNullOrEmpty ( mapping )) return null ;
return char . ConvertFromUtf32 ( int . Parse ( mapping , NumberStyles . HexNumber ));
}
private static string NullIfEmpty ( string s )
{
return string . IsNullOrEmpty ( s ) ? null : s ;
2014-11-08 01:43:17 +01:00
}
2014-11-02 21:56:24 +01:00
2014-11-15 00:58:03 +01:00
public static async Task < UnicodeInfoBuilder > BuildDataAsync ( IDataSource ucdSource , IDataSource unihanSource )
2014-10-27 21:12:57 +01:00
{
2014-11-08 01:43:17 +01:00
var builder = new UnicodeInfoBuilder ( new Version ( 7 , 0 ));
2014-10-27 21:12:57 +01:00
2014-11-08 01:43:17 +01:00
await ProcessUnicodeDataFile ( ucdSource , builder ). ConfigureAwait ( false );
await ProcessPropListFile ( ucdSource , builder ). ConfigureAwait ( false );
2014-11-08 22:00:12 +01:00
await ProcessDerivedCorePropertiesFile ( ucdSource , builder ). ConfigureAwait ( false );
2014-11-08 01:43:17 +01:00
await ProcessBlocksFile ( ucdSource , builder ). ConfigureAwait ( false );
2014-11-15 11:50:43 +01:00
await ProcessUnihanReadings ( unihanSource , builder ). ConfigureAwait ( false );
await ProcessUnihanVariants ( unihanSource , builder ). ConfigureAwait ( false );
await ProcessUnihanNumericValues ( unihanSource , builder ). ConfigureAwait ( false );
2014-11-08 01:43:17 +01:00
return builder ;
}
2014-11-15 00:58:03 +01:00
private static async Task ProcessUnicodeDataFile ( IDataSource ucdSource , UnicodeInfoBuilder builder )
2014-11-08 01:43:17 +01:00
{
2014-11-15 11:50:43 +01:00
using ( var reader = new UnicodeDataFileReader ( await ucdSource . OpenDataFileAsync ( UnicodeDataFileName ). ConfigureAwait ( false ), ';' ))
2014-10-27 21:12:57 +01:00
{
2014-11-02 21:56:24 +01:00
int rangeStartCodePoint = - 1 ;
2014-11-01 14:43:50 +01:00
while ( reader . MoveToNextLine ())
2014-10-27 21:12:57 +01:00
{
2014-11-02 21:56:24 +01:00
var codePoint = new UnicodeCharacterRange ( int . Parse ( reader . ReadField (), NumberStyles . HexNumber ));
string name = reader . ReadField ();
if (! string . IsNullOrEmpty ( name ) && name [ 0 ] == '<' && name [ name . Length - 1 ] == '>' )
2014-10-27 21:12:57 +01:00
{
2014-11-02 21:56:24 +01:00
if ( name . EndsWith ( ", First>" , StringComparison . OrdinalIgnoreCase ))
{
if ( rangeStartCodePoint >= 0 ) throw new InvalidDataException ( "Invalid range data in UnicodeData.txt." );
rangeStartCodePoint = codePoint . FirstCodePoint ;
continue ;
}
else if ( name . EndsWith ( ", Last>" , StringComparison . OrdinalIgnoreCase ))
{
if ( rangeStartCodePoint < 0 ) throw new InvalidDataException ( "Invalid range data in UnicodeData.txt." );
codePoint = new UnicodeCharacterRange ( rangeStartCodePoint , codePoint . LastCodePoint );
2014-11-16 21:27:57 +01:00
name = name . Substring ( 1 , name . Length - 8 ). ToUpperInvariant (); // Upper-case the name in order to respect unicode naming scheme. (Spec says all names are uppercase ASCII)
2014-11-02 21:56:24 +01:00
rangeStartCodePoint = - 1 ;
2014-11-02 22:29:56 +01:00
}
2014-11-08 01:43:17 +01:00
else if ( name == "<control>" ) // Ignore the name of the property for these code points, as it should really be empty by the spec.
2014-11-08 00:15:49 +01:00
{
// For control characters, we can derive a character label in of the form <control-NNNN>, which is not the character name.
name = null ;
}
2014-11-02 22:29:56 +01:00
else
{
2014-11-08 00:15:49 +01:00
throw new InvalidDataException ( "Unexpected code point name tag: " + name + "." );
2014-11-02 22:29:56 +01:00
}
2014-11-02 21:56:24 +01:00
}
else if ( rangeStartCodePoint >= 0 )
2014-11-08 01:43:17 +01:00
{
2014-11-02 21:56:24 +01:00
throw new InvalidDataException ( "Invalid range data in UnicodeData.txt." );
}
// NB: Fields 10 and 11 are deemed obsolete. Field 11 should always be empty, and will be ignored here.
var characterData = new UnicodeCharacterDataBuilder ( codePoint )
{
Name = NullIfEmpty ( name ),
2014-11-01 14:43:50 +01:00
Category = UnicodeCategoryInfo . FromShortName ( reader . ReadField ()). Category ,
CanonicalCombiningClass = ( CanonicalCombiningClass ) byte . Parse ( reader . ReadField ()),
};
2014-11-02 21:56:24 +01:00
BidirectionalClass bidirectionalClass ;
if ( EnumHelper < BidirectionalClass >. TryGetNamedValue ( reader . ReadField (), out bidirectionalClass ))
{
characterData . BidirectionalClass = bidirectionalClass ;
}
else
{
throw new InvalidDataException ( string . Format ( "Missing Bidi_Class property for code point(s) {0}." , codePoint ));
}
2014-11-07 01:05:40 +01:00
characterData . CharacterDecompositionMapping = CharacterDecompositionMapping . Parse ( NullIfEmpty ( reader . ReadField ()));
2014-11-02 21:56:24 +01:00
string numericDecimalField = NullIfEmpty ( reader . ReadField ());
string numericDigitField = NullIfEmpty ( reader . ReadField ());
string numericNumericField = NullIfEmpty ( reader . ReadField ());
2014-11-01 14:43:50 +01:00
characterData . BidirectionalMirrored = reader . ReadField () == "Y" ;
2014-11-02 21:56:24 +01:00
characterData . OldName = NullIfEmpty ( reader . ReadField ());
2014-11-01 14:43:50 +01:00
reader . SkipField ();
2014-11-02 21:56:24 +01:00
characterData . SimpleUpperCaseMapping = ParseSimpleCaseMapping ( reader . ReadField ());
characterData . SimpleLowerCaseMapping = ParseSimpleCaseMapping ( reader . ReadField ());
characterData . SimpleTitleCaseMapping = ParseSimpleCaseMapping ( reader . ReadField ());
2014-11-01 14:43:50 +01:00
// Handle Numeric_Type & Numeric_Value:
// If field 6 is set, fields 7 and 8 should have the same value, and Numeric_Type is Decimal.
// If field 6 is not set but field 7 is set, field 8 should be set and have the same value. Then, the type is Digit.
// If field 6 and 7 are not set, but field 8 is set, then Numeric_Type is Numeric.
2014-11-02 21:56:24 +01:00
if ( numericNumericField != null )
2014-11-01 14:43:50 +01:00
{
characterData . NumericValue = UnicodeRationalNumber . Parse ( numericNumericField );
2014-11-02 21:56:24 +01:00
if ( numericDigitField != null )
2014-10-27 21:12:57 +01:00
{
2014-11-01 14:43:50 +01:00
if ( numericDigitField != numericNumericField )
2014-10-27 21:12:57 +01:00
{
2014-11-02 21:56:24 +01:00
throw new InvalidDataException ( "Invalid value for field 7 of code point " + characterData . CodePointRange . ToString () + "." );
2014-11-01 14:43:50 +01:00
}
2014-10-27 21:12:57 +01:00
2014-11-02 21:56:24 +01:00
if ( numericDecimalField != null )
2014-11-01 14:43:50 +01:00
{
if ( numericDecimalField != numericDigitField )
2014-10-27 21:12:57 +01:00
{
2014-11-02 21:56:24 +01:00
throw new InvalidDataException ( "Invalid value for field 6 of code point " + characterData . CodePointRange . ToString () + "." );
2014-10-27 21:12:57 +01:00
}
2014-11-05 23:10:22 +01:00
characterData . NumericType = UnicodeNumericType . Decimal ;
2014-10-27 21:12:57 +01:00
}
else
{
2014-11-01 14:43:50 +01:00
characterData . NumericType = UnicodeNumericType . Digit ;
2014-10-27 21:12:57 +01:00
}
}
2014-11-01 14:43:50 +01:00
else
2014-11-01 14:09:56 +01:00
{
2014-11-01 14:43:50 +01:00
characterData . NumericType = UnicodeNumericType . Numeric ;
2014-11-01 14:09:56 +01:00
}
}
2014-11-01 14:43:50 +01:00
2014-11-02 21:56:24 +01:00
builder . Insert ( characterData );
2014-11-01 14:43:50 +01:00
}
2014-10-27 21:12:57 +01:00
}
2014-11-08 01:43:17 +01:00
}
2014-11-15 00:58:03 +01:00
private static async Task ProcessPropListFile ( IDataSource ucdSource , UnicodeInfoBuilder builder )
2014-11-08 01:43:17 +01:00
{
2014-11-15 11:50:43 +01:00
using ( var reader = new UnicodeDataFileReader ( await ucdSource . OpenDataFileAsync ( PropListFileName ). ConfigureAwait ( false ), ';' ))
2014-11-01 14:43:50 +01:00
{
while ( reader . MoveToNextLine ())
{
ContributoryProperties property ;
2014-11-08 22:00:12 +01:00
var range = UnicodeCharacterRange . Parse ( reader . ReadTrimmedField ());
if ( EnumHelper < ContributoryProperties >. TryGetNamedValue ( reader . ReadTrimmedField (), out property ))
{
builder . SetProperties ( property , range );
}
}
}
}
2014-11-15 00:58:03 +01:00
private static async Task ProcessDerivedCorePropertiesFile ( IDataSource ucdSource , UnicodeInfoBuilder builder )
2014-11-08 22:00:12 +01:00
{
2014-11-15 11:50:43 +01:00
using ( var reader = new UnicodeDataFileReader ( await ucdSource . OpenDataFileAsync ( DerivedCorePropertiesFileName ). ConfigureAwait ( false ), ';' ))
2014-11-08 22:00:12 +01:00
{
while ( reader . MoveToNextLine ())
{
CoreProperties property ;
var range = UnicodeCharacterRange . Parse ( reader . ReadTrimmedField ());
if ( EnumHelper < CoreProperties >. TryGetNamedValue ( reader . ReadTrimmedField (), out property ))
2014-11-01 14:43:50 +01:00
{
2014-11-02 21:56:24 +01:00
builder . SetProperties ( property , range );
2014-11-01 14:43:50 +01:00
}
}
}
2014-11-08 01:43:17 +01:00
}
2014-10-27 21:12:57 +01:00
2014-11-15 00:58:03 +01:00
private static async Task ProcessBlocksFile ( IDataSource ucdSource , UnicodeInfoBuilder builder )
2014-11-08 01:43:17 +01:00
{
2014-11-15 11:50:43 +01:00
using ( var reader = new UnicodeDataFileReader ( await ucdSource . OpenDataFileAsync ( BlocksFileName ). ConfigureAwait ( false ), ';' ))
2014-11-08 01:43:17 +01:00
{
while ( reader . MoveToNextLine ())
{
2014-11-08 22:00:12 +01:00
builder . AddBlockEntry ( new UnicodeBlock ( UnicodeCharacterRange . Parse ( reader . ReadField ()), reader . ReadTrimmedField ()));
2014-11-08 01:43:17 +01:00
}
}
2014-10-27 21:12:57 +01:00
}
2014-11-15 11:50:43 +01:00
private static async Task ProcessUnihanReadings ( IDataSource unihanDataSource , UnicodeInfoBuilder builder )
{
using ( var reader = new UnihanDataFileReader ( await unihanDataSource . OpenDataFileAsync ( UnihanReadingsFileName ). ConfigureAwait ( false )))
{
while ( reader . Read ())
{
// This statement is used to skip unhandled properties entirely.
switch ( reader . PropertyName )
{
case UnihanProperty . kDefinition :
case UnihanProperty . kMandarin :
case UnihanProperty . kCantonese :
case UnihanProperty . kJapaneseKun :
case UnihanProperty . kJapaneseOn :
case UnihanProperty . kKorean :
case UnihanProperty . kHangul :
case UnihanProperty . kVietnamese :
break ;
default :
// Ignore unhandled properties for now.
continue ;
}
// This entry will only be created if there is meaningful data.
var entry = builder . GetUnihan ( reader . CodePoint );
switch ( reader . PropertyName )
{
case UnihanProperty . kDefinition :
entry . Definition = reader . PropertyValue ;
break ;
case UnihanProperty . kMandarin :
entry . MandarinReading = reader . PropertyValue ;
break ;
case UnihanProperty . kCantonese :
entry . CantoneseReading = reader . PropertyValue ;
break ;
case UnihanProperty . kJapaneseKun :
entry . JapaneseKunReading = reader . PropertyValue ;
break ;
case UnihanProperty . kJapaneseOn :
entry . JapaneseOnReading = reader . PropertyValue ;
break ;
case UnihanProperty . kKorean :
entry . KoreanReading = reader . PropertyValue ;
break ;
case UnihanProperty . kHangul :
entry . HangulReading = reader . PropertyValue ;
break ;
case UnihanProperty . kVietnamese :
entry . VietnameseReading = reader . PropertyValue ;
break ;
default :
throw new InvalidOperationException ();
}
}
}
}
private static async Task ProcessUnihanVariants ( IDataSource unihanDataSource , UnicodeInfoBuilder builder )
{
using ( var reader = new UnihanDataFileReader ( await unihanDataSource . OpenDataFileAsync ( UnihanVariantsFileName ). ConfigureAwait ( false )))
{
while ( reader . Read ())
{
// This statement is used to skip unhandled properties entirely.
switch ( reader . PropertyName )
{
case UnihanProperty . kSimplifiedVariant :
case UnihanProperty . kTraditionalVariant :
break ;
default :
// Ignore unhandled properties for now.
continue ;
}
var entry = builder . GetUnihan ( reader . CodePoint );
switch ( reader . PropertyName )
{
case UnihanProperty . kSimplifiedVariant :
entry . SimplifiedVariant = char . ConvertFromUtf32 ( HexCodePoint . ParsePrefixed ( reader . PropertyValue ));
break ;
case UnihanProperty . kTraditionalVariant :
entry . TraditionalVariant = char . ConvertFromUtf32 ( HexCodePoint . ParsePrefixed ( reader . PropertyValue ));
break ;
default :
throw new InvalidOperationException ();
}
}
}
}
private static async Task ProcessUnihanNumericValues ( IDataSource unihanDataSource , UnicodeInfoBuilder builder )
{
using ( var reader = new UnihanDataFileReader ( await unihanDataSource . OpenDataFileAsync ( UnihanNumericValuesFileName ). ConfigureAwait ( false )))
{
while ( reader . Read ())
{
var entry = builder . GetUnihan ( reader . CodePoint );
switch ( reader . PropertyName )
{
case UnihanProperty . kAccountingNumeric :
entry . NumericType = UnihanNumericType . Accounting ;
break ;
case UnihanProperty . kOtherNumeric :
entry . NumericType = UnihanNumericType . Other ;
break ;
case UnihanProperty . kPrimaryNumeric :
entry . NumericType = UnihanNumericType . Primary ;
break ;
default :
throw new InvalidDataException ( "Unrecognized property name: " + reader . PropertyName + "." );
}
entry . NumericValue = long . Parse ( reader . PropertyValue );
}
}
}
2014-10-27 21:12:57 +01:00
}
}