.NET Core 용 StringTokenizer
토큰에서 문자열을 분할하는 것은 String.Split () 이 우리를 믿게 만드는 것보다 더 복잡한 주제 입니다. 문자열이 토큰으로 해석되고 분할 될 수있는 공통 정책이 3 개 이상 있습니다.
정책 1 : String.Split ()과 동일
이 정책에 대해 언급 할 내용이 많지 않습니다. 문자열을 감안할 s와 구분 기호 d, 휴식 s에 의해 구분 된 세그먼트로를 d. 여기서 주요 단점은 구분 기호가 토큰 중 하나 이상의 일부인 경우 원하는 토큰을 재구성하는 데 비용이 많이들 수 있다는 것입니다.
정책 2 : 특수 문자 이스케이프
문자는 이스케이프 문자 e (일반적으로 백 슬래시 \)로 선언되어 그 뒤에 오는 문자가 특별한 의미를 잃게됩니다. 토큰 문자열은 다음과 같습니다.
token_1 token_2 very\ long \ token
이는
{ "token_1", "token_2", "very long token" }
정책 3 : 따옴표 안에 토큰 배치
이 접근 방식은 예를 들어 MSExcel에서 생성 된 CSV 파일에 사용됩니다. 따옴표 사이의 모든 것은 토큰으로 간주됩니다. 따옴표 "가 토큰의 일부인 경우 두 배가 ""됩니다. 토큰 문자열은 다음과 같습니다.
token_1,token_2,"token2,5"
이는
{ "token_1", "token_2", "token2,5" }
암호
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace Pillepalle1.ConsoleTelegramBot.Model.Misc
{
public sealed class StringTokenizer
{
private string _sourceString = null; // Provided data to split
#region Constructors
/// <summary>
/// Creates a new StringTokenizer
/// </summary>
/// <param name="dataInput">Data to be split into tokens</param>
public StringTokenizer(string dataInput)
{
_sourceString = dataInput ?? string.Empty;
}
#endregion
#region Interface
/// <summary>
/// Access tokens by index
/// </summary>
public string this[int index]
{
get
{
if (index >= this.Count)
{
return String.Empty;
}
return _Tokens[index];
}
}
/// <summary>
/// How many tokens does the command consist of
/// </summary>
public int Count
{
get
{
return _Tokens.Count;
}
}
/// <summary>
/// Which strategy is used to split the string into tokens
/// </summary>
public StringTokenizerStrategy Strategy
{
get
{
return _strategy;
}
set
{
if (value != _strategy)
{
_strategy = value;
_tokens = null;
}
}
}
private StringTokenizerStrategy _strategy = StringTokenizerStrategy.Split;
/// <summary>
/// Character used to delimit tokens
/// </summary>
public char Delimiter
{
get
{
return _delimiter;
}
set
{
if (value != _delimiter)
{
_delimiter = value;
_tokens = null;
}
}
}
private char _delimiter = ' ';
/// <summary>
/// Character used to escape the following character
/// </summary>
public char Escape
{
get
{
return _escape;
}
set
{
if (value != _escape)
{
_escape = value;
if (Strategy == StringTokenizerStrategy.Escaping)
{
_tokens = null;
}
}
}
}
private char _escape = '\\';
/// <summary>
/// Character used to surround tokens
/// </summary>
public char Quotes
{
get
{
return _quotes;
}
set
{
if (value != _quotes)
{
_quotes = value;
if (Strategy == StringTokenizerStrategy.Quotation)
{
_tokens = null;
}
}
}
}
private char _quotes = '"';
#endregion
#region Predefined Regex
private Regex Whitespaces
{
get
{
return new Regex("\\s+");
}
}
#endregion
#region Implementation Details
/// <summary>
/// Formats and splits the tokens by delimiter allowing to add delimiters by quoting
/// </summary>
private List<string> _SplitRespectingQuotation()
{
string data = _sourceString;
// Doing some basic transformations
data = Whitespaces.Replace(data, " ");
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialisation
List<string> l = new List<string>();
char[] record = data.ToCharArray();
StringBuilder property = new StringBuilder();
char c;
bool quoting = false;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Scan character by character
for (int i = 0; i < record.Length; i++)
{
c = record[i];
// Quotation-Character: Single -> Quote; Double -> Append
if (c == Quotes)
{
if (i == record.Length - 1)
{
quoting = !quoting;
}
else if (Quotes == record[1 + i])
{
property.Append(c);
i++;
}
else
{
quoting = !quoting;
}
}
// Delimiter: Escaping -> Append; Otherwise append
else if (c == Delimiter)
{
if (quoting)
{
property.Append(c);
}
else
{
l.Add(property.ToString());
property.Clear();
}
}
// Any other character: Append
else
{
property.Append(c);
}
}
l.Add(property.ToString()); // Add last token
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Checking consistency
if (quoting) throw new FormatException(); // All open quotation marks closed
return l;
}
/// <summary>
/// Splits the string by declaring one character as escape
/// </summary>
private List<string> _SplitRespectingEscapes()
{
string data = _sourceString;
// Doing some basic transformations
data = Whitespaces.Replace(data, " ");
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialisation
List<string> l = new List<string>();
char[] record = data.ToCharArray();
StringBuilder property = new StringBuilder();
char c;
bool escaping = false;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Scan character by character
for (int i = 0; i < record.Length; i++)
{
c = record[i];
if (escaping)
{
property.Append(c);
escaping = false;
continue;
}
if (c == Escape)
{
escaping = true;
}
else if (c == Delimiter)
{
l.Add(property.ToString());
property.Clear();
}
else
{
property.Append(c);
}
}
return l;
}
/// <summary>
/// Splits the string by calling a simple String.Split
/// </summary>
private List<string> _SplitPlain()
{
return new List<string>(Whitespaces.Replace(_sourceString, " ").Split(Delimiter));
}
/// <summary>
/// Backer for tokens
/// </summary>
private List<string> _Tokens
{
get
{
if (null == _tokens)
{
switch (Strategy)
{
case (StringTokenizerStrategy.Quotation): _tokens = _SplitRespectingQuotation(); break;
case (StringTokenizerStrategy.Escaping): _tokens = _SplitRespectingEscapes(); break;
default: _tokens = _SplitPlain(); break;
}
}
return _tokens;
}
}
private List<string> _tokens = null;
#endregion
}
public enum StringTokenizerStrategy
{
Split,
Quotation,
Escaping
}
}
답변
나는 이것이 클래스에 속하는지 확신하지 못합니다-적어도 하나는 아닙니다!
한 걸음 물러서서 무엇이 통합되고 각 "전략"을 구분하는지 살펴보십시오. 그들은 모두 입력 문자열을 변수 구분 기호를 기반으로 토큰 목록으로 변환해야합니다. 그러나 세 가지 옵션 중 하나에서만 사용되는 속성이 있으며 분할 논리의 대부분은 해당 전략에 고유합니다.
제안 1 : 세 가지 "독립형"기능.
정말로 그것들을 정적 클래스에 넣거나 델리게이트 / 람다로 특별한 일을해야하지만, 궁극적으로 하나의 큰 클래스를 갖는 것으로부터 얻을 것이 거의 없습니다.
public static IList<string> SplitRespectingQuotation(string sourceString, char delimiter = ' ', char quote = '"') { ... }
public static IList<string> SplitRespectingEscapes(string sourceString, char delimiter = ' ', char escape = '\') { ... }
public static IList<string> SplitPlain(string sourceString, char delimiter = ' ') { ... }
출력이 입력 매개 변수를 전달하도록하려면 그렇게하는 훨씬 더 가벼운 클래스를 만들 수 있습니다. 그 속성은 다음과 같습니다 readonly. 변경하고 다시 계산해야하는 경우 함수를 다시 호출하면됩니다. 결국, 그것은 본질적으로 현재 수업에서하고있는 일입니다!
또 다른 장점 : 분할을위한 새로운 전략이 떠오르면 다른 기능에 영향을주지 않고 새로운 기능을 생성 할 수 있습니다. 모두 독립적으로 테스트, 편집 및 삭제할 수 있습니다.
제안 2 : 추상 기본 클래스를 확장하는 세 가지 구체적인 클래스.
나는 당신이 _Tokens속성으로 했던 일을 좋아 합니다. 그것은 당신이 정말로 필요로 할 때까지 계산을 연기 할 수있게하여 당신이 원하지 않는 경우에 도움이됩니다. 또한 지원하는 사용 사례 ( "독립 실행 형"기능에서 지원되지 않음)는 예를 들어 이스케이프 문자를 변경하고 결과가 자동으로 "무효화"되도록하는 것입니다.
이러한 동작을 유지하기 위해 다음과 같이 공통 요소를 추상 기본 클래스로 가져올 수 있습니다.
public abstract class StringTokenizer
{
public string SourceString { get; }
public StringTokenizer(string dataInput)
{
SourceString = dataInput ?? string.Empty;
}
public string this[int index] => index >= this.Count ? String.Empty : Tokens[index];
public int Count => Tokens.Count;
public char Delimiter
{
get { return _delimiter; }
set
{
if (value != _delimiter)
{
_delimiter = value;
InvalidateResult();
}
}
}
private char _delimiter = ' ';
public IEnumerable<string> Tokens
{
get
{
if (_tokens is null)
{
_tokens = ComputeTokens();
}
return _tokens;
}
}
private List<string> _tokens = null;
protected abstract List<string> ComputeTokens();
protected void InvalidateResult()
{
_tokens = null;
}
}
주목할만한 변화 :
- 실제 분할 논리가 없습니다. 각 전략은 자체적으로 제공됩니다.
- 전략 별 속성이 없습니다. 이스케이프 기반 전략이 따옴표 문자에 대한 속성을 가질 필요가 없으며 그 반대의 경우도 마찬가지입니다.
- 직접 설정하는 대신
_tokens = null속성을 호출해야합니다InvalidateResult. 이것은 기본 클래스에 포함 된 논리를 유지하는 것을 허용_tokens합니다private. Tokens은 (는) 공개이며IEnumerable. 이를 통해 소비자는을 사용할 수foreach있지만 직접 수정하는 것은 권장하지 않습니다.
이제 기본 클래스에는 정확히 하나의 작업이 ComputeTokens있습니다. 이를 위해 속성을 만들어야하는 경우 자체 전략 별 논리를 기반으로 할 수 있습니다. 이러한 속성이 변경 될 때 이전에 계산 된 토큰을 무효화해야하는 경우 InvalidateResult.
다음은 전략 하위 클래스의 대략적인 예입니다.
public sealed class EscapeStringTokenizer : StringTokenizer
{
public EscapeStringTokenizer (string dataInput) : base(dataInput) { }
public char Escape
{
get { return _escape; }
set
{
if (value != _escape)
{
_escape = value;
InvalidateResult();
}
}
}
protected override List<string> ComputeTokens()
{
// Actual logic omitted
}
}
기타 관찰
- 구분 기호를 지정할 수 있지만 항상 공백을 압축합니다.
"a,a and b,b"구분 기호로 분할","하면{"a", "a and b", "b"}다시 돌아올 것으로 예상 하지만 실제로는 얻을 수{"a", "a and b", "b"}있습니다. - 구분 기호 등을 공개적으로 읽을 수 있다면 소스 문자열도 노출하지 않는 이유는 무엇입니까?
SourceString위의 추상 클래스를 참조하십시오 . - 나는 (상대적으로 새로운) 표현 본문 속성 접근자가 단순한 속성에 대해 더 나은 것을 발견했습니다.
Count위의 추상 클래스를 참조하십시오 . - 실수로
nullif 문의 조건으로 변수에 할당 하는 것이 가능하지 않다고 생각합니다 . 는 유효한 조건이 되려면 이어야합니다 (따라서 nullable이 아님)와x = null동일한 유형으로 평가 되기 때문 입니다. 그래도 피하고 싶다면라고 말할 수 있습니다 .xboolx == nullx is null - 다른 사람들이 언급했듯이 속성 앞에
_. 공개와 비공개를 구분하는 것이 아니라 지역 변수와 클래스 필드를 구분합니다. 그러나 개인적으로 나는_그 경우 에도 사용하지 않고 대신this.필요한 경우 선호 합니다. 그러나 전반적으로 이에 대해 유연하게 대처하고 기존 팀이나 프로젝트에서 이미 확립 된 패턴을 따라야합니다. - 또한 다른 사람들이 언급했듯이
var가능하면 변수를 선언 할 때 사용하십시오. 좋은 IDE는 변수 위로 마우스를 가져 가면 유형을 알려줄 수 있으며 그 이름은 유형이 없어도 해당 유형을 알려줍니다. - 그 메모에서 피 이름은 좋아
c하고l.i루프 / 인덱스 변수로서 관용적이기 때문에 괜찮지 만 다른 것들은 이해하기 위해 추가 컨텍스트가 필요합니다. 소스 코드 문자는 저렴하므로currentChar및 을 사용하여 가독성을 높이려면 비용을 지불하십시오finishedTokens. - 소스를
string로 번역 할 필요가 없습니다char[]. 이미string인덱스로 문자에 액세스 할 수 있습니다 .
당신은하지 말았어야
Whitespaces대한 GET 전용 속성으로 만 비공개로readonly필드와 당신은 당신이 매우 자주 사용하고 있기 때문에 그 정규식 컴파일해야한다.사용 region은 antypattern으로 간주됩니다.
개인 필드에만 밑줄 접두사를 사용하십시오. 메서드 나 속성에 사용하지 마십시오.
할당의 오른쪽에서 변수 유형이 명확
var하면 구체적인 유형 대신 사용해야 합니다.전달 된 ctor-argument 가 또는
_sourceString일 수string.Empty있기 때문에 코드가 많이 수행되고dataInput있습니다 . .NET Framework에서 예외를 던지고 싶습니다 .nullstring.Emptyctor변수를 다른 변수에 할당 한 다음 결과 변수를 조작하는 대신 다음과 같이 한 줄에서 수행 할 수 있습니다.
string data = Whitespaces.Replace(_sourceString, " ");대신에
string data = _sourceString; // Doing some basic transformations data = Whitespaces.Replace(data, " ");배열의 단일 항목에만 액세스해야하고 앞을 볼 필요가없는
foreach경우for루프 보다 우선해야합니다 .
한 글자로 된
l이름은 나 에게 좋지 않은 것 같습니다.오류의 원인을 설명하는 메시지를 예외에 추가해야한다고 생각합니다.
기본적으로 데이터에서 모든 공백을 제거합니다. 그러나 토큰 내부에 필요할 수 있습니다. 이를 지정하는 추가 옵션을 만들 수 있습니다.
훌륭한 피드백을 주신 모든 분들께 감사드립니다. FOS로 호스팅되는 내 코드에 대한 대부분의 변경 사항을 채택했습니다.https://github.com/pillepalle1/dotnet-pillepalle1 추가 유지 보수를 받게됩니다.
지금은 분할 논리를 세 가지 정적 확장 메서드로 압축했습니다. 또한 자동 토큰 무효화의 편의를 선택적으로 유지하기 위해 therubberduck 에서 제안한 래퍼를 만들었습니다.
내가 구현 한 제안
변수 이름 지정
l보다 설명적인 이름으로 대체 된 것과 같은 변수 이름예외 메시지 가 추가되었습니다.
토큰 콘텐츠 수정 확장 메서드에서 완전히 제거되었으며 래퍼에서 선택적으로 사용할 수 있습니다.
지역 이 완전히 제거되었습니다.
합리적이고 / 가능할 때마다 var 사용
루프 선호
foreach이상for루프 이상 반복sourceString대신으로 변환char[]첫번째입력 문자열
ArgumentNullException로 변환null하는 대신 던지기String.EmptyRFC4180에 따른 CSV 분할
더 많은 변경 사항을 채택했지만 일부 제안 (예 : 관련 Whitespaces및 표현 본문 속성)은 새로운 구현에서 쓸모 없게되었습니다.
구현하지 않은 제안
- 개인 / 보호 된 모든 항목에 대한 밑줄 이름 지정은 멤버 및 로컬 변수를 구분하는 것보다 더 합리적으로 보입니다
Tasks. 메서드는 동시성 검사를 수행하거나 (공개) 수행하지 않습니다 (개인).
암호
정적 토크 나이저 방법
using System;
using System.Text;
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public static class StringTokenizer
{
private static FormatException _nonQuotedTokenMayNotContainQuotes =
new FormatException("[RFC4180] If fields are not enclosed with double quotes, then double quotes may not appear inside the fields.");
private static FormatException _quotesMustBeEscapedException =
new FormatException("[RFC4180] If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote.");
private static FormatException _tokenNotFullyEnclosed =
new FormatException("[RFC4180] \"Each field may or may not be enclosed in double quotes\". However, for the final field the closing quotes are missing.");
/// <summary>
/// <para>
/// Formats and splits the tokens by delimiter allowing to add delimiters by quoting
/// similar to https://tools.ietf.org/html/rfc4180
/// </para>
///
/// <para>
/// Each field may or may not be enclosed in double quotes (however some programs, such as
/// Microsoft Excel, do not use double quotes at all). If fields are not enclosed with
/// double quotes, then double quotes may not appear inside the fields.
/// </para>
///
/// <para>
/// Fields containing line breaks (CRLF), double quotes, and commas should be enclosed in
/// double-quotes.
/// </para>
///
/// <para>
/// If double-quotes are used to enclose fields, then a double-quote appearing inside a
/// field must be escaped by preceding it with another double quote.
/// </para>
///
/// <para>
/// The ABNF defines
///
/// [field = (escaped / non-escaped)] ||
/// [non-escaped = *TEXTDATA] ||
/// [TEXTDATA = %x20-21 / %x23-2B / %x2D-7E]
///
/// specifically forbidding to include quotes in non-escaped fields, hardening the *SHOULD*
/// requirement above.
/// </para>
/// </summary>
public static ImmutableList<string> SplitRespectingQuotation(this string sourceString, char delimiter = ' ', char quotes = '"')
{
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialisation
var tokenList = ImmutableList<string>.Empty;
var tokenBuilder = new StringBuilder();
var expectingDelimiterOrQuotes = false; // Next char must be Delimiter or Quotes
var hasReadTokenChar = false; // We are not between tokens (=> No quoting)
var isQuoting = false;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Scan character by character
foreach (char c in sourceString)
{
if (expectingDelimiterOrQuotes)
{
expectingDelimiterOrQuotes = false;
if (c == delimiter)
{
isQuoting = false;
}
else if (c == quotes)
{
tokenBuilder.Append(c);
hasReadTokenChar = true;
continue;
}
else
{
throw _quotesMustBeEscapedException;
}
}
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
if (c == quotes)
{
if (isQuoting)
{
expectingDelimiterOrQuotes = true;
}
else
{
if (hasReadTokenChar)
{
throw _nonQuotedTokenMayNotContainQuotes;
}
isQuoting = true;
}
}
else if (c == delimiter)
{
if (isQuoting)
{
tokenBuilder.Append(c);
hasReadTokenChar = true;
}
else
{
tokenList = tokenList.Add(tokenBuilder.ToString());
tokenBuilder.Clear();
hasReadTokenChar = false;
}
}
// Any other character is just being appended to
else
{
tokenBuilder.Append(c);
hasReadTokenChar = true;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Tidy up open flags and checking consistency
tokenList = tokenList.Add(tokenBuilder.ToString());
if (isQuoting && !expectingDelimiterOrQuotes)
{
throw _tokenNotFullyEnclosed;
}
return tokenList;
}
/// <summary>
/// Splits the string by declaring one character as escape
/// </summary>
public static ImmutableList<string> SplitRespectingEscapes(this string sourceString, char delimiter = ' ', char escapeChar = '\\')
{
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialisation
var tokenList = ImmutableList<string>.Empty;
var tokenBuilder = new StringBuilder();
var escapeNext = false;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Scan character by character
foreach (char c in sourceString)
{
if (escapeNext)
{
tokenBuilder.Append(c);
escapeNext = false;
continue;
}
if (c == escapeChar)
{
escapeNext = true;
}
else if (c == delimiter)
{
tokenList = tokenList.Add(tokenBuilder.ToString());
tokenBuilder.Clear();
}
else
{
tokenBuilder.Append(c);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Tidy up open flags and checking consistency
tokenList = tokenList.Add(tokenBuilder.ToString());
if (escapeNext) throw new FormatException(); // Expecting additional char
return tokenList;
}
/// <summary>
/// Splits the string by calling a simple String.Split
/// </summary>
public static ImmutableList<string> SplitPlain(this string sourceString, char delimiter = ' ')
{
return ImmutableList<string>.Empty.AddRange(sourceString.Split(delimiter));
}
}
}
추상 래퍼 기본 클래스
using System;
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public abstract class AStringTokenizer
{
public AStringTokenizer()
{
}
public AStringTokenizer(string sourceString)
{
SourceString = sourceString;
}
/// <summary>
/// String that is supposed to be split in tokens
/// </summary>
public string SourceString
{
get
{
return _sourceString;
}
set
{
if (null == value)
{
throw new ArgumentNullException("Cannot split null in tokens");
}
else if (_sourceString.Equals(value))
{
// nop
}
else
{
_sourceString = value;
_InvalidateTokens();
}
}
}
private string _sourceString = String.Empty;
/// <summary>
/// Character indicating how the source string is supposed to be split
/// </summary>
public char Delimiter
{
get
{
return _delimiter;
}
set
{
if (value != _delimiter)
{
_delimiter = value;
_InvalidateTokens();
}
}
}
private char _delimiter = ' ';
/// <summary>
/// Flag indicating whether whitespaces should be removed from start and end of each token
/// </summary>
public bool TrimTokens
{
get
{
return _trimTokens;
}
set
{
if (value != _trimTokens)
{
_trimTokens = value;
_InvalidateTokens();
}
}
}
private bool _trimTokens = false;
/// <summary>
/// Result of tokenization
/// </summary>
public ImmutableList<string> Tokens
{
get
{
if (null == _tokens)
{
_tokens = Tokenize();
if (TrimTokens)
{
_tokens = _TrimTokens(_tokens);
}
}
return _tokens;
}
}
private ImmutableList<string> _tokens = null;
/// <summary>
/// Split SourceString into tokens
/// </summary>
protected abstract ImmutableList<string> Tokenize();
/// <summary>
/// Trims whitespaces from tokens
/// </summary>
/// <param name="candidates">List of tokens</param>
private ImmutableList<string> _TrimTokens(ImmutableList<string> candidates)
{
var trimmedTokens = ImmutableList<string>.Empty;
foreach (var token in candidates)
{
trimmedTokens = trimmedTokens.Add(token.Trim());
}
return trimmedTokens;
}
/// <summary>
/// Invalidate and recompute tokens if necessary
/// </summary>
protected void _InvalidateTokens()
{
_tokens = null;
}
}
}
일반 토큰 화를위한 래퍼
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class PlainStringTokenizer : AStringTokenizer
{
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitPlain(Delimiter);
}
}
}
견적 토큰 화를위한 래퍼
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class QuotationStringTokenizer : AStringTokenizer
{
/// <summary>
/// Indicates which character is used to encapsulate tokens
/// </summary>
public char Quotes
{
get
{
return _quotes;
}
set
{
if (value != _quotes)
{
_quotes = value;
_InvalidateTokens();
}
}
}
private char _quotes = '"';
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitRespectingQuotation(Delimiter, Quotes);
}
}
}
이스케이프 토큰 화를위한 래퍼
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class EscapedStringTokenizer : AStringTokenizer
{
/// <summary>
/// Indicates which character is used to escape characters
/// </summary>
public char Escape
{
get
{
return _escape;
}
set
{
if (value != _escape)
{
_escape = value;
_InvalidateTokens();
}
}
}
private char _escape = '"';
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitRespectingEscapes(Delimiter, Escape);
}
}
}