Удаление скрытых символов из строк
Моя проблема:
У меня есть приложение.NET, которое рассылает информационные бюллетени по электронной почте. Когда информационные бюллетени просматриваются в outlook, outlook отображает знак вопроса вместо скрытого символа, который он не может распознать. Эти скрытые символы исходят от конечных пользователей, которые копируют и вставляют html, который составляет информационные бюллетени в форму и отправляет их. C# trim() удаляет эти скрытые символы, если они встречаются в конце или начале строки. Когда новостная рассылка просматривается в gmail, gmail хорошо игнорирует их. При вставке этих скрытых символов в документ Word и включении параметра "Показывать метки абзаца и скрытые символы" символы отображаются в виде одного прямоугольника внутри большего прямоугольника. Кроме того, текст, из которого состоят информационные бюллетени, может быть на любом языке, поэтому принятие символов Unicode является обязательным. Я попытался перебрать строку, чтобы обнаружить символ, но цикл не распознает его и проходит по нему. Также просить конечного пользователя вставить html в блокнот, прежде чем отправлять его, не может быть и речи.
Мой вопрос:
Как я могу обнаружить и устранить эти скрытые символы, используя C#?
12 ответов
Вы можете удалить все управляющие символы из входной строки следующим образом:
string input; // this is your input string
string output = new string(input.Where(c => !char.IsControl(c)).ToArray());
Вот документация для IsControl()
метод.
Или, если вы хотите сохранить только буквы и цифры, вы также можете использовать IsLetter
а также IsDigit
функция:
string output = new string(input.Where(c => char.IsLetter(c) || char.IsDigit(c)).ToArray());
Я обычно использую это регулярное выражение для замены всех непечатаемых символов.
Кстати, большинство людей думают, что табуляция, перевод строки и возврат каретки являются непечатными символами, но для меня это не так.
Итак, вот выражение:
string output = Regex.Replace(input, @"[^\u0009\u000A\u000D\u0020-\u007E]", "*");
^
означает, что это одно из следующих:\u0009
это вкладка\u000A
перевод строки\u000D
возврат каретки\u0020-\u007E
означает все от космоса до~
- то есть все в ASCII.
Смотрите таблицу ASCII, если вы хотите внести изменения. Помните, что это скинет каждый не-ASCII символ.
Для проверки выше вы можете создать строку самостоятельно, например так:
string input = string.Empty;
for (int i = 0; i < 255; i++)
{
input += (char)(i);
}
Что лучше всего сработало для меня:
string result = new string(value.Where(c => char.IsLetterOrDigit(c) || (c >= ' ' && c <= byte.MaxValue)).ToArray());
Когда я проверяю, является ли символ любой буквой или цифрой, чтобы я не игнорировал любые неанглийские буквы, или, если это не буква, я проверяю, является ли это символ ascii, который больше или равен пробелу, чтобы убедиться, Я игнорирую некоторые управляющие символы, это гарантирует, что я не игнорирую знаки препинания.
Некоторые предлагают использовать IsControl, чтобы проверить, является ли символ непечатным или нет, но при этом игнорируется, например, метка слева направо.
new string(input.Where(c => !char.IsControl(c)).ToArray());
IsControl пропускает некоторые управляющие символы, такие как метка слева направо (LRM) (символ, который обычно скрывается в строке при копировании). Если вы уверены, что ваша строка имеет только цифры и цифры, вы можете использовать IsLetterOrDigit
new string(input.Where(c => char.IsLetterOrDigit(c)).ToArray())
Если в вашей строке есть специальные символы, то
new string(input.Where(c => c < 128).ToArray())
Вы можете сделать это:
var hChars = new char[] {...};
var result = new string(yourString.Where(c => !hChars.Contains(c)).ToArray());
Ответ TLDR
Используйте это регулярное выражение ...
\P{Cc}\P{Cn}\P{Cs}
Как это...
var regex = new Regex(@"![\P{Cc}\P{Cn}\P{Cs}]");
Объяснение TLDR
- : Не совпадать с управляющими символами.
-
\P{Cn}
: Не соответствовать неназначенным символам. -
\P{Cs}
: Не использовать недопустимые символы UTF-8.
Рабочая демонстрация
В этой демонстрации я использую это регулярное выражение для поиска строки
"Hello, World!"
. Этот странный персонаж в конце
(char)4
- это персонаж для END TRANSMISSION
.
using System;
using System.Text.RegularExpressions;
public class Test {
public static void Main() {
var regex = new Regex(@"![\P{Cc}\P{Cn}\P{Cs}]");
var matches = regex.Matches("Hello, World!" + (char)4);
Console.WriteLine("Results: " + matches.Count);
foreach (Match match in matches) {
Console.WriteLine("Result: " + match);
}
}
}
Полная рабочая демонстрация на IDEOne.com
Вывод из приведенного выше кода:
Results: 1
Result: !
Альтернативы
-
\P{C}
: Соответствие только видимым символам. Не совмещайте невидимые символы. -
\P{Cc}
: Соответствие только неконтролирующим символам. Не совпадайте с управляющими символами. -
\P{Cc}\P{Cn}
: Соответствие только назначенным неконтролирующим символам. Не совпадайте с управляющими или неназначенными символами. -
\P{Cc}\P{Cn}\P{Cs}
: Соответствие только неконтролирующим символам, которые были назначены и допустимы в кодировке UTF-8. Не сопоставляйте управляющие, неназначенные или недопустимые символы UTF-8. -
\P{Cc}\P{Cn}\P{Cs}\P{Cf}
: Соответствие только неконтролирующим, неформатирующим символам, которые были назначены и соответствуют UTF-8. Не сопоставляйте управляющие, неназначенные, форматирующие или недопустимые символы UTF-8.
Источник и объяснение
Взгляните на доступные свойства символов Unicode, которые можно использовать для проверки в регулярном выражении. Вы должны уметь использовать эти регулярные выражения в Microsoft .NET, JavaScript, Python, Java, PHP, Ruby, Perl, Golang и даже Adobe . Знание классов символов Unicode - это очень полезное знание, поэтому я рекомендую его использовать!
Если вам нужна скорость, создайте статический метод, который выглядит следующим образом:
private static string RemoveControlCharacters(ReadOnly<char> input)
{
Span<char> output = stackalloc char[input.Length];
int j = 0;
foreach (char c in input)
{
if (!char.IsControl(c))
{
output[j++] = c;
}
}
return new string(output.Slice(0, j));
}
Он использует stackalloc для выделения памяти для выходной строки в стеке, что быстрее, чем выделение кучи.
Если вы знаете, что это за символы, вы можете использовать string.Replace
:
newString = oldString.Replace("?", "");
где "?" представляет персонажа, которого вы хотите вырезать.
Недостаток этого подхода заключается в том, что вам необходимо повторять этот вызов, если есть несколько символов, которые вы хотите удалить.
У меня возникла ошибка с AWS S3 SDK «Путь к целевому ресурсу [имя -3.30.2022 -15.27.00.pdf] имеет двунаправленные символы, которые не поддерживаются System.Uri и, следовательно, не может быть обработан пакетом SDK для .NET"
Имя файла в моем экземпляре содержало символ Unicode «ЗНАК СЛЕВА НАПРАВО» (U+200E) между точками. Они не были видны в html или в Notepad++. Когда текст был вставлен в редактор Visual Studio 2019, текст Unicode был виден, и мне удалось решить проблему.
Проблема решилась заменой всех управляющих и других непечатаемых символов из имени файла с помощью следующего скрипта.
var input = Regex.Replace(s, @"\p{C}+", string.Empty);
Это было некоторое время, но это еще не ответили.
Как вы включаете содержимое HMTL в код отправки? если вы читаете его из файла, проверьте кодировку файла. Если вы используете UTF-8 с подписью (имя немного различается у разных редакторов), это может вызвать странный символ в начале письма.
Я использовал этот быстрый и грязный лайнер, чтобы очистить некоторые входные данные от меток LTR/RTL, оставленных неработающим калькулятором в Windows 10. Вероятно, это далеко не идеально, но достаточно для быстрого исправления:
string cleaned = new string(input.Where(c => !char.IsControl(c) && (char.IsLetterOrDigit(c) || char.IsPunctuation(c) || char.IsSeparator(c) || char.IsSymbol(c) || char.IsWhiteSpace(c))).ToArray());
Строка output = новая строка (input.Where(c =>!char.IsControl(c)).ToArray()); Это, безусловно, решит проблему. У меня был непечатаемый символ замены (ASCII 26) в строке, которая приводила к разрыву моего приложения, и эта строка кода удаляла символы