diff --git a/UnicodeInformation.Tests/FileUcdSource.cs b/UnicodeInformation.Tests/FileUcdSource.cs new file mode 100644 index 0000000..2fe34d0 --- /dev/null +++ b/UnicodeInformation.Tests/FileUcdSource.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnicodeInformation.Tests +{ + internal sealed class FileUcdSource : IUcdSource + { + private readonly string baseDirectory; + + public FileUcdSource(string baseDirectory) + { + this.baseDirectory = Path.GetFullPath(baseDirectory); + } + + public Task OpenDataFileAsync(string fileName) + { + return Task.FromResult(File.OpenRead(Path.Combine(baseDirectory, fileName))); + } + } +} diff --git a/UnicodeInformation.Tests/UnicodeDataManagerTests.cs b/UnicodeInformation.Tests/UnicodeDataManagerTests.cs index 4031145..f2a9654 100644 --- a/UnicodeInformation.Tests/UnicodeDataManagerTests.cs +++ b/UnicodeInformation.Tests/UnicodeDataManagerTests.cs @@ -1,16 +1,42 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; +using System.Net; +using System.IO; +using System.IO.Compression; namespace UnicodeInformation.Tests { [TestClass] public class UnicodeDataManagerTests { + private const string UcdDirectoryName = "UCD"; + + [TestInitialize] + public void Initialize() + { + var directoryName = Path.GetFullPath(UcdDirectoryName); + + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + + var fileName = Path.Combine(directoryName, "UCD.zip"); + + if (!File.Exists(fileName)) + { + new WebClient().DownloadFile("http://www.unicode.org/Public/UCD/latest/ucd/UCD.zip", fileName); + ZipFile.ExtractToDirectory(fileName, directoryName); + } + } + } + [TestMethod] public async Task DownloadAndBuildDataAsync() { - await UnicodeDataManager.DownloadAndBuildDataAsync(); + var source = new FileUcdSource(UcdDirectoryName); + + await UnicodeDataManager.DownloadAndBuildDataAsync(source); } } } diff --git a/UnicodeInformation.Tests/UnicodeInformation.Tests.csproj b/UnicodeInformation.Tests/UnicodeInformation.Tests.csproj index 99a8747..ce75d6d 100644 --- a/UnicodeInformation.Tests/UnicodeInformation.Tests.csproj +++ b/UnicodeInformation.Tests/UnicodeInformation.Tests.csproj @@ -36,6 +36,8 @@ + + ..\packages\Microsoft.Net.Http.2.2.28\lib\net45\System.Net.Http.Extensions.dll @@ -58,6 +60,7 @@ + diff --git a/UnicodeInformation/HttpUcdSource.cs b/UnicodeInformation/HttpUcdSource.cs new file mode 100644 index 0000000..1662675 --- /dev/null +++ b/UnicodeInformation/HttpUcdSource.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace UnicodeInformation +{ + public class HttpUcdSource : IUcdSource + { + public static readonly Uri UnicodeCharacterDataUri = new Uri("http://www.unicode.org/Public/UCD/latest/ucd/", UriKind.Absolute); + + private readonly HttpClient httpClient; + private readonly Uri baseUri; + + public HttpUcdSource() + : this(UnicodeCharacterDataUri) + { + } + + public HttpUcdSource(Uri baseUri) + { + this.httpClient = new HttpClient(); + this.baseUri = baseUri; + } + + public Task OpenDataFileAsync(string fileName) + { + return httpClient.GetStreamAsync(baseUri + fileName); + } + } +} diff --git a/UnicodeInformation/IUcdSource.cs b/UnicodeInformation/IUcdSource.cs new file mode 100644 index 0000000..197085e --- /dev/null +++ b/UnicodeInformation/IUcdSource.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnicodeInformation +{ + public interface IUcdSource + { + Task OpenDataFileAsync(string fileName); + } +} diff --git a/UnicodeInformation/UnicodeDataManager.cs b/UnicodeInformation/UnicodeDataManager.cs index a359f22..4a74db0 100644 --- a/UnicodeInformation/UnicodeDataManager.cs +++ b/UnicodeInformation/UnicodeDataManager.cs @@ -10,92 +10,88 @@ namespace UnicodeInformation { public class UnicodeDataManager { - public static readonly Uri UnicodeCharacterDataUri = new Uri("http://www.unicode.org/Public/UCD/latest/ucd/", UriKind.Absolute); public const string UnicodeDataFileName = "UnicodeData.txt"; public const string PropListFileName = "PropList.txt"; - public static async Task DownloadAndBuildDataAsync() + public static async Task DownloadAndBuildDataAsync(IUcdSource ucdSource) { var characterDataBuilders = new List(); - using (var httpClient = new HttpClient()) + using (var reader = new UnicodeDataFileReader(await ucdSource.OpenDataFileAsync(UnicodeDataFileName).ConfigureAwait(false))) { - using (var reader = new UnicodeDataFileReader(await httpClient.GetStreamAsync(UnicodeCharacterDataUri + UnicodeDataFileName).ConfigureAwait(false))) + while (reader.MoveToNextLine()) { - while (reader.MoveToNextLine()) + // NB: Fields 10 and 11 are deemed obsolete. Field 11 should always be empty, and will be ignored here. + var characterData = new UnicodeCharacterDataBuilder(int.Parse(reader.ReadField(), NumberStyles.HexNumber)) { - // NB: Fields 10 and 11 are deemed obsolete. Field 11 should always be empty, and will be ignored here. - var characterData = new UnicodeCharacterDataBuilder(int.Parse(reader.ReadField(), NumberStyles.HexNumber)) + Name = reader.ReadField(), + Category = UnicodeCategoryInfo.FromShortName(reader.ReadField()).Category, + CanonicalCombiningClass = (CanonicalCombiningClass)byte.Parse(reader.ReadField()), + BidirectionalClass = reader.ReadField(), + DecompositionType = reader.ReadField() + }; + + string numericDecimalField = reader.ReadField(); + string numericDigitField = reader.ReadField(); + string numericNumericField = reader.ReadField(); + + characterData.BidirectionalMirrored = reader.ReadField() == "Y"; + characterData.OldName = reader.ReadField(); + reader.SkipField(); + characterData.SimpleUpperCaseMapping = reader.ReadField(); + characterData.SimpleLowerCaseMapping = reader.ReadField(); + characterData.SimpleTitleCaseMapping = reader.ReadField(); + + // 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. + if (!string.IsNullOrEmpty(numericNumericField)) + { + characterData.NumericValue = UnicodeRationalNumber.Parse(numericNumericField); + + if (!string.IsNullOrEmpty(numericDigitField)) { - Name = reader.ReadField(), - Category = UnicodeCategoryInfo.FromShortName(reader.ReadField()).Category, - CanonicalCombiningClass = (CanonicalCombiningClass)byte.Parse(reader.ReadField()), - BidirectionalClass = reader.ReadField(), - DecompositionType = reader.ReadField() - }; - - string numericDecimalField = reader.ReadField(); - string numericDigitField = reader.ReadField(); - string numericNumericField = reader.ReadField(); - - characterData.BidirectionalMirrored = reader.ReadField() == "Y"; - characterData.OldName = reader.ReadField(); - reader.SkipField(); - characterData.SimpleUpperCaseMapping = reader.ReadField(); - characterData.SimpleLowerCaseMapping = reader.ReadField(); - characterData.SimpleTitleCaseMapping = reader.ReadField(); - - // 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. - if (!string.IsNullOrEmpty(numericNumericField)) - { - characterData.NumericValue = UnicodeRationalNumber.Parse(numericNumericField); - - if (!string.IsNullOrEmpty(numericDigitField)) + if (numericDigitField != numericNumericField) { - if (numericDigitField != numericNumericField) - { - throw new InvalidDataException("Invalid value for field 7 of character U+" + characterData.CodePoint.ToString("X4") + "."); - } + throw new InvalidDataException("Invalid value for field 7 of character U+" + characterData.CodePoint.ToString("X4") + "."); + } - if (!string.IsNullOrEmpty(numericDecimalField)) + if (!string.IsNullOrEmpty(numericDecimalField)) + { + if (numericDecimalField != numericDigitField) { - if (numericDecimalField != numericDigitField) - { - throw new InvalidDataException("Invalid value for field 6 of character U+" + characterData.CodePoint.ToString("X4") + "."); - } - } - else - { - characterData.NumericType = UnicodeNumericType.Digit; + throw new InvalidDataException("Invalid value for field 6 of character U+" + characterData.CodePoint.ToString("X4") + "."); } } else { - characterData.NumericType = UnicodeNumericType.Numeric; + characterData.NumericType = UnicodeNumericType.Digit; } } - - characterDataBuilders.Add(characterData); - } - } - /* - using (var reader = new UnicodeDataFileReader(await httpClient.GetStreamAsync(UnicodeCharacterDataUri + PropListFileName).ConfigureAwait(false))) - { - while (reader.MoveToNextLine()) - { - ContributoryProperties property; - - var range = UnicodeCharacterRange.Parse(reader.ReadField().TrimEnd()); - if (EnumHelper.TryGetNamedValue(reader.ReadField().Trim(), out property)) + else { + characterData.NumericType = UnicodeNumericType.Numeric; } } - } - */ + + characterDataBuilders.Add(characterData); + } } + /* + using (var reader = new UnicodeDataFileReader(await ucdSource.OpenDataFileAsync(PropListFileName).ConfigureAwait(false))) + { + while (reader.MoveToNextLine()) + { + ContributoryProperties property; + + var range = UnicodeCharacterRange.Parse(reader.ReadField().TrimEnd()); + if (EnumHelper.TryGetNamedValue(reader.ReadField().Trim(), out property)) + { + } + } + } + */ var finalData = new UnicodeCharacterData[characterDataBuilders.Count]; diff --git a/UnicodeInformation/UnicodeInformation.csproj b/UnicodeInformation/UnicodeInformation.csproj index 7497c63..30fbe91 100644 --- a/UnicodeInformation/UnicodeInformation.csproj +++ b/UnicodeInformation/UnicodeInformation.csproj @@ -52,6 +52,8 @@ + +