Restructured the projects, and put all the data construction logic in a separate assembly.

This commit is contained in:
GoldenCrystal
2014-11-03 00:05:54 +01:00
parent 5ac0889b40
commit de36586c64
35 changed files with 217 additions and 79 deletions
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Unicode;
namespace System.Unicode.Builder
{
public sealed class FileUcdSource : IUcdSource
{
private readonly string baseDirectory;
public FileUcdSource(string baseDirectory)
{
this.baseDirectory = Path.GetFullPath(baseDirectory);
}
public Task<Stream> OpenDataFileAsync(string fileName)
{
return Task.FromResult<Stream>(File.OpenRead(Path.Combine(baseDirectory, fileName)));
}
}
}
@@ -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 System.Unicode.Builder
{
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<Stream> OpenDataFileAsync(string fileName)
{
return httpClient.GetStreamAsync(baseUri + fileName);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
public interface IUcdSource
{
Task<Stream> OpenDataFileAsync(string fileName);
}
}
+15
View File
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
class Program
{
static void Main(string[] args)
{
}
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("UnicodeInformation.Builder")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("UnicodeInformation.Builder")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8dfdee6c-4f0d-4de1-b346-574cb56d2b8b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
public sealed class UnicodeCharacterDataBuilder
{
private readonly UnicodeCharacterRange codePointRange;
private string name;
private UnicodeCategory category = UnicodeCategory.OtherNotAssigned;
private CanonicalCombiningClass canonicalCombiningClass;
private BidirectionalClass bidirectionalClass;
private string decompositionType;
private UnicodeNumericType numericType;
private UnicodeRationalNumber numericValue;
private bool bidirectionalMirrored;
private string oldName;
private string simpleUpperCaseMapping;
private string simpleLowerCaseMapping;
private string simpleTitleCaseMapping;
private ContributoryProperties contributoryProperties;
private List<int> relatedCodePoints = new List<int>();
public UnicodeCharacterRange CodePointRange { get { return codePointRange; } }
public string Name
{
get { return name; }
set { name = value; }
}
public UnicodeCategory Category
{
get { return category; }
set
{
if (!Enum.IsDefined(typeof(UnicodeCategory), value))
{
throw new ArgumentOutOfRangeException("value");
}
category = value;
}
}
public CanonicalCombiningClass CanonicalCombiningClass
{
get { return canonicalCombiningClass; }
set { canonicalCombiningClass = value; } // Even values not defined in the enum are allowed here.
}
public BidirectionalClass BidirectionalClass
{
get { return bidirectionalClass; }
set { bidirectionalClass = value; }
}
public string DecompositionType
{
get { return decompositionType; }
set { decompositionType = value; }
}
public UnicodeNumericType NumericType
{
get { return numericType; }
set { numericType = value; }
}
public UnicodeRationalNumber NumericValue
{
get { return numericValue; }
set { numericValue = value; }
}
public string OldName
{
get { return oldName; }
set { oldName = value; }
}
public bool BidirectionalMirrored
{
get { return bidirectionalMirrored; }
set { bidirectionalMirrored = value; }
}
public string SimpleUpperCaseMapping
{
get { return simpleUpperCaseMapping; }
set { simpleUpperCaseMapping = value; }
}
public string SimpleLowerCaseMapping
{
get { return simpleLowerCaseMapping; }
set { simpleLowerCaseMapping = value; }
}
public string SimpleTitleCaseMapping
{
get { return simpleTitleCaseMapping; }
set { simpleTitleCaseMapping = value; }
}
public ContributoryProperties ContributoryProperties
{
get { return contributoryProperties; }
set { contributoryProperties = value; }
}
public ICollection<int> RelatedCodePoints { get { return relatedCodePoints; } }
public UnicodeCharacterDataBuilder(int codePoint)
: this(new UnicodeCharacterRange(codePoint))
{
}
public UnicodeCharacterDataBuilder(UnicodeCharacterRange codePointRange)
{
this.codePointRange = codePointRange;
this.category = UnicodeCategory.OtherNotAssigned;
}
public UnicodeCharacterData ToCharacterData()
{
return new UnicodeCharacterData
(
codePointRange,
Name,
category,
canonicalCombiningClass,
bidirectionalClass,
decompositionType,
numericType,
numericValue,
bidirectionalMirrored,
oldName,
simpleUpperCaseMapping,
simpleLowerCaseMapping,
simpleTitleCaseMapping,
contributoryProperties,
relatedCodePoints.Count > 0 ? relatedCodePoints.ToArray() : null
);
}
}
}
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
public class UnicodeDataBuilder
{
private readonly Version unicodeVersion;
private UnicodeCharacterDataBuilder[] characterDataBuilders = new UnicodeCharacterDataBuilder[10000];
private int characterCount;
public UnicodeDataBuilder(Version unicodeVersion)
{
this.unicodeVersion = unicodeVersion;
}
private int FindCodePoint(int codePoint)
{
if (characterCount == 0) return -1;
int minIndex = 0;
int maxIndex = characterCount - 1;
do
{
int index = (minIndex + maxIndex) >> 1;
int Δ = characterDataBuilders[index].CodePointRange.CompareCodePoint(codePoint);
if (Δ == 0) return index;
else if (Δ < 0) maxIndex = index - 1;
else minIndex = index + 1;
} while (minIndex <= maxIndex);
return -1;
}
private int FindInsertionPoint(int startCodePoint, int endCodePoint)
{
int minIndex;
int maxIndex;
if (characterCount == 0 || characterDataBuilders[maxIndex = characterCount - 1].CodePointRange.LastCodePoint < startCodePoint) return characterCount;
else if (endCodePoint < characterDataBuilders[minIndex = 0].CodePointRange.FirstCodePoint) return 0;
else if (characterCount == 1) return -1;
do
{
int index = (minIndex + maxIndex) >> 1;
int Δ = characterDataBuilders[index].CodePointRange.CompareCodePoint(startCodePoint);
if (Δ == 0) return -1;
else if (Δ < 0) maxIndex = index;
else minIndex = index;
} while (maxIndex - minIndex > 1);
if (characterDataBuilders[maxIndex].CodePointRange.FirstCodePoint < endCodePoint) return -1;
else return maxIndex;
}
public void Insert(UnicodeCharacterDataBuilder data)
{
int insertionPoint = FindInsertionPoint(data.CodePointRange.FirstCodePoint, data.CodePointRange.LastCodePoint);
if (insertionPoint < 0) throw new InvalidOperationException("The specified range overlaps with pre-existing ranges.");
if (insertionPoint >= characterDataBuilders.Length)
{
Array.Resize(ref characterDataBuilders, characterDataBuilders.Length << 1);
}
if (insertionPoint < characterCount)
{
Array.Copy(characterDataBuilders, insertionPoint, characterDataBuilders, insertionPoint + 1, characterCount - insertionPoint);
}
characterDataBuilders[insertionPoint] = data;
++characterCount;
}
public UnicodeCharacterDataBuilder Get(int codePoint)
{
int index = FindCodePoint(codePoint);
return index >= 0 ? characterDataBuilders[index] : null;
}
public void SetProperties(ContributoryProperties property, UnicodeCharacterRange codePointRange)
{
int firstIndex = FindCodePoint(codePointRange.FirstCodePoint);
int lastIndex = FindCodePoint(codePointRange.LastCodePoint);
if (firstIndex < 0 && lastIndex < 0)
{
Insert(new UnicodeCharacterDataBuilder(codePointRange) { ContributoryProperties = property });
return;
}
if (firstIndex < 0
|| lastIndex < 0
|| characterDataBuilders[firstIndex].CodePointRange.FirstCodePoint < codePointRange.FirstCodePoint
|| characterDataBuilders[lastIndex].CodePointRange.LastCodePoint > codePointRange.LastCodePoint)
{
throw new InvalidOperationException();
}
int i = firstIndex;
while (true)
{
characterDataBuilders[i].ContributoryProperties |= property;
if (i == lastIndex) break;
++i;
}
}
public UnicodeData ToUnicodeData()
{
var finalData = new UnicodeCharacterData[characterCount];
for (int i = 0; i < finalData.Length; ++i)
finalData[i] = characterDataBuilders[i].ToCharacterData();
return new UnicodeData(unicodeVersion, finalData);
}
}
}
@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
public class UnicodeDataFileReader : IDisposable
{
private struct AsciiCharBuffer
{
private readonly UnicodeDataFileReader reader;
private int length;
public AsciiCharBuffer(UnicodeDataFileReader reader)
{
this.reader = reader;
this.length = 0;
}
public int Length { get { return length; } }
private void EnsureExtraCapacity(int count)
{
if (count < 0) throw new ArgumentOutOfRangeException("requiredExtraCapacity");
if (reader.charBuffer.Length < checked(length + count))
Array.Resize(ref reader.charBuffer, Math.Max(length + count, reader.charBuffer.Length << 1));
}
public void Append(byte[] value, int startIndex, int count)
{
if (value == null) throw new ArgumentNullException("value");
if (startIndex >= value.Length) throw new ArgumentOutOfRangeException("startIndex");
if (checked(count += startIndex) > value.Length) throw new ArgumentOutOfRangeException("count");
EnsureExtraCapacity(value.Length);
var buffer = reader.charBuffer;
for (int i = startIndex; i < count; ++i)
{
byte b = value[i];
if (b > 127) throw new InvalidDataException(string.Format("Unexpected character value: {0}.", b));
buffer[length++] = (char)b;
}
}
public override string ToString()
{
return length > 0 ? new string(reader.charBuffer, 0, length) : string.Empty;
}
}
private readonly Stream stream;
private readonly byte[] byteBuffer;
private char[] charBuffer;
private int index;
private int length;
private bool hasField = false;
private readonly bool leaveOpen;
public UnicodeDataFileReader(Stream stream)
: this(stream, false)
{
}
public UnicodeDataFileReader(Stream stream, bool leaveOpen)
{
this.stream = stream;
this.byteBuffer = new byte[8192];
this.charBuffer = new char[100];
this.leaveOpen = leaveOpen;
}
public void Dispose()
{
if (!leaveOpen) stream.Dispose();
}
private bool RefillBuffer()
{
// Evilish line of code. 😈
return (length = stream.Read(byteBuffer, 0, byteBuffer.Length)) != (index = 0);
}
private static bool IsNewLineOrComment(byte b)
{
return b == '\n' || b == '#';
}
/// <summary>Moves the stream to the next valid data row.</summary>
/// <returns><see langword="true"/> if data is available; <see langword="false"/> otherwise.</returns>
public bool MoveToNextLine()
{
if (length == 0)
{
if (RefillBuffer())
{
if (!IsNewLineOrComment(byteBuffer[index]))
{
hasField = true;
goto Completed;
}
}
else
{
return false;
}
}
do
{
while (index < length)
{
if (byteBuffer[index++] == '\n')
{
if (index < length && !IsNewLineOrComment(byteBuffer[index]))
{
hasField = true;
goto Completed;
}
}
}
} while (RefillBuffer());
hasField = false;
Completed: ;
return hasField;
}
/// <summary>Reads the next data field.</summary>
/// <remarks>This method will return <see langword="null"/> on end of line.</remarks>
/// <returns>The text value of the read field, if available; <see langword="null"/> otherwise.</returns>
public string ReadField()
{
if (length == 0) throw new InvalidOperationException();
if (!hasField) return null;
else if (index >= length) RefillBuffer();
// If the current character is a new line or a comment, we are at the end of a line.
if (IsNewLineOrComment(byteBuffer[index]))
{
if (hasField)
{
hasField = false;
return string.Empty;
}
else
{
return null;
}
}
var buffer = new AsciiCharBuffer(this);
int startOffset;
int endOffset;
do
{
startOffset = index;
endOffset = -1;
while (index < length)
{
byte b = byteBuffer[index];
if (IsNewLineOrComment(b)) // NB: Do not advance to the next byte when end of line has been reached.
{
endOffset = index;
hasField = false;
break;
}
else if(b == ';')
{
endOffset = index++;
break;
}
else
{
++index;
}
}
if (endOffset >= 0)
{
buffer.Append(byteBuffer, startOffset, endOffset - startOffset);
break;
}
else if (index > startOffset)
{
buffer.Append(byteBuffer, startOffset, index - startOffset);
}
} while (RefillBuffer());
return buffer.ToString();
}
/// <summary>Skips the next data field.</summary>
/// <remarks>This method will return <see langword="false"/> on end of line.</remarks>
/// <returns><see langword="true"/> if a field was skipped; <see langword="false"/> otherwise.</returns>
public bool SkipField()
{
if (length == 0) throw new InvalidOperationException();
if (!hasField) return false;
else if (index >= length) RefillBuffer();
// If the current character is a new line or a comment, we are at the end of a line.
if (IsNewLineOrComment(byteBuffer[index]))
{
hasField = false;
return false;
}
do
{
while (index < length)
{
byte b = byteBuffer[index];
if (IsNewLineOrComment(b)) // NB: Do not advance to the next byte when end of line has been reached.
{
hasField = false;
return true;
}
else
{
++index;
if (b == ';')
{
return true;
}
}
}
} while (RefillBuffer());
return true;
}
}
}
@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace System.Unicode.Builder
{
public class UnicodeDataManager
{
public const string UnicodeDataFileName = "UnicodeData.txt";
public const string PropListFileName = "PropList.txt";
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;
}
public static async Task<UnicodeData> DownloadAndBuildDataAsync(IUcdSource ucdSource)
{
var builder = new UnicodeDataBuilder(new Version(7, 0));
using (var reader = new UnicodeDataFileReader(await ucdSource.OpenDataFileAsync(UnicodeDataFileName).ConfigureAwait(false)))
{
int rangeStartCodePoint = -1;
while (reader.MoveToNextLine())
{
var codePoint = new UnicodeCharacterRange(int.Parse(reader.ReadField(), NumberStyles.HexNumber));
string name = reader.ReadField();
if (!string.IsNullOrEmpty(name) && name[0] == '<' && name[name.Length - 1] == '>')
{
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);
name = name.Substring(1, name.Length - 8);
rangeStartCodePoint = -1;
}
else
{
name = name.Substring(1, name.Length - 2);
if (codePoint.IsSingleCodePoint) name = name + "-" + codePoint.ToString();
}
}
else if (rangeStartCodePoint >= 0)
{
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),
Category = UnicodeCategoryInfo.FromShortName(reader.ReadField()).Category,
CanonicalCombiningClass = (CanonicalCombiningClass)byte.Parse(reader.ReadField()),
};
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));
}
characterData.DecompositionType = NullIfEmpty(reader.ReadField());
string numericDecimalField = NullIfEmpty(reader.ReadField());
string numericDigitField = NullIfEmpty(reader.ReadField());
string numericNumericField = NullIfEmpty(reader.ReadField());
characterData.BidirectionalMirrored = reader.ReadField() == "Y";
characterData.OldName = NullIfEmpty(reader.ReadField());
reader.SkipField();
characterData.SimpleUpperCaseMapping = ParseSimpleCaseMapping(reader.ReadField());
characterData.SimpleLowerCaseMapping = ParseSimpleCaseMapping(reader.ReadField());
characterData.SimpleTitleCaseMapping = ParseSimpleCaseMapping(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 (numericNumericField != null)
{
characterData.NumericValue = UnicodeRationalNumber.Parse(numericNumericField);
if (numericDigitField != null)
{
if (numericDigitField != numericNumericField)
{
throw new InvalidDataException("Invalid value for field 7 of code point " + characterData.CodePointRange.ToString() + ".");
}
if (numericDecimalField != null)
{
if (numericDecimalField != numericDigitField)
{
throw new InvalidDataException("Invalid value for field 6 of code point " + characterData.CodePointRange.ToString() + ".");
}
}
else
{
characterData.NumericType = UnicodeNumericType.Digit;
}
}
else
{
characterData.NumericType = UnicodeNumericType.Numeric;
}
}
builder.Insert(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<ContributoryProperties>.TryGetNamedValue(reader.ReadField().Trim(), out property))
{
builder.SetProperties(property, range);
}
}
}
return builder.ToUnicodeData();
}
}
}
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8DFDEE6C-4F0D-4DE1-B346-574CB56D2B8B}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>System.Unicode.Builder</RootNamespace>
<AssemblyName>UnicodeInformation.Builder</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\System.Unicode.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileUcdSource.cs" />
<Compile Include="HttpUcdSource.cs" />
<Compile Include="IUcdSource.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UnicodeCharacterDataBuilder.cs" />
<Compile Include="UnicodeDataBuilder.cs" />
<Compile Include="UnicodeDataFileReader.cs" />
<Compile Include="UnicodeDataManager.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\System.Unicode.snk">
<Link>System.Unicode.snk</Link>
</None>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UnicodeInformation\UnicodeInformation.csproj">
<Project>{cb722958-a1c4-4121-804b-7d5a671491b1}</Project>
<Name>UnicodeInformation</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>