StringTokenizer für .NET Core
Das Aufteilen eines Strings in Token ist ein komplexeres Thema, als String.Split () uns glauben machen möchte. Es gibt mindestens drei allgemeine Richtlinien, nach denen eine Zeichenfolge interpretiert und in Token aufgeteilt werden kann.
Richtlinie 1: Entspricht String.Split ()
Zu dieser Politik gibt es nicht viel zu erwähnen. Bei einem String sund ein Trennzeichen d, Pause sin Segmente begrenzt durch d. Der Hauptnachteil hierbei ist, dass die Rekonstruktion der gewünschten Token kostspielig sein kann, wenn der Begrenzer Teil mindestens eines der Token ist.
Richtlinie 2: Escapezeichen für Sonderzeichen
Ein Zeichen wird als Escape-Zeichen deklariert e(üblicherweise als Backslash \), was dazu führt, dass das darauf folgende Zeichen seine besondere Bedeutung verliert. Eine Token-Zeichenfolge könnte dann folgendermaßen aussehen:
token_1 token_2 very\ long \ token
das wäre gleichbedeutend mit
{ "token_1", "token_2", "very long token" }
Richtlinie 3: Setzen Sie Token in Anführungszeichen
Dieser Ansatz wird beispielsweise in CSV-Dateien verwendet, die in MSExcel generiert wurden. Alles zwischen Anführungszeichen wird als Token betrachtet. Wenn Anführungszeichen "Teil des Tokens sind, werden sie verdoppelt "". Eine Token-Zeichenfolge könnte dann folgendermaßen aussehen:
token_1,token_2,"token2,5"
das wäre gleichbedeutend mit
{ "token_1", "token_2", "token2,5" }
Code
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
}
}
Antworten
Ich bin mir nicht sicher, ob dies zu einer Klasse gehört - zumindest nicht zu einer einzigen!
Machen Sie einen Schritt zurück und schauen Sie sich an, was jede "Strategie" verbindet und was sie voneinander trennt. Sie alle müssen eine Eingabezeichenfolge in eine Liste von Token umwandeln, die auf einem Variablenbegrenzer basieren. Es gibt jedoch Eigenschaften, die nur von einer der drei Optionen verwendet werden, und der Großteil der Aufteilungslogik ist für seine Strategie eindeutig.
Vorschlag 1: Drei "Standalone" -Funktionen.
Man müsste sie wirklich in eine statische Klasse einordnen oder etwas Besonderes mit Delegierten / Lambdas machen, aber letztendlich gibt es wenig zu gewinnen, wenn man eine große Klasse hat.
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 = ' ') { ... }
Wenn Sie möchten, dass die Ausgabe die Eingabeparameter kommuniziert, können Sie eine viel leichtere Klasse erstellen, die dies tut. Seine Eigenschaften wären readonly; Wenn Sie sie ändern und neu berechnen müssen, rufen Sie die Funktion einfach erneut auf. Schließlich ist es im Wesentlichen das, was Sie in Ihrer aktuellen Klasse tun!
Ein weiteres Plus: Wenn Sie eine neue Strategie für die Aufteilung entwickeln, können Sie einfach eine neue Funktion erstellen, ohne die anderen zu beeinflussen. Sie sind alle unabhängig voneinander testbar, bearbeitbar und löschbar.
Vorschlag 2: Drei konkrete Klassen, die eine abstrakte Basisklasse erweitern.
Mir gefällt, was Sie mit der _TokensEigenschaft gemacht haben: Sie können die Berechnung verschieben, bis Sie sie wirklich benötigen. Dies ist hilfreich, wenn Sie dies nicht tun . Ein Anwendungsfall, den es unterstützt (der von "Standalone" -Funktionen nicht unterstützt wird), besteht darin, z. B. das Escape-Zeichen zu ändern und das Ergebnis automatisch "ungültig zu machen".
Um dieses Verhalten beizubehalten, können Sie die allgemeinen Elemente wie folgt in eine abstrakte Basisklasse ziehen:
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;
}
}
Bemerkenswerte Änderungen:
- Die eigentliche Split-Logik fehlt. Jede Strategie bietet ihre eigene.
- Strategiespezifische Eigenschaften fehlen. Es ist keine Escape-basierte Strategie erforderlich, um eine Eigenschaft für ein Anführungszeichen zu haben, und umgekehrt.
- Anstatt direkt festzulegen
_tokens = null, sollten Eigenschaften aufgerufen werdenInvalidateResult. Dies ermöglicht_tokenses,privatedie Logik in der Basisklasse zu halten. Tokensist öffentlich und ist einIEnumerable. Dies ermöglicht den Verbrauchern die Verwendungforeach, rät jedoch von direkten Änderungen ab.
Eine Basisklasse hat jetzt genau einen Job: implementieren ComputeTokens. Wenn dazu Eigenschaften erstellt werden müssen, kann dies auf der Grundlage einer eigenen strategiespezifischen Logik erfolgen. Wenn diese Eigenschaften zuvor berechnete Token beim Ändern ungültig machen müssen, werden sie möglicherweise aufgerufen InvalidateResult.
Hier ist ein grobes Beispiel dafür, wie eine Strategie-Unterklasse aussehen würde:
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
}
}
Andere Beobachtungen
- Sie erlauben die Angabe des Trennzeichens, verdichten jedoch immer Leerzeichen. Wenn ich mich
"a,a and b,b"mit einem Trennzeichen von","trennen würde, würde ich erwarten,{"a", "a and b", "b"}zurück zu kommen - würde es aber tatsächlich bekommen{"a", "a and b", "b"}. - Wenn das Trennzeichen usw. öffentlich gelesen werden kann, warum nicht auch die Quellzeichenfolge verfügbar machen? Siehe
SourceStringmeine abstrakte Klasse oben. - Ich finde, dass die (relativ neuen) Eigenschafts-Accessoren mit Ausdruckskörper für einfache Eigenschaften rundum besser sind. Siehe oben
Countin meiner abstrakten Klasse. - Ich glaube nicht, dass es möglich ist, versehentlich
nulleine Variable als Bedingung für eine if-Anweisung zuzuweisen . Dies liegt daran, dassx = nullAuswertungen denselben Typ haben wiex, der einebool(und damit nicht nullbare) sein muss, um eine gültige Bedingung zu sein. Wenn Sie immer noch vermeiden möchtenx == null, können Sie sagenx is null. - Wie von anderen erwähnt, sollten Sie den Eigenschaften kein Präfix voranstellen
_. Es ist nicht da, um zwischen öffentlich und privat zu unterscheiden, sondern zwischen lokalen Variablen und Klassenfeldern. Persönlich verwende ich_in diesem Fall jedoch nicht einmal , sondern bevorzuge esthis.bei Bedarf. Insgesamt müssen Sie jedoch flexibel sein und sicherstellen, dass Sie alle Muster befolgen, die bereits in einem vorhandenen Team oder Projekt festgelegt wurden. - Verwenden Sie, wie bereits erwähnt,
varbeim Deklarieren von Variablen nach Möglichkeit. Jede gute IDE kann Ihnen den Typ mitteilen, wenn Sie den Mauszeiger über die Variable bewegen, und der Name sollte Ihnen auch ohne den Typ mitteilen, wofür er gedacht ist. - Vermeiden Sie in diesem Sinne Namen wie
cundl.iist in Ordnung, da es als Schleifen- / Indexvariable idiomatisch ist, aber die anderen benötigen zusätzlichen Kontext, um es zu verstehen. Quellcode-Zeichen sind billig, zahlen Sie also mitcurrentCharund für zusätzliche LesbarkeitfinishedTokens. - Sie müssen die Quelle nicht
stringin a übersetzenchar[]. Sie können bereits auf Zeichen in einemstringBy-Index zugreifen .
Sie sollten nicht
Whitespacesals Nur-Get-Eigenschaft, sondern als privatesreadonlyFeld haben und diesen regulären Ausdruck kompilieren lassen, da Sie ihn ziemlich oft verwenden.Die Verwendung regionwird als Antimuster angesehen
Verwenden Sie das Unterstrichpräfix nur für private Felder. Verwenden Sie sie nicht für Methoden oder Eigenschaften.
Wenn der Typ einer Variablen auf der rechten Seite einer Zuweisung klar ist, sollten Sie
varanstelle des konkreten Typs verwenden.Der Code wird dabei eine Menge althought
_sourceStringsein kann ,string.Emptyweil das übergebene Ctor-ArgumentdataInputkann sein ,nulloderstring.Empty. Ich würde es vorziehen, eine Ausnahme in die zu werfenctor.Anstatt eine Variable einer anderen zuzuweisen und dann die resultierende Variable zu manipulieren, können Sie dies auch in einer Zeile tun, wie z
string data = Whitespaces.Replace(_sourceString, " ");Anstatt von
string data = _sourceString; // Doing some basic transformations data = Whitespaces.Replace(data, " ");Wenn Sie nur auf einzelne Elemente eines Arrays zugreifen müssen und nicht nach vorne schauen müssen, sollten
foreachSie eineforSchleife vorziehen .
Ein
lName aus einem Buchstaben scheint mir schlecht zu sein.Ich denke, Sie sollten der Ausnahme eine Nachricht hinzufügen, die den Grund für den Fehler beschreibt.
Standardmäßig entfernen Sie alle Leerzeichen aus den Daten. Aber sie können in Token benötigt werden. Sie können eine zusätzliche Option festlegen, um dies anzugeben.
Vielen Dank an alle für das tolle Feedback. Ich habe die meisten Änderungen an meinem Code übernommen, der als FOS gehostet wirdhttps://github.com/pillepalle1/dotnet-pillepalle1 wo es weitere Wartung erhalten wird.
Im Moment habe ich die Aufteilungslogik in drei statische Erweiterungsmethoden gepackt. Zusätzlich habe ich Wrapper gebaut, wie von therubberduck vorgeschlagen, um optional den Komfort einer automatischen Token-Ungültigmachung beizubehalten
Vorschläge, die ich umgesetzt habe
Variablennamen Variablennamen, wie
lsie durch aussagekräftigere Namen ersetzt wurdenAusnahmemeldungen wurden hinzugefügt
Änderung des Token-Inhalts Wurde vollständig aus den Erweiterungsmethoden entfernt und optional in den Wrappern verfügbar gemacht
Regionen wurden vollständig entfernt
Verwenden von var, wann immer dies sinnvoll / möglich ist
Schleifen Bevorzugen
foreachSieforSchleifen und iterieren Sie über die Schleifen,sourceStringanstatt siechar[]zuerst zu konvertierenInputstring Werfen
ArgumentNullExceptionstatt konvertierennullinString.EmptyCSV-Aufteilung nach RFC4180
Ich hätte mehr Änderungen vorgenommen, aber einige Vorschläge (dh in Bezug auf Whitespacesund Ausdruck von Körpereigenschaften) sind in der neuen Implementierung überholt.
Vorschläge habe ich nicht umgesetzt
- Die Unterstreichungsbenennung für alles Private / Geschützte erscheint mir vernünftiger als nur die Unterscheidung zwischen Mitglieds- und lokalen Variablen, da es bei der Implementierung von parallelen robusten Datenstrukturen (die seitdem eine große Sache geworden
Taskssind) unglaublich wertvoll ist, auf den ersten Blick zu sehen, ob a Methode führt Parallelitätsprüfungen durch (öffentlich) oder nicht (privat).
Code
Statische Tokenizer-Methoden
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));
}
}
}
Abstrakte Wrapper-Basisklasse
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;
}
}
}
Wrapper für die einfache Tokenisierung
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class PlainStringTokenizer : AStringTokenizer
{
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitPlain(Delimiter);
}
}
}
Wrapper für die Angebots-Tokenisierung
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);
}
}
}
Wrapper für Escape-Tokenisierung
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);
}
}
}