.NET Core için StringTokenizer
Bir dizeyi belirteçlere bölmek String.Split () in bizi inandırmak istediğinden daha karmaşık bir konudur . Bir dizenin yorumlanabileceği ve belirteçlere bölünebileceği en az üç ortak ilke vardır.
İlke 1: String.Split () ile Eşdeğer
Bu politika hakkında söylenecek pek bir şey yok. Bir dize sve sınırlayıcı verildiğinde d, ile sayrılmış segmentlere bölün d. Buradaki ana dezavantaj, sınırlayıcının jetonlardan en az birinin parçası olması durumunda, istenen jetonların yeniden yapılandırılmasının maliyetli olabilmesidir.
Politika 2: Özel karakterlerden kaçın
Bir karakter, kaçış karakteri e (genellikle ters eğik çizgi \) olarak bildirilir ve bu , onu izleyen karakterin özel anlamını kaybetmesine neden olur. Bir belirteç dizesi şu şekilde görünebilir:
token_1 token_2 very\ long \ token
eşdeğer olacaktır
{ "token_1", "token_2", "very long token" }
Politika 3: Jetonları tırnak içine alın
Bu yaklaşım, örneğin MSExcel'de oluşturulan CSV dosyalarında kullanılır. Tırnak işaretleri arasındaki her şey bir simge olarak kabul edilir. Tırnak işaretleri "belirtecin parçasıysa, iki katına çıkar "". Bir belirteç dizesi şu şekilde görünebilir:
token_1,token_2,"token2,5"
eşdeğer olacaktır
{ "token_1", "token_2", "token2,5" }
Kod
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
}
}
Yanıtlar
Bunun bir sınıfa ait olduğundan pek emin değilim - en azından tek bir sınıfa değil!
Bir adım geri gidin ve neyin birleştiğine ve her "stratejiyi" neyin ayırdığına bakın. Hepsinin bir girdi dizesini değişken sınırlayıcıya dayalı bir simge listesine dönüştürmesi gerekir. Ancak, üç seçenekten yalnızca biri tarafından kullanılan özellikler vardır ve bölme mantığının çoğu stratejisine özgüdür.
Öneri 1: Üç "bağımsız" işlev.
Onları gerçekten statik bir sınıfa koymanız veya delegeler / lambdalar ile özel bir şeyler yapmanız gerekir, ancak sonuçta büyük bir sınıfa sahip olmanın kazanacağı çok az şey vardır.
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 = ' ') { ... }
Çıktının girdi parametrelerini iletmesini istiyorsanız, bunu yapan çok daha hafif bir sınıf yapabilirsiniz. Özellikleri readonly; onları değiştirmeniz ve yeniden hesaplamanız gerekirse, işlevi tekrar çağırmanız yeterlidir. Sonuçta, aslında şu anki sınıfınızın içinde yaptığınız şey bu!
Başka bir artı: Bölme için yeni bir strateji bulduğunuzda, diğerlerini etkilemeden yeni bir işlev oluşturabilirsiniz. Hepsi bağımsız olarak test edilebilir, düzenlenebilir ve silinebilir.
Öneri 2: Soyut bir temel sınıfı genişleten üç somut sınıf.
Mülkle yaptığınız şeyi beğendim _Tokens: gerçekten ihtiyacınız olana kadar hesaplamayı ertelemenizi sağlar, bu da istemeyeceğiniz durumlarda faydalıdır. Ayrıca, desteklediği bir kullanım durumu ("bağımsız" işlevler tarafından desteklenmeyen), örneğin kaçış karakterini değiştirmek ve sonucun otomatik olarak "geçersiz kılınmasını" sağlamaktır.
Bu davranışı korumak için, ortak öğeleri aşağıdaki gibi soyut bir temel sınıfa çekebilirsiniz:
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;
}
}
Önemli değişiklikler:
- Gerçek bölünmüş mantık yoktur. Her strateji kendi stratejisini sağlayacaktır.
- Stratejiye özgü özellikler yoktur. Bir alıntı karakteri için bir özelliğe sahip olmak için kaçış tabanlı bir stratejiye gerek yoktur ve bunun tersi de geçerlidir.
- Doğrudan ayar yapmak yerine
_tokens = null, özellikler çağırmalıdırInvalidateResult. Bu , temel sınıfa dahil edilen mantığı tutan_tokensyapılmasına izin verirprivate. Tokensherkese açıktır ve birIEnumerable. Bu, tüketicilerin kullanmasına izin verirforeach, ancak doğrudan değişiklik yapılmasını engeller.
Artık bir temel sınıfın tam olarak bir işi vardır: uygulama ComputeTokens. Bunu yapmak için özellikler yaratması gerekiyorsa, bunu kendi stratejiye özgü mantığına dayanarak yapabilir. Bu özelliklerin önceden hesaplanmış jetonları değiştiklerinde geçersiz kılmaları gerekiyorsa, çağırabilirler InvalidateResult.
İşte bir strateji alt sınıfının nasıl görüneceğine dair kaba bir örnek:
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
}
}
Diğer Gözlemler
- Sınırlayıcının belirtilmesine izin verirsiniz, ancak her zaman beyaz boşluğu yoğunlaştırırsınız. Ben bölmek Eğer
"a,a and b,b"bir sınırlayıcısıyla",", benim hemen beklenebilir{"a", "a and b", "b"}geri - ama aslında alacağı{"a", "a and b", "b"}. - Sınırlayıcı vb. Herkes tarafından okunabiliyorsa, neden kaynak dizeyi de göstermiyorsunuz?
SourceStringYukarıdaki soyut sınıfıma bakın . - (Nispeten yeni) ifade gövdeli özellik erişimcilerini basit özellikler için her yönüyle daha iyi buluyorum.
CountYukarıdaki soyut sınıfıma bakın . nullBir değişkene yanlışlıkla if ifadesinin koşulu olarak atamanın mümkün olduğunu sanmıyorum . Bunun nedeni , geçerli bir koşul olması için bir olması gereken (ve dolayısıyla nullx = nullyapılamaz) ile aynı tür olarak değerlendirilmesidir . Hala kaçınmak istiyorsan diyebilirsin .xboolx == nullx is null- Başkalarının da bahsettiği gibi, özelliklerin önüne
_. Genel ve özel arasında ayrım yapmak için orada değil, yerel değişkenler ve sınıf alanları arasında ayrım yapmak var. Şahsen ben_bu durumda bile kullanmıyorum , bunun yerinethis.gerekirse tercih ediyorum . Ancak genel olarak bu konuda esnek olmanız ve mevcut bir ekip veya projede halihazırda oluşturulmuş herhangi bir modeli takip ettiğinizden emin olmanız gerekecektir. - Ayrıca diğerlerinin de bahsettiği gibi,
varmümkün olduğunda değişkenleri bildirirken kullanın . Herhangi bir iyi IDE, değişkenin üzerine geldiğinizde size türü söyleyebilir ve adı, türü olmadan bile bunun ne için olduğunu size söyleyecektir. - Bu notta,
cve gibi isimlerden kaçınınl.iiyidir çünkü döngü / dizin değişkeni olarak deyimseldir, ancak diğerleri anlamak için fazladan bağlam gerektirir. Kaynak kod karakterleri bazı ekstra okunabilmesi için ödeme böylece kullanarak, ucuzcurrentCharvefinishedTokens. - Kaynağı
stringa'ya çevirmenize gerek yokturchar[];stringdizine göre zaten karakterlere erişebilirsiniz .
Yalnızca
Whitespaceselde edilen bir mülk olarak değil, özel birreadonlyalan olarak sahip olmalısınız ve bu normal ifadeyi çok sık kullandığınız için derlemelisiniz.Kullanımı regionbir antypattern olarak kabul edilir
Alt çizgi önekini yalnızca özel alanlar için kullanın. Bunları yöntemler veya özellikler için kullanmayın.
Bir değişkenin türü bir atamanın sağ tarafından netse
varsomut tür yerine kullanmalısınız .Kod çok şey yapıyor olsa
_sourceStringdastring.Empty, geçirilen ctor argümanı veyadataInputolabilir . İçinde bir istisna atmayı tercih ederim .nullstring.EmptyctorBir değişkeni diğerine atamak ve sonra ortaya çıkan değişkeni değiştirmek yerine, bunu örneğin bir satırda yapabilirsiniz.
string data = Whitespaces.Replace(_sourceString, " ");onun yerine
string data = _sourceString; // Doing some basic transformations data = Whitespaces.Replace(data, " ");Bir dizinin yalnızca tek tek öğelerine erişmeniz gerekiyorsa ve ileriye bakmanız gerekmiyorsa,
foreachbirfordöngü yerine bir döngü tercih etmelisiniz .
Tek harfli bir
lisim bana kötü geliyor.Hatanın nedenini açıklayan istisnaya bir mesaj eklemeniz gerektiğini düşünüyorum.
Varsayılan olarak, tüm beyaz boşlukları verilerden kaldırırsınız. Ancak belirteçlerin içinde gerekli olabilir. Bunu belirtmek için ek bir seçenek yapabilirsiniz.
Harika geri bildirimler için herkese teşekkürler. Kodumda FOS olarak barındırılan değişikliklerin çoğunu kabul ettimhttps://github.com/pillepalle1/dotnet-pillepalle1 daha fazla bakım alacağı yer.
Şimdilik, bölme mantığını üç statik genişletme yöntemine paketledim. Ek olarak , isteğe bağlı olarak otomatik jeton geçersiz kılma rahatlığını korumak için therubberduck tarafından önerilen sarmalayıcılar oluşturdum .
Uyguladığım öneriler
Değişken adlandırma Değişken adları
l, daha açıklayıcı adlarla değiştirilmiş gibiİstisna mesajları eklendi
Simge içeriğinin değiştirilmesi , uzantı yöntemlerinden tamamen çıkarılmış ve isteğe bağlı olarak sarmalayıcılarda kullanılabilir hale getirilmiştir
Bölgeler tamamen kaldırıldı
Makul / mümkün olduğunda var kullanma
Döngüler Tercih etmek
foreachüzerindefordöngüler ve yinelemesourceStringyerine dönüştürülmesichar[]ilkİnputString atma
ArgumentNullExceptiondönüştürme yerinenullgöreString.EmptyRFC4180'e göre CSV Bölme
Daha fazla değişiklik yapardım ama Whitespacesyeni uygulamada bazı öneriler (yani gövdeli özelliklere ilişkin ve ifade eden) geçerliliğini yitirdi.
Uygulamamış olduğum öneriler
- Özel / korumalı her şeyin alt çizgi isimlendirmesi, sadece üye ve yerel değişkenler arasında ayrım yapmaktan daha mantıklı görünüyor, çünkü eşzamanlılık sağlam veri yapılarını uygularken (
Tasksuygulandığından beri büyük bir şey haline geldi ), ilk bakışta bir yöntem eşzamanlılık kontrolleri (genel) veya değil (özel) gerçekleştirir.
Kod
Statik Tokenizatör Yöntemleri
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));
}
}
}
Soyut sarmalayıcı temel sınıfı
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;
}
}
}
Düz belirteçler için sarmalayıcı
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class PlainStringTokenizer : AStringTokenizer
{
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitPlain(Delimiter);
}
}
}
Teklif jetonlama için sarmalayıcı
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);
}
}
}
Kaçış belirteçleri için sarmalayıcı
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);
}
}
}