私は自分自身の質問に答えるために、より良い答えが見つからないと思います。
原因。
スペースを使用しない修飾子を使用する言語は、多くの場合、文字にはです。ヨーロッパ言語の場合、置換がある。 "u" (U+0075) + "¨" (U+00A8) = "ü" (U+00FC)
。この場合、@ tchristによる解決策で十分です。
しかし、複雑な書込みシステムでは、非スペーシング修飾子に代わるものはありません。したがって、NUnitのTextMessageWriter.WriteCaretLine(int mismatch)
は、mismatch
パラメータをオフセットとしてとして扱いますが、タイ語の文字列の画面表現は、と短いキャレットライン("-----^"
)のです。
解決策。
WriteCaretLine(int mismatch)
に非スペーシング修飾子を遵守させるには、このオフセットの前にスペップされたスペップ修飾子の数を減らすことが必要です。
新しいコードを呼び出すためにのみ必要な補足クラスをすべて実装します。
タイと並んで、私はDevanagariとTibetanでテストしました。それは期待どおりに動作します。
さらに別の落とし穴。 ReSharperを使ってVisual StudioでNUnitを使用している場合は、Internet Explorerのフォント(R#で管理することはできません)を設定して、タイ語、デーバナガリなどに適切なモノスペースフォントを使用するようにしてください。
実装。
TextMessageWriter
を継承し、そのDisplayStringDifferences
をオーバーライドします。
- 独自の
ClipExpectedAndActual
とFindMismatchPosition
を実装します。ここには非スペーシング修飾子があります。スペーシングのない要素の計算にも影響する可能性があるため、適切なクリッピングが必要です。
EqualConstraint
を継承し、そのWriteMessageTo(MessageWriter writer)
を上書きして、MessageWriterを使用しました。
- オプションで、カスタム制約の簡単な呼び出しのためのカスタムラッパーを作成します。
ソースコードは以下のとおりです。コードの約80%は何も役に立ちませんが、元のコードのアクセスレベルのために含まれています。
// Step 1.
public class ThaiMessageWriter : TextMessageWriter
{
/// <summary>
/// This method is merely a copy of the original method taken from NUnit sources,
/// except that it changes meaning of <paramref name="mismatch"/> before the caret line is displayed.
/// <remarks>
/// Originally passed <paramref name="mismatch"/> contains byte offset, while proper display of caret requires
/// it position to be calculated in character placeholder units. They are different in case of
/// over- or under-string Unicode characters like acute mark or complex script (Thai)
/// </remarks>
/// </summary>
/// <param name="clipping"></param>
public override void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping)
{
// Maximum string we can display without truncating
int maxDisplayLength = MaxLineLength
- PrefixLength // Allow for prefix
- 2; // 2 quotation marks
int mismatchOffset = mismatch;
if (clipping)
MsgUtils2.ClipExpectedAndActual(ref expected, ref actual, maxDisplayLength, mismatchOffset);
expected = MsgUtils.EscapeControlChars(expected);
actual = MsgUtils.EscapeControlChars(actual);
// The mismatch position may have changed due to clipping or white space conversion
int mismatchInCharPlaceholders = MsgUtils2.FindMismatchPosition(expected, actual, 0, ignoreCase);
Write(Pfx_Expected);
WriteExpectedValue(expected);
if (ignoreCase)
WriteModifier("ignoring case");
WriteLine();
WriteActualLine(actual);
//DisplayDifferences(expected, actual);
if (mismatch >= 0)
WriteCaretLine(mismatchInCharPlaceholders);
}
// Copied due to private
/// <summary>
/// Write the generic 'Actual' line for a constraint
/// </summary>
/// <param name="constraint">The constraint for which the actual value is to be written</param>
private void WriteActualLine(Constraint constraint)
{
Write(Pfx_Actual);
constraint.WriteActualValueTo(this);
WriteLine();
}
// Copied due to private
/// <summary>
/// Write the generic 'Actual' line for a given value
/// </summary>
/// <param name="actual">The actual value causing a failure</param>
private void WriteActualLine(object actual)
{
Write(Pfx_Actual);
WriteActualValue(actual);
WriteLine();
}
// Copied due to private
private void WriteCaretLine(int mismatch)
{
// We subtract 2 for the initial 2 blanks and add back 1 for the initial quote
WriteLine(" {0}^", new string('-', PrefixLength + mismatch - 2 + 1));
}
}
// Step 2.
public static class MsgUtils2
{
private static readonly string ELLIPSIS = "...";
/// <summary>
/// Almost a copy of MsgUtil.ClipExpectedAndActual method
/// </summary>
/// <param name="expected"></param>
/// <param name="actual"></param>
/// <param name="maxDisplayLength"></param>
/// <param name="mismatch"></param>
public static void ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, int mismatch)
{
// Case 1: Both strings fit on line
int maxStringLength = Math.Max(expected.Length, actual.Length);
if (maxStringLength <= maxDisplayLength)
return;
// Case 2: Assume that the tail of each string fits on line
int clipLength = maxDisplayLength - ELLIPSIS.Length;
int clipStart = maxStringLength - clipLength;
// Case 3: If it doesn't, center the mismatch position
if (clipStart > mismatch)
clipStart = Math.Max(0, mismatch - clipLength/2);
// shift both clipStart and maxDisplayLength if they split non-placeholding symbol
AdjustForNonPlaceholdingCharacter(expected, ref clipStart);
AdjustForNonPlaceholdingCharacter(expected, ref maxDisplayLength);
expected = MsgUtils.ClipString(expected, maxDisplayLength, clipStart);
actual = MsgUtils.ClipString(actual, maxDisplayLength, clipStart);
}
private static void AdjustForNonPlaceholdingCharacter(string expected, ref int index)
{
while (index > 0 && CharUnicodeInfo.GetUnicodeCategory(expected[index]) == UnicodeCategory.NonSpacingMark)
{
index--;
}
}
static public int FindMismatchPosition(string expected, string actual, int istart, bool ignoreCase)
{
int length = Math.Min(expected.Length, actual.Length);
string s1 = ignoreCase ? expected.ToLower() : expected;
string s2 = ignoreCase ? actual.ToLower() : actual;
int iSpacingCharacters = 0;
for (int i = 0; i < istart; i++)
{
if (CharUnicodeInfo.GetUnicodeCategory(s1[i]) != UnicodeCategory.NonSpacingMark)
iSpacingCharacters++;
}
for (int i = istart; i < length; i++)
{
if (s1[i] != s2[i])
return iSpacingCharacters;
if (CharUnicodeInfo.GetUnicodeCategory(s1[i]) != UnicodeCategory.NonSpacingMark)
iSpacingCharacters++;
}
//
// Strings have same content up to the length of the shorter string.
// Mismatch occurs because string lengths are different, so show
// that they start differing where the shortest string ends
//
if (expected.Length != actual.Length)
return length;
//
// Same strings : We shouldn't get here
//
return -1;
}
}
// Step 3.
public class ThaiEqualConstraint : EqualConstraint
{
private readonly string _expected;
// WTF expected is private?
public ThaiEqualConstraint(string expected) : base(expected)
{
_expected = expected;
}
public override void WriteMessageTo(MessageWriter writer)
{
// redirect output to customized MessageWriter
var myMessageWriter = new ThaiMessageWriter();
base.WriteMessageTo(myMessageWriter);
writer.Write(myMessageWriter);
}
}
// Step 4.
public static class ThaiText
{
public static EqualConstraint IsEqual(string expected)
{
return new ThaiEqualConstraint(expected);
}
}
残念ながら、これはオプションではありません。六角ダンプの真ん中を指している_correctly_は、元のテキストを指している_misplaced_矢印と比較して、解釈がさらに困難です。 – bytebuster