StringTokenizer cho .NET Core
Tách một chuỗi trong mã thông báo là một chủ đề phức tạp hơn String.Split () muốn khiến chúng ta tin tưởng. Có ít nhất ba chính sách phổ biến mà theo đó, một chuỗi có thể được diễn giải và chia thành các mã thông báo.
Chính sách 1: Tương đương với String.Split ()
Không có nhiều điều để đề cập về chính sách này. Cho một chuỗi svà một dấu phân cách d, ngắt sthành các đoạn được phân cách bằng d. Hạn chế chính ở đây là nếu dấu phân cách là một phần của ít nhất một trong các mã thông báo, việc tạo lại các mã thông báo mong muốn có thể tốn kém.
Chính sách 2: Thoát các ký tự đặc biệt
Một ký tự được khai báo là ký tự thoát e (thường là dấu gạch chéo ngược \) dẫn đến ký tự theo sau nó mất đi ý nghĩa đặc biệt. Một chuỗi mã thông báo sau đó có thể trông như thế này:
token_1 token_2 very\ long \ token
sẽ tương đương với
{ "token_1", "token_2", "very long token" }
Chính sách 3: Đặt mã thông báo trong dấu ngoặc kép
Ví dụ, phương pháp này được sử dụng trong các tệp CSV được tạo trong MSExcel. Mọi thứ giữa dấu ngoặc kép được coi như một mã thông báo. Nếu dấu ngoặc kép "là một phần của mã thông báo, chúng sẽ được nhân đôi "". Một chuỗi mã thông báo sau đó có thể trông như thế này:
token_1,token_2,"token2,5"
sẽ tương đương với
{ "token_1", "token_2", "token2,5" }
Mã
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
}
}
Trả lời
Tôi không chắc cái này thuộc về một lớp - ít nhất không phải là lớp nào!
Lùi lại một bước và xem điều gì hợp nhất và điều gì tách biệt từng "chiến lược". Tất cả chúng đều cần chuyển đổi một chuỗi đầu vào thành một danh sách các mã thông báo dựa trên một dấu phân cách biến. Tuy nhiên, có những thuộc tính chỉ được sử dụng bởi một trong ba tùy chọn và phần lớn logic phân tách là duy nhất cho chiến lược của nó.
Gợi ý 1: Ba chức năng "độc lập".
Bạn thực sự sẽ phải đặt chúng vào một lớp tĩnh hoặc làm điều gì đó đặc biệt với các ủy viên / lambdas, nhưng cuối cùng thì chẳng thu được gì từ việc có một lớp lớn.
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 = ' ') { ... }
Nếu bạn muốn đầu ra giao tiếp các tham số đầu vào, bạn có thể tạo một lớp nhẹ hơn nhiều để làm như vậy. Thuộc tính của nó sẽ là readonly; nếu bạn cần thay đổi chúng và tính toán lại, chỉ cần gọi lại hàm. Xét cho cùng, đó thực chất là những gì bạn đang làm ở bên trong lớp học hiện tại của mình!
Một điểm cộng khác: nếu và khi bạn đưa ra một chiến lược mới để tách, bạn có thể chỉ cần tạo một hàm mới mà không ảnh hưởng đến các hàm khác. Tất cả chúng đều có thể kiểm tra độc lập, có thể chỉnh sửa và có thể xóa.
Gợi ý 2: Ba lớp cụ thể mở rộng một lớp cơ sở trừu tượng.
Tôi thích những gì bạn đã làm với thuộc _Tokenstính: nó cho phép bạn trì hoãn việc tính toán cho đến khi bạn thực sự cần, điều này rất hữu ích trong những trường hợp bạn không làm như vậy. Ngoài ra, một trường hợp sử dụng mà nó hỗ trợ (không được hỗ trợ bởi các chức năng "độc lập") là thay đổi, ví dụ: ký tự thoát và kết quả tự động bị "vô hiệu hóa".
Để duy trì hành vi đó, bạn có thể kéo các phần tử chung vào một lớp cơ sở trừu tượng, như sau:
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;
}
}
Những thay đổi đáng chú ý:
- Không có logic phân chia thực tế. Mỗi chiến lược sẽ cung cấp riêng của mình.
- Các thuộc tính dành riêng cho chiến lược không có. Không cần chiến lược dựa trên lối thoát để có thuộc tính cho ký tự trích dẫn và ngược lại.
- Thay vì thiết lập trực tiếp
_tokens = null, các thuộc tính nên gọiInvalidateResult. Điều này cho phép_tokensđược thực hiệnprivateđể giữ logic được chứa trong lớp cơ sở. Tokenslà công khai, và là mộtIEnumerable. Điều này cho phép người tiêu dùng sử dụngforeach, nhưng không khuyến khích sửa đổi trực tiếp.
Một lớp cơ sở bây giờ có chính xác một công việc: thực hiện ComputeTokens. Nếu nó cần tạo thuộc tính để làm như vậy, nó có thể làm như vậy, dựa trên logic chiến lược cụ thể của riêng nó. Nếu các thuộc tính đó cần làm mất hiệu lực các mã thông báo đã tính toán trước đó khi chúng thay đổi, chúng có thể gọi InvalidateResult.
Dưới đây là một ví dụ sơ bộ về lớp con chiến lược sẽ trông như thế nào:
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
}
}
Các quan sát khác
- Bạn cho phép chỉ định dấu phân cách, nhưng bạn luôn cô đặc khoảng trắng. Nếu tôi phân tách
"a,a and b,b"bằng dấu phân cách",", tôi sẽ mong đợi lấy{"a", "a and b", "b"}lại - nhưng thực tế sẽ nhận được{"a", "a and b", "b"}. - Nếu dấu phân cách, v.v., có thể được đọc công khai, tại sao không hiển thị cả chuỗi nguồn? Xem
SourceStringlớp trừu tượng của tôi ở trên. - Tôi thấy các trình truy cập thuộc tính dạng biểu thức (tương đối mới) sẽ tốt hơn cho các thuộc tính đơn giản. Xem
Counttrong lớp trừu tượng của tôi ở trên. - Tôi không nghĩ có thể vô tình gán
nullcho một biến làm điều kiện của câu lệnh if. Điều này là dox = nullđánh giá phải cùng kiểu vớix, cần phải làbool(và do đó, không thể null) để là một điều kiện hợp lệ. Nếu bạn vẫn muốn tránhx == null, bạn có thể nóix is null. - Như những người khác đã đề cập, bạn không nên đặt tiền tố thuộc tính với
_. Nó không ở đó để phân biệt giữa công khai và riêng tư, mà là giữa các biến cục bộ và các trường lớp. Cá nhân tôi, mặc dù, tôi thậm chí không sử dụng_trong trường hợp đó, nhưng thay vào đó thích hơnthis.nếu cần. Nhưng nhìn chung, bạn sẽ cần phải linh hoạt về điều đó và đảm bảo tuân theo bất kỳ khuôn mẫu nào đã được thiết lập trong một nhóm hoặc dự án hiện có. - Cũng như những người khác đã đề cập, sử dụng
varkhi khai báo các biến bất cứ khi nào có thể. Bất kỳ IDE nào tốt sẽ có thể cho bạn biết loại khi bạn di chuột qua biến và tên của nó sẽ cho bạn biết nó dùng để làm gì ngay cả khi không có loại. - Lưu ý, hãy tránh những cái tên như
cvàl.icũng tốt vì nó có tính thành ngữ như một biến vòng lặp / chỉ mục, nhưng những biến số khác yêu cầu thêm ngữ cảnh để hiểu. Các ký tự mã nguồn rẻ, vì vậy hãy trả tiền để có thêm một số khả năng đọc bằng cách sử dụngcurrentCharvàfinishedTokens. - Bạn không cần phải dịch nguồn
stringthành achar[]; bạn đã có thể truy cập các ký tự trong mộtstringchỉ mục.
Bạn không nên có
Whitespacesnhư một thuộc tính chỉ nhận mà là mộtreadonlytrường riêng tư và bạn nên biên dịch regex đó vì bạn đang sử dụng nó khá thường xuyên.Sử dụng regionđược coi là một phản vật chất
Chỉ sử dụng tiền tố gạch dưới cho các trường riêng tư. Không sử dụng chúng cho các phương thức hoặc thuộc tính.
Nếu loại biến rõ ràng ở phía bên phải của nhiệm vụ, bạn nên sử dụng
varthay vì loại cụ thể.Mã đang hoạt động rất nhiều
_sourceStringcó thể làstring.Emptydo đối số ctor được truyền vàodataInputcó thể lànullhoặcstring.Empty. Tôi muốn ném một ngoại lệ vàoctor.Thay vì gán một biến cho một biến khác và sau đó thao tác với biến kết quả, bạn chỉ có thể làm điều đó trên một dòng như vd:
string data = Whitespaces.Replace(_sourceString, " ");thay vì
string data = _sourceString; // Doing some basic transformations data = Whitespaces.Replace(data, " ");Nếu bạn chỉ cần truy cập mục duy nhất của một mảng và không cần phải nhìn về phía trước, bạn nên chọn một
foreachtrong mộtforvòng lặp.
Một cái
ltên chỉ có một chữ cái có vẻ xấu đối với tôi.Tôi nghĩ bạn nên thêm thông báo vào ngoại lệ mô tả lý do gây ra lỗi.
Theo mặc định, bạn xóa tất cả khoảng trắng khỏi dữ liệu. Nhưng chúng có thể cần thiết bên trong các mã thông báo. Bạn có thể thực hiện một tùy chọn bổ sung để chỉ định điều này.
Cảm ơn mọi người vì những phản hồi tuyệt vời. Tôi đã áp dụng hầu hết các thay đổi đối với mã của mình đang được lưu trữ dưới dạng FOS trênhttps://github.com/pillepalle1/dotnet-pillepalle1 nơi nó sẽ được bảo trì thêm.
Hiện tại, tôi đã đóng gói logic phân tách thành ba phương thức mở rộng tĩnh. Ngoài ra, tôi đã xây dựng các trình bao bọc theo đề xuất của therubberduck để có thể tùy chọn giữ sự thoải mái của việc vô hiệu mã thông báo tự động
Đề xuất tôi đã thực hiện
Đặt tên biến Tên biến chẳng hạn như
lđã được thay thế bằng tên mô tả hơnThông báo ngoại lệ đã được thêm vào
Sửa đổi nội dung mã thông báo Đã bị loại bỏ hoàn toàn khỏi các phương thức mở rộng và có sẵn tùy chọn trong trình bao bọc
Các khu vực đã bị xóa hoàn toàn
Sử dụng var bất cứ khi nào hợp lý / có thể
Vòng lặp Thích
foreachhơnforcác vòng lặp và lặp lạisourceStringthay vì chuyển đổi nó thànhchar[]đầu tiênNém chuỗi nhập
ArgumentNullExceptionthay vì chuyển đổinullthànhString.EmptyTách CSV theo RFC4180
Tôi sẽ áp dụng nhiều thay đổi hơn nhưng một số đề xuất (tức là Whitespacescác thuộc tính liên quan và biểu hiện) đã trở nên lỗi thời trong triển khai mới.
Các đề xuất tôi chưa thực hiện
- Việc đặt tên gạch dưới cho mọi thứ riêng tư / được bảo vệ có vẻ hợp lý hơn đối với tôi hơn là chỉ phân biệt giữa biến thành viên và biến cục bộ vì khi triển khai các cấu trúc dữ liệu mạnh mẽ đồng thời (đã trở thành một điều quan trọng kể từ khi
Tasksđược triển khai). phương thức thực hiện kiểm tra đồng thời (công khai) hay không (riêng tư).
Mã
Phương pháp Tokenizer tĩnh
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));
}
}
}
Lớp cơ sở trình bao bọc trừu tượng
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 để mã hóa đơn giản
using System.Collections.Immutable;
namespace pillepalle1.Text
{
public class PlainStringTokenizer : AStringTokenizer
{
protected override ImmutableList<string> Tokenize()
{
return SourceString.SplitPlain(Delimiter);
}
}
}
Wrapper để mã hóa báo giá
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 để mã hóa thoát
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);
}
}
}