StringTokenizer dla .NET Core
Dzielenie łańcucha na tokeny jest bardziej złożonym tematem niż String.Split () chce nam wmówić. Istnieją co najmniej trzy wspólne zasady, zgodnie z którymi ciąg może być interpretowany i dzielony na tokeny.
Zasada 1: odpowiednik String.Split ()
Nie ma wiele do wspominania o tej polityce. Biorąc pod uwagę ciąg si separator d, podziel się sna segmenty rozdzielone d. Główną wadą jest to, że jeśli separator jest częścią co najmniej jednego z tokenów, odtworzenie pożądanych tokenów może być kosztowne.
Zasada 2: Unikaj znaków specjalnych
Znak jest deklarowany jako znak ucieczki e (zwykle ukośnik odwrotny \), w wyniku czego znak następujący po nim traci swoje specjalne znaczenie. Łańcuch tokena może wtedy wyglądać następująco:
token_1 token_2 very\ long \ token
co byłoby równoważne z
{ "token_1", "token_2", "very long token" }
Zasada 3: Umieść tokeny w cudzysłowie
Takie podejście jest używane na przykład w plikach CSV generowanych w programie MSExcel. Wszystko w cudzysłowie jest traktowane jako symbol. Jeśli cudzysłowy "są częścią tokena, są podwajane "". Łańcuch tokena może wtedy wyglądać następująco:
token_1,token_2,"token2,5"
co byłoby równoważne z
{ "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
}
}
Odpowiedzi
Nie jestem pewien, czy to należy do klasy - przynajmniej nie do jednej!
Cofnij się o krok i zobacz, co łączy, a co oddziela każdą „strategię”. Wszystkie muszą przekształcić ciąg wejściowy w listę tokenów na podstawie ogranicznika zmiennej. Istnieją jednak właściwości, które są używane tylko przez jedną z trzech opcji, a większość logiki podziału jest unikalna dla jej strategii.
Sugestia 1: Trzy „samodzielne” funkcje.
Naprawdę musiałbyś umieścić je w klasie statycznej lub zrobić coś specjalnego z delegatami / lambdami, ale ostatecznie niewiele można zyskać z posiadania jednej dużej klasy.
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 = ' ') { ... }
Jeśli chcesz, aby dane wyjściowe przekazywały parametry wejściowe, możesz stworzyć znacznie lżejszą klasę, która to robi. Jego właściwości byłyby readonly; jeśli chcesz je zmienić i ponownie obliczyć, po prostu ponownie wywołaj funkcję. W końcu to właśnie robisz w swojej obecnej klasie!
Kolejny plus: jeśli i kiedy wymyślisz nową strategię podziału, możesz po prostu utworzyć nową funkcję bez wpływu na inne. Wszystkie są niezależnie testowalne, edytowalne i usuwalne.
Sugestia 2: Trzy konkretne klasy, które rozszerzają abstrakcyjną klasę bazową.
Podoba mi się to, co zrobiłeś z tą _Tokenswłaściwością: pozwala odroczyć obliczenia, dopóki naprawdę ich nie potrzebujesz, co jest pomocne w przypadkach, gdy tego nie zrobisz. Ponadto jednym z przypadków użycia, które obsługuje (który nie jest obsługiwany przez funkcje „samodzielne”) jest zmiana np. Znaku ucieczki i automatyczne „unieważnienie” wyniku.
Aby zachować to zachowanie, możesz przeciągnąć wspólne elementy do abstrakcyjnej klasy bazowej, jak poniżej:
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;
}
}
Istotne zmiany:
- Brak rzeczywistej logiki podziału. Każda strategia zapewni własną.
- Brak właściwości specyficznych dla strategii. Nie ma potrzeby, aby strategia oparta na ucieczce miała właściwość dla postaci cytowanej i odwrotnie.
- Zamiast bezpośredniego ustawiania
_tokens = nullwłaściwości powinny wywoływaćInvalidateResult. Pozwala to_tokensnaprivatezachowanie logiki zawartej w klasie bazowej. Tokensjest publiczny i jestIEnumerable. Umożliwia to konsumentom korzystanieforeach, ale zniechęca do bezpośredniej modyfikacji.
Klasa bazowa ma teraz dokładnie jedno zadanie: implementację ComputeTokens. Jeśli w tym celu musi utworzyć właściwości, może to zrobić w oparciu o własną logikę specyficzną dla strategii. Jeśli te właściwości muszą unieważnić wcześniej obliczone tokeny po zmianie, mogą wywołać InvalidateResult.
Oto przybliżony przykład tego, jak wyglądałaby podklasa strategii:
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
}
}
Inne obserwacje
- Zezwalasz na określenie separatora, ale zawsze zagęszczasz spacje. Gdybym podzielił się
"a,a and b,b"z ogranicznikiem",", spodziewałbym się{"a", "a and b", "b"}powrotu - ale faktycznie dostałbym{"a", "a and b", "b"}. - Jeśli separator itp. Można odczytać publicznie, dlaczego nie ujawnić również ciągu źródłowego? Zobacz
SourceStringmoją klasę abstrakcyjną powyżej. - Uważam, że (stosunkowo nowe) metody dostępu do właściwości z wyrażeniem są ogólnie lepsze dla prostych właściwości. Zobacz
Countw mojej klasie abstrakcyjnej powyżej. - Nie sądzę, aby możliwe było przypadkowe przypisanie
nullzmiennej jako warunku instrukcji if. Dzieje się tak, ponieważx = nullszacuje się, że jest tego samego typux, co, który musi być abool(a zatem nie dopuszcza wartości null), aby był prawidłowym warunkiem. Jeśli nadal chcesz tego uniknąćx == null, możesz powiedziećx is null. - Jak wspominali inni, nie należy poprzedzać właściwości przedrostkiem
_. Nie służy do rozróżniania między publicznym a prywatnym, ale między zmiennymi lokalnymi a polami klas. Osobiście jednak nawet nie używam_w tym przypadku, ale zamiast tego wolę wthis.razie potrzeby. Ale ogólnie będziesz musiał być elastyczny w tym zakresie i upewnić się, że postępujesz zgodnie z dowolnym wzorcem, który jest już ustalony w istniejącym zespole lub projekcie. - Jak wspominali inni, używaj
varprzy deklarowaniu zmiennych, kiedy tylko jest to możliwe. Każde dobre IDE będzie w stanie powiedzieć ci typ po najechaniu kursorem na zmienną, a jego nazwa powinna ci powiedzieć, do czego służy, nawet bez typu. - W tej notatce unikaj nazw takich jak
cil.ijest w porządku, ponieważ jest idiomatyczny jako zmienna pętli / indeksu, ale inne wymagają dodatkowego kontekstu do zrozumienia. Znaki kodu źródłowego są tanie, więc zapłać za dodatkową czytelność, używająccurrentCharifinishedTokens. - Nie musisz tłumaczyć źródła
stringnachar[]; możesz już uzyskać dostęp do znaków wstringindeksie.
Nie powinieneś mieć
Whitespaceswłaściwości tylko do pobierania, ale jakoreadonlypola prywatnego i powinieneś skompilować to wyrażenie regularne, ponieważ używasz go dość często.Używanie regionjest traktowane jako anty-wzorzec
Przedrostki używaj tylko w przypadku pól prywatnych. Nie używaj ich jako metod lub właściwości.
Jeśli typ zmiennej jest jasny po prawej stronie przypisania, należy użyć
varzamiast konkretnego typu.Kod robi wiele, chociaż
_sourceStringmoże byćstring.Emptyspowodowane tym, że przekazany argument ctordataInputmoże byćnulllubstring.Empty. Wolałbym wrzucić wyjątek wctor.Zamiast przypisywać zmienną do innej, a następnie manipulować zmienną wynikową, możesz to zrobić w jednej linii, jak np
string data = Whitespaces.Replace(_sourceString, " ");zamiast
string data = _sourceString; // Doing some basic transformations data = Whitespaces.Replace(data, " ");Jeśli potrzebujesz tylko dostępu do pojedynczych elementów tablicy i nie musisz patrzeć w przyszłość, powinieneś preferować pętlę
foreachover afor.
lImię jednoliterowe wydaje mi się złe.Myślę, że do wyjątku należy dodać wiadomość opisującą przyczynę błędu.
Domyślnie wszystkie spacje są usuwane z danych. Ale mogą być potrzebne w tokenach. Możesz skorzystać z dodatkowej opcji, aby to określić.
Dziękuję wszystkim za wspaniałe opinie. Zaadoptowałem większość zmian w moim kodzie, który jest hostowany jako FOShttps://github.com/pillepalle1/dotnet-pillepalle1 gdzie otrzyma dalsze utrzymanie.
Na razie spakowałem logikę podziału na trzy statyczne metody rozszerzające. Dodatkowo, zgodnie z sugestią therubberduck, zbudowałem otoki, aby opcjonalnie zachować komfort automatycznego unieważniania tokena
Sugestie, które wdrożyłem
Nazewnictwo zmiennych Nazwy zmiennych, takie jak
lzostały zastąpione nazwami bardziej opisowymiDodano komunikaty o wyjątkach
Modyfikacja zawartości tokena została całkowicie usunięta z metod rozszerzających i opcjonalnie dostępna w opakowaniach
Regiony zostały całkowicie usunięte
Używanie var, gdy jest to uzasadnione / możliwe
Pętle preferujących
foreachnaforpętli i iteracji nadsourceStringzamiast przekształcając gochar[]najpierwInputstring Rzucanie
ArgumentNullExceptionzamiast konwertowanianullnaString.EmptyDzielenie CSV zgodnie z RFC4180
Przyjąłbym więcej zmian, ale niektóre sugestie (np. Dotyczące Whitespaceswłaściwości treści i wyrażeń) stały się nieaktualne w nowej implementacji.
Sugestie nie wdrożyłem
- Nazewnictwo podkreślenia dla wszystkiego, co prywatne / chronione, wydaje mi się bardziej rozsądne niż rozróżnienie między zmiennymi składowymi i lokalnymi, ponieważ podczas implementowania solidnych struktur danych współbieżności (które stały się wielką rzeczą od czasu
Tasksich zaimplementowania) niezwykle cenne jest sprawdzenie na pierwszy rzut oka, czy Metoda wykonuje sprawdzenia współbieżności (publiczne) lub nie (prywatne).
Kod
Statyczne metody tokenizatora
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));
}
}
}
Klasa bazowa abstrakcyjnego opakowania
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;
}
}
}
Opakowanie do zwykłej tokenizacji
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class PlainStringTokenizer : AStringTokenizer
{
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitPlain(Delimiter);
}
}
}
Opakowanie do tokenizacji ofert
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);
}
}
}
Opakowanie do tokenizacji ucieczki
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);
}
}
}