diff --git a/sakura_core/parse/CWordParse.h b/sakura_core/parse/CWordParse.h index d646c8a543..9f3d49c97e 100644 --- a/sakura_core/parse/CWordParse.h +++ b/sakura_core/parse/CWordParse.h @@ -125,15 +125,40 @@ class CWordParse{ static bool _match_charlist( const WCHAR c, const WCHAR *pszList ); }; -BOOL IsURL( const wchar_t* psz, int offset, int length, int* outLength);/* offset 引数の追加により境界判定が行える高速版 */ +/** 指定アドレスが URL の先頭ならば TRUE とその長さを返す。 + @param[in] pszLine 文字列バッファの先頭アドレス + @param[in] offset URL 判定開始文字を示す、pszLine からの相対位置。 + @param[in] nLineLen URL 判定最終文字の次を示す、pszLine からの相対位置。 + @param[out] pnMatchLen URL の長さを受け取る変数のアドレス。NULL可。長さとは pszLine + offset からの距離。 + + 境界判定はメールアドレスの先頭でのみ行われ、URL の先頭ではこれまで通り行われません。 +*/ +BOOL IsURL( const wchar_t* pszLine, int offset, int nLineLen, int* pnMatchLen); + +/** @deprecated 互換性のために残されています。offset 引数が追加されたものを使用してください。 +*/ inline -BOOL IsURL( const wchar_t* psz, int length, int* outLength) /* 指定アドレスがURLの先頭ならばTRUEとその長さを返す。高速版の追加により obsolete. */ +BOOL IsURL( const wchar_t* pszLine, int nLineLen, int* pnMatchLen) { - return IsURL(psz, 0, length, outLength); + return IsURL(pszLine, 0, nLineLen, pnMatchLen); } -BOOL IsMailAddress( const wchar_t* pszBuf, int offset, int nBufLen, int* pnAddressLength); /* offset 引数の追加により境界判定が行える高速版 */ + +/** 指定アドレスがメールアドレスの先頭ならば TRUE とその長さを返す。 + @param[in] pszBuf 文字列バッファの先頭アドレス + @param[in] offset メールアドレス判定開始文字を示す、pszBuf からの相対位置。 + @param[in] nBufLen メールアドレス判定最終文字の次を示す、pszBuf からの相対位置。 + @param[out] pnAddressLength メールアドレスの長さを受け取る変数のアドレス。NULL可。長さとは pszBuf + offset からの距離。 + + 正の offset が与えられた場合は、その場合に限り、判定開始位置直前の文字との間で境界判定を行います。 + 途中から切り出したメールアドレスの一部をメールアドレスであると誤って判定しないために + pszBuf を固定し offset を0以上の範囲で変化させるのが望ましい使用方法です。 +*/ +BOOL IsMailAddress( const wchar_t* pszBuf, int offset, int nBufLen, int* pnAddressLength); + +/** @deprecated 互換性のために残されています。offset 引数が追加されたものを使用してください。 +*/ inline -BOOL IsMailAddress( const wchar_t* pszBuf, int nBufLen, int* pnAddressLength) /* 現在位置がメールアドレスならば、NULL以外と、その長さを返す。高速版の追加により obsolete. */ +BOOL IsMailAddress( const wchar_t* pszBuf, int nBufLen, int* pnAddressLength) { return IsMailAddress(pszBuf, 0, nBufLen, pnAddressLength); } diff --git a/tests/unittests/test-is_mailaddress.cpp b/tests/unittests/test-is_mailaddress.cpp index 992f145898..3ad214b57e 100644 --- a/tests/unittests/test-is_mailaddress.cpp +++ b/tests/unittests/test-is_mailaddress.cpp @@ -26,6 +26,9 @@ #define NOMINMAX #include +#include +#include +#include #include #include "parse/CWordParse.h" @@ -199,6 +202,120 @@ TEST(testIsMailAddress, CheckAwithAtmark) ASSERT_SAME(FALSE, szTest, _countof(szTest) - 1, NULL); } +TEST(testIsMailAddress, OffsetParameter) +{ + /* + Prepare test cases. + + 3つの offset値(-1, 0, 1)と、メールアドレスに見える2つの文字列 + (Buffer+1=メールアドレスの先頭, Buffer+2=メールアドレスの途中) + の組み合わせにより定義する。 + */ + const wchar_t* const Buffer = L" test@example.com"; + const wchar_t* const BufferEnd = Buffer + wcslen(Buffer); + const struct { + bool expected; + const wchar_t* address; // to be tested by IsMailAddress. + int offset; // passed to IsMailAddress as 2nd param. + const wchar_t* buffer() const { // passed to IsMailAddress as 1st param. + return this->address - this->offset; + } + } testCases[] = { + { true, Buffer+1, 0 }, // true is OK. Buffer+1 is a mail address. + { true, Buffer+1, 1 }, // true is OK. Buffer+1 is a mail address. + { true, Buffer+1, -1 }, // true is OK. Buffer+1 is a mail address. + { true, Buffer+2, 0 }, // Limitation: Non positive offset prevents IsMailAddress from looking behind of a possible head of a mail address. + { false, Buffer+2, 1 }, // false is OK. Buffer+2 is not a head of a mail adderss. + { true, Buffer+2, -1 } // Limitation: Non positive offset prevents IsMailAddress from looking behind of a possible head of a mail address. + }; + for (auto& aCase: testCases) { + assert(Buffer <= aCase.buffer()); + } + + /* + Apply IsMailAddress to the cases. + */ + for (auto& aCase: testCases) { + EXPECT_EQ( + aCase.expected, + bool(IsMailAddress(aCase.buffer(), aCase.offset, BufferEnd - aCase.buffer(), NULL)) + ) << "1st param of IsMailAddress: pszBuf is \"" << aCase.buffer() << "\"\n" + << "2nd param of IsMailAddress: offset is " << aCase.offset; + } +} + +TEST(testIsMailAddress, OffsetParameter2) +{ + const wchar_t* const Text = L" test@example.com "; + const wchar_t* const Address = Text + 3; // Address is "test@example.com" + const wchar_t* const PseudoAddress = Text + 6; // PseudoAddress is "t@example.c", shortest form recognized by IsMailAddress. + const wchar_t* const PseudoAddressEnd = Text + 17; + const wchar_t* const AddressEnd = Text + 19; + const wchar_t* const TextEnd = Text + 22; + + struct Result { + bool is_address; + int length; + }; + const Result FalseResult = {false, 0}; + auto ExpectedResult = [=](const wchar_t* p1, const wchar_t* p2, const wchar_t* p3) -> Result + { + /* + p2 と p3 が以下の条件を外れたら、TRUE 判定の可能性はゼロ。 + * Address <= p2 <= PseudoAddress + * PseudoAddressEnd <= p3 + */ + if (p2 < Address || PseudoAddress < p2) { + return FalseResult; + } + if (p3 < PseudoAddressEnd) { + return FalseResult; + } + + if (p2 == Address) { + if (AddressEnd <= p3) { + return Result{true, static_cast(AddressEnd - Address)}; // 文句なしの TRUE 判定。 + } else { + return Result{true, static_cast(p3 - Address)}; // アドレスの終端が切り詰められているが、IsMailAddress には知る由がない。ゆえに問題なし。 + } + } else if (p2 <= p1) { + if (AddressEnd <= p3) { + return Result{true, static_cast(AddressEnd - p2)}; // アドレスの先端が切り詰められているが、IsMailAddress には知る由がない。ゆえに問題なし。 + } else { + return Result{true, static_cast(p3 - p2)}; // アドレスの先端と終端が切り詰められているが、IsMailAddress には知る由がない。ゆえに問題なし。 + } + } else { + return FalseResult; // アドレスの先端が切り詰められ、IsMailAddress が境界判定によりそれを検知した。文句なしの FALSE 判定。 + } + }; + auto IsEqualResult = [](const Result& expected, const Result& actual) -> testing::AssertionResult + { + if (expected.is_address != actual.is_address) { + return testing::AssertionFailure() << "IsMailAddress returned " << (actual.is_address?"TRUE":"FALSE") << " but expected " << (expected.is_address?"TRUE":"FALSE") << "."; + } + if (expected.length != actual.length) { + return testing::AssertionFailure() << "IsMailAddress returned the address length " << (actual.length) << " but expected " << (expected.length) << "."; + } + return testing::AssertionSuccess(); + }; + + /* + Text...TextEnd の範囲の文字配列に対して、IsMailAddress の3つの + 引数(pszBuf, offset, nBufLen)がとりうるすべての値を総当たりで試す。 + */ + for (const wchar_t* p1 = Text; p1 != TextEnd; ++p1) // p1 is a pointer to buffer. + for (const wchar_t* p2 = Text; p2 != TextEnd; ++p2) // p2 is a pointer to address. + for (const wchar_t* p3 = Text; p3 != TextEnd; ++p3) { // p3 is a pointer to the end of buffer. + Result actual = {false, 0}; + actual.is_address = IsMailAddress(p1, p2 - p1, p3 - p1, &(actual.length)); + + EXPECT_TRUE(IsEqualResult(ExpectedResult(p1, p2, p3), actual)) + << "1st param of IsMailAddress: pszBuf is \"" << (p1 <= p3 ? std::string(p1, p3) : "") << "\"\n" + << "2nd param of IsMailAddress: offset is " << (p2 - p1) << "\n" + << "pszBuf + offset is \"" << (p2 <= p3 ? std::string(p2, p3) : "") << "\""; + } +} + ////////////////////////////////////////////////////////////////////// // テストマクロの後始末