Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 84 additions & 39 deletions src/OneScript.StandardLibrary/StringOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,35 @@ This Source Code Form is subject to the terms of the
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using System;
using System.Linq;
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>
/// если вариант не найден, то возвращает вариант для английского языка,
/// если не задан вариант для английского языка, то возвращает первый вариант из списка)</param>
[ContextMethod("НСтр", "NStr")]
public string NStr(string src, string lang = null)
{
Expand All @@ -39,19 +44,20 @@ public string NStr(string src, string lang = null)
/// Определяет, что строка начинается с указанной подстроки.
/// </summary>
/// <param name="inputString">Строка, начало которой проверяется на совпадение с подстрокой поиска.</param>
/// <param name="searchString">Строка, содержащая предполагаемое начало строки. В случае если переданное значение является пустой строкой генерируется исключительная ситуация.</param>
/// <param name="searchString">Строка, содержащая предполагаемое начало строки.
/// В случае, если переданное значение является пустой строкой, генерируется исключительная ситуация.</param>
[ContextMethod("СтрНачинаетсяС", "StrStartsWith")]
public bool StrStartsWith(string inputString, string searchString)
{
bool result = false;

if(!string.IsNullOrEmpty(inputString))
if (!string.IsNullOrEmpty(inputString))
{
if (!string.IsNullOrEmpty(searchString))
{
result = inputString.StartsWith(searchString);
}
else throw new RuntimeException("Ошибка при вызове метода контекста (СтрНачинаетсяС): Недопустимое значение параметра (параметр номер '2')");
else throw StringOpException.StrStartsWith();
}

return result;
Expand All @@ -61,19 +67,20 @@ public bool StrStartsWith(string inputString, string searchString)
/// Определяет, заканчивается ли строка указанной подстрокой.
/// </summary>
/// <param name="inputString">Строка, окончание которой проверяется на совпадение с подстрокой поиска.</param>
/// <param name="searchString">Строка, содержащая предполагаемое окончание строки. В случае если переданное значение является пустой строкой генерируется исключительная ситуация.</param>
/// <param name="searchString">Строка, содержащая предполагаемое окончание строки.
/// В случае, если переданное значение является пустой строкой, генерируется исключительная ситуация.</param>
[ContextMethod("СтрЗаканчиваетсяНа", "StrEndsWith")]
public bool StrEndsWith(string inputString, string searchString)
{
bool result = false;

if(!string.IsNullOrEmpty(inputString))
if (!string.IsNullOrEmpty(inputString))
{
if (!string.IsNullOrEmpty(searchString))
{
result = inputString.EndsWith(searchString);
}
else throw new RuntimeException("Ошибка при вызове метода контекста (СтрЗаканчиваетсяНа): Недопустимое значение параметра (параметр номер '2')");
else throw StringOpException.StrEndsWith();
}

return result;
Expand All @@ -84,22 +91,25 @@ public bool StrEndsWith(string inputString, string searchString)
/// </summary>
/// <param name="inputString">Разделяемая строка.</param>
/// <param name="stringDelimiter">Строка символов, каждый из которых является индивидуальным разделителем.</param>
/// <param name="includeEmpty">Указывает необходимость включать в результат пустые строки, которые могут образоваться в результате разделения исходной строки. Значение по умолчанию: Истина. </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))
includeEmpty = true;
if (!string.IsNullOrEmpty(inputString))
{
arrParsed = inputString.Split(stringDelimiter?.ToCharArray(), (bool) includeEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
arrParsed = inputString.Split(stringDelimiter?.ToCharArray(),
(bool)includeEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
}
else
{
arrParsed = (bool) includeEmpty ? new string[] { string.Empty } : new string[0];
arrParsed = (bool)includeEmpty ? new string[] { string.Empty } : Array.Empty<string>();
}

return new ArrayImpl(arrParsed.Select(x => ValueFactory.Create(x)));
}

Expand All @@ -111,8 +121,8 @@ public ArrayImpl StrSplit(string inputString, string stringDelimiter, bool? incl
[ContextMethod("СтрСоединить", "StrConcat")]
public string StrConcat(IBslProcess process, ArrayImpl input, string delimiter = null)
{
var strings = input.Select(x => x.AsString(process));
var strings = input.Select(x => x.AsString(process));
return String.Join(delimiter, strings);
}

Expand All @@ -138,24 +148,28 @@ public int StrCompare(string first, string second)
/// <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 = 0)
public int StrFind(string haystack, string needle, SearchDirection direction = SearchDirection.FromBegin, int startPos = 0, int occurance = 1)
{
int len = haystack.Length;
if (len == 0 || needle.Length == 0)
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)
if (startPos == 0)
{
startPos = fromBegin ? 1 : len;
}
else if (startPos < 1 || startPos > len)
throw RuntimeException.InvalidNthArgumentValue(4);

if (startPos < 1 || startPos > len)
throw RuntimeException.InvalidArgumentValue();

if (occurance == 0)
occurance = 1;
if (occurance < 0)
return 0;
else if (occurance == 0)
throw RuntimeException.InvalidNthArgumentValue(5);

int startIndex = startPos - 1;
int foundTimes = 0;
Expand All @@ -174,7 +188,6 @@ public int StrFind(string haystack, string needle, SearchDirection direction = S
if (startIndex >= len)
break;
}

}
else
{
Expand All @@ -189,13 +202,9 @@ public int StrFind(string haystack, string needle, SearchDirection direction = S
if (startIndex < 0)
break;
}

}

if (foundTimes == occurance)
return index + 1;
else
return 0;
return foundTimes == occurance ? index + 1 : 0;
}

/// <summary>
Expand All @@ -216,9 +225,8 @@ public string StrTemplate(string template,
.SkipWhile(x => x == null)
.Count();

var re = new System.Text.RegularExpressions.Regex(@"(%%)|%(\d+)|%\((\d+)\)|%");
int maxNumber = 0;
var result = re.Replace(srcFormat, (m) =>
var result = _templateRe.Replace(srcFormat, (m) =>
{
if (m.Groups[1].Success)
return "%";
Expand All @@ -228,7 +236,7 @@ public string StrTemplate(string template,
var number = int.Parse(m.Groups[2].Success ? m.Groups[2].Value : m.Groups[3].Value);

if (number < 1 || number > 10)
throw new RuntimeException($"Ошибка синтаксиса шаблона в позиции {m.Index+2}: недопустимый номер подстановки");
throw StringOpException.TemplateSubst(m.Index + 2, number);

//FIXME: отключено, т.к. платформа игнорирует ошибку с недостаточным числом параметров
//if (number > passedArgsCount)
Expand All @@ -240,7 +248,7 @@ public string StrTemplate(string template,
return arguments[10-number] ?? "";
}

throw new RuntimeException("Ошибка синтаксиса шаблона в позиции " + (m.Index + 2));
throw StringOpException.TemplateSyntax(m.Index + 2);
});

if (passedArgsCount > maxNumber)
Expand All @@ -253,9 +261,9 @@ public static IAttachableContext CreateInstance()
{
return new StringOperations();
}

}


[EnumerationType("НаправлениеПоиска", "SearchDirection")]
public enum SearchDirection
{
Expand All @@ -265,5 +273,42 @@ public enum SearchDirection
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}'"));
}
}

}
70 changes: 70 additions & 0 deletions tests/stringoperations.os
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_ВтороеВхождениеСНачала");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_ВтороеВхождениеСКонца");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_ВтороеВхождениеСКонцаНачинаяСПредпоследнегоСимвола");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СПустойСтрокой");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СПустойПодстрокойПоиска");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СПропущеннымиСтроками");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СНевернойНачальнойПозицией");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СНулевымЧисломВхождений");
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СОтрицательнымЧисломВхождений");

ВсеТесты.Добавить("Тест_ДолженПроверитьНСтрВозвращаетПервуюСтроку");
ВсеТесты.Добавить("ТестДолжен_Проверить_Что_НСТР_С_СуществующимПараметром_ВозвращаетНужнуюСтроку");
ВсеТесты.Добавить("ТестДолжен_Проверить_Что_НСТР_С_НесуществующимПараметром_ВозвращаетПустуюСтроку");
Expand Down Expand Up @@ -459,6 +466,69 @@

КонецПроцедуры

Процедура ТестДолжен_ВызватьМетод_СтрНайти_СПустойСтрокой() Экспорт

ГдеИщем = "";
ЧтоИщем = ",";

юТест.ПроверитьРавенство(0, СтрНайти(ГдеИщем, ЧтоИщем));

КонецПроцедуры

Процедура ТестДолжен_ВызватьМетод_СтрНайти_СПустойПодстрокойПоиска() Экспорт

ГдеИщем = "Один,Два,Три,";
ЧтоИщем = "";

юТест.ПроверитьРавенство(1, СтрНайти(ГдеИщем, ЧтоИщем));

КонецПроцедуры

Процедура ТестДолжен_ВызватьМетод_СтрНайти_СПропущеннымиСтроками() Экспорт
юТест.ПроверитьРавенство(1, СтрНайти(,));
КонецПроцедуры

Процедура ТестДолжен_ВызватьМетод_СтрНайти_СНевернойНачальнойПозицией() Экспорт

ГдеИщем = "Один,Два,Три,";
ЧтоИщем = ",";

Попытка
СтрНайти(ГдеИщем, ЧтоИщем, , 999);
Исключение
юТест.ТестПройден();
Возврат;
КонецПопытки;

юТест.ТестПровален("Ожидаемое исключение не возникло.");

КонецПроцедуры

Процедура ТестДолжен_ВызватьМетод_СтрНайти_СНулевымЧисломВхождений() Экспорт

ГдеИщем = "Один,Два,Три,";
ЧтоИщем = ",";

Попытка
СтрНайти(ГдеИщем, ЧтоИщем,,, 0);
Исключение
юТест.ТестПройден();
Возврат;
КонецПопытки;

юТест.ТестПровален("Ожидаемое исключение не возникло.");

КонецПроцедуры

Процедура ТестДолжен_ВызватьМетод_СтрНайти_СОтрицательнымЧисломВхождений() Экспорт

ГдеИщем = "Один,Два,Три,";
ЧтоИщем = ",";

юТест.ПроверитьРавенство(0, СтрНайти(ГдеИщем, ЧтоИщем,,, -2));

КонецПроцедуры

Процедура Тест_ДолженПроверитьНСтрВозвращаетПервуюСтроку() Экспорт

Стр = НСтр("ru = 'Строка1'; en = 'Строка2'", "ru");
Expand Down