forked from EvilBeaver/OneScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStringOperations.cs
More file actions
314 lines (270 loc) · 14.7 KB
/
StringOperations.cs
File metadata and controls
314 lines (270 loc) · 14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*----------------------------------------------------------
This Source Code Form is subject to the terms of the
Mozilla Public License, v.2.0. If a copy of the MPL
was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/
using OneScript.Commons;
using OneScript.Contexts;
using OneScript.Contexts.Enums;
using OneScript.Exceptions;
using OneScript.Execution;
using OneScript.Localization;
using OneScript.StandardLibrary.Collections;
using ScriptEngine.Machine;
using ScriptEngine.Machine.Contexts;
using System;
using System.Linq;
namespace OneScript.StandardLibrary
{
[GlobalContext(Category = "Операции со строками")]
public class StringOperations : GlobalContextBase<StringOperations>
{
private static readonly System.Text.RegularExpressions.Regex _templateRe
= new System.Text.RegularExpressions.Regex(@"(%%)|%(\d+)|%\((\d+)\)|%",
System.Text.RegularExpressions.RegexOptions.Compiled);
/// <summary>
/// Получает строку на языке, заданном во втором параметре (коды языков в соответствии с ISO 639-1)
/// или на текущем языке системы.
/// </summary>
/// <param name="src">Строка на нескольких языках</param>
/// <param name="lang">Код языка (если не указан, возвращает вариант для текущего языка системы,
/// если вариант не найден, то возвращает вариант для английского языка,
/// если не задан вариант для английского языка, то возвращает первый вариант из списка)</param>
[ContextMethod("НСтр", "NStr")]
public string NStr(string src, string lang = null)
{
return Locale.NStr(src, lang);
}
/// <summary>
/// Определяет, что строка начинается с указанной подстроки.
/// </summary>
/// <param name="inputString">Строка, начало которой проверяется на совпадение с подстрокой поиска.</param>
/// <param name="searchString">Строка, содержащая предполагаемое начало строки.
/// В случае, если переданное значение является пустой строкой, генерируется исключительная ситуация.</param>
[ContextMethod("СтрНачинаетсяС", "StrStartsWith")]
public bool StrStartsWith(string inputString, string searchString)
{
bool result = false;
if (!string.IsNullOrEmpty(inputString))
{
if (!string.IsNullOrEmpty(searchString))
{
result = inputString.StartsWith(searchString);
}
else throw StringOpException.StrStartsWith();
}
return result;
}
/// <summary>
/// Определяет, заканчивается ли строка указанной подстрокой.
/// </summary>
/// <param name="inputString">Строка, окончание которой проверяется на совпадение с подстрокой поиска.</param>
/// <param name="searchString">Строка, содержащая предполагаемое окончание строки.
/// В случае, если переданное значение является пустой строкой, генерируется исключительная ситуация.</param>
[ContextMethod("СтрЗаканчиваетсяНа", "StrEndsWith")]
public bool StrEndsWith(string inputString, string searchString)
{
bool result = false;
if (!string.IsNullOrEmpty(inputString))
{
if (!string.IsNullOrEmpty(searchString))
{
result = inputString.EndsWith(searchString);
}
else throw StringOpException.StrEndsWith();
}
return result;
}
/// <summary>
/// Разделяет строку на части по указанным символам-разделителям.
/// </summary>
/// <param name="inputString">Разделяемая строка.</param>
/// <param name="stringDelimiter">Строка символов, каждый из которых является индивидуальным разделителем.</param>
/// <param name="includeEmpty">Указывает необходимость включать в результат пустые строки,
/// которые могут образоваться в результате разделения исходной строки. Значение по умолчанию: Истина. </param>
[ContextMethod("СтрРазделить", "StrSplit")]
public ArrayImpl StrSplit(string inputString, string stringDelimiter, bool? includeEmpty = true)
{
string[] arrParsed;
if (includeEmpty == null)
includeEmpty = true;
if (!string.IsNullOrEmpty(inputString))
{
arrParsed = inputString.Split(stringDelimiter?.ToCharArray(),
(bool)includeEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
}
else
{
arrParsed = (bool)includeEmpty ? new string[] { string.Empty } : Array.Empty<string>();
}
return new ArrayImpl(arrParsed.Select(x => ValueFactory.Create(x)));
}
/// <summary>
/// Соединяет массив переданных строк в одну строку с указанным разделителем
/// </summary>
/// <param name="input">Массив - соединяемые строки</param>
/// <param name="delimiter">Разделитель. Если не указан, строки объединяются слитно</param>
[ContextMethod("СтрСоединить", "StrConcat")]
public string StrConcat(IBslProcess process, ArrayImpl input, string delimiter = null)
{
var strings = input.Select(x => x.AsString(process));
return String.Join(delimiter, strings);
}
/// <summary>
/// Сравнивает строки без учета регистра.
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns>-1 первая строка больше, 1 - вторая строка больше. 0 - строки равны</returns>
[ContextMethod("СтрСравнить", "StrCompare")]
public int StrCompare(string first, string second)
{
return String.Compare(first, second, true);
}
/// <summary>
/// Находит вхождение искомой строки как подстроки в исходной строке
/// </summary>
/// <param name="haystack">Строка, в которой ищем</param>
/// <param name="needle">Строка, которую надо найти</param>
/// <param name="direction">значение перечисления НаправлениеПоиска (с конца/с начала)</param>
/// <param name="startPos">Начальная позиция, с которой начинать поиск</param>
/// <param name="occurance">Указывает номер вхождения искомой подстроки в исходной строке</param>
/// <returns>Позицию искомой строки в исходной строке. Возвращает 0, если подстрока не найдена.</returns>
[ContextMethod("СтрНайти", "StrFind")]
public int StrFind(string haystack, string needle, SearchDirection direction = SearchDirection.FromBegin, int startPos = 0, int occurance = 1)
{
if (needle == null || needle.Length == 0)
return 1;
int len = haystack?.Length ?? 0;
if (len == 0)
return 0;
bool fromBegin = direction == SearchDirection.FromBegin;
if (startPos == 0)
{
startPos = fromBegin ? 1 : len;
}
else if (startPos < 1 || startPos > len)
throw RuntimeException.InvalidNthArgumentValue(4);
if (occurance < 0)
return 0;
else if (occurance == 0)
throw RuntimeException.InvalidNthArgumentValue(5);
int startIndex = startPos - 1;
int foundTimes = 0;
int index = len + 1;
if(fromBegin)
{
while(foundTimes < occurance && index >= 0)
{
index = haystack.IndexOf(needle, startIndex, StringComparison.Ordinal);
if (index >= 0)
{
startIndex = index + 1;
foundTimes++;
}
if (startIndex >= len)
break;
}
}
else
{
while(foundTimes < occurance && index >= 0)
{
index = haystack.LastIndexOf(needle, startIndex, StringComparison.Ordinal);
if (index >= 0)
{
startIndex = index - 1;
foundTimes++;
}
if (startIndex < 0)
break;
}
}
return foundTimes == occurance ? index + 1 : 0;
}
/// <summary>
/// Подставляет параметры в строку по номеру
/// </summary>
/// <param name="template">Шаблон: строка, содержащая маркеры подстановки вида %N</param>
/// <param name="p1-p10">Параметры, строковые представления которых должны быть подставлены в шаблон</param>
/// <returns>Строка шаблона с подставленными параметрами</returns>
[ContextMethod("СтрШаблон", "StrTemplate")]
public string StrTemplate(string template,
string p1=null, string p2=null, string p3=null, string p4=null, string p5=null,
string p6=null, string p7=null, string p8=null, string p9=null, string p10=null)
{
var srcFormat = template ?? "";
var arguments = new [] { p10,p9,p8,p7,p6,p5,p4,p3,p2,p1 };
int passedArgsCount = arguments
.SkipWhile(x => x == null)
.Count();
int maxNumber = 0;
var result = _templateRe.Replace(srcFormat, (m) =>
{
if (m.Groups[1].Success)
return "%";
if(m.Groups[2].Success || m.Groups[3].Success)
{
var number = int.Parse(m.Groups[2].Success ? m.Groups[2].Value : m.Groups[3].Value);
if (number < 1 || number > 10)
throw StringOpException.TemplateSubst(m.Index + 2, number);
//FIXME: отключено, т.к. платформа игнорирует ошибку с недостаточным числом параметров
//if (number > passedArgsCount)
// throw RuntimeException.TooFewArgumentsPassed();
if (number > maxNumber)
maxNumber = number;
return arguments[10-number] ?? "";
}
throw StringOpException.TemplateSyntax(m.Index + 2);
});
if (passedArgsCount > maxNumber)
throw RuntimeException.TooManyArgumentsPassed();
return result;
}
public static IAttachableContext CreateInstance()
{
return new StringOperations();
}
}
[EnumerationType("НаправлениеПоиска", "SearchDirection")]
public enum SearchDirection
{
[EnumValue("СНачала", "FromBegin")]
FromBegin,
[EnumValue("СКонца", "FromEnd")]
FromEnd
}
public class StringOpException : RuntimeException
{
public StringOpException(BilingualString message, Exception innerException) : base(message,
innerException)
{}
public StringOpException(BilingualString message) : base(message)
{}
public static StringOpException StrStartsWith()
{
return new StringOpException(new BilingualString(
"Ошибка при вызове метода контекста (СтрНачинаетсяС): Недопустимое значение параметра номер '2'",
"Error calling context method (StrStartsWith): Invalid parameter number '2' value"));
}
public static StringOpException StrEndsWith()
{
return new StringOpException(new BilingualString(
"Ошибка при вызове метода контекста (СтрЗаканчиваетсяНа): Недопустимое значение параметра номер '2'",
"Error calling context method (StrEndsWith): Invalid parameter number '2' value"));
}
public static StringOpException TemplateSyntax(int pos)
{
return new StringOpException(new BilingualString(
$"Ошибка синтаксиса шаблона в позиции {pos}",
$"Template syntax error at position {pos}"));
}
public static StringOpException TemplateSubst(int pos, int num)
{
return new StringOpException(new BilingualString(
$"Ошибка синтаксиса шаблона в позиции {pos}. Недопустимый номер подстановки: '{num}'",
$"Template syntax error at position {pos}. Invalid substitution number: '{num}'"));
}
}
}