iCalendar RFC 2445, Раздел 4.1, Складывание содержимого
Я создаю простой iCalendar с использованием C# и обнаружил, что сворачивание содержимого в соответствии с разделом 4.1 RFC 2445 является головной болью (для меня:-).
http://www.apps.ietf.org/rfc/rfc2445.html
Для длинных строк вы должны экранировать некоторые символы (обратная косая черта, точка с запятой, запятая и перевод строки, я считаю), а затем сложить их так, чтобы ни одна строка не была длиннее 75 октетов. Я нашел несколько простых способов сделать это в Интернете. Самое простое - заменить рассматриваемые символы на экранированную версию, а затем вставлять CRLF в каждый 75-й символ. Что-то вроде:
// too simple, could break at an escape sequence boundary or multi-byte character may overflow 75 octets
txt = txt.Replace(@"\", "\\\\").Replace(";", "\\;").Replace(",", "\\,").Replace("\r\n", "\\n");
var regex = new System.Text.RegularExpressions.Regex( ".{75}");
var escape_and_folded = regex.Replace( txt, "$0\r\n ");
Я вижу две проблемы. Возможно, CRLF вставлен в экранированную последовательность. Например, если вставка происходит так, что экранированная новая строка последовательности "\n" становится "\CRLF" (тогда "n" будет на следующей строке). Вторая проблема, когда есть многобайтовые символы. Поскольку вычисление производится на символы, возможно, строка станет длиннее 75 октетов.
Простое решение состоит в том, чтобы пройти строку за символом и сбежать и сбросить, но это кажется довольно грубой силой. У кого-нибудь есть более элегантное решение?
3 ответа
Прежде всего, убедитесь, что вы смотрите на RFC5545. RFC2445 устарел. Вы можете найти мою реализацию PHP здесь:
https://github.com/fruux/sabre-vobject/blob/master/lib/Property.php
В php у нас есть функция mb_strcut. Я не уверен, что есть аналог.NET, но это, по крайней мере, значительно упростит ситуацию. У меня пока не было проблем со сворачиванием escape-последовательностей (\
) в половине. Хороший парсер сначала развернет строки, и только потом будет иметь дело с эскейпингом. Тем более, какие символы должны быть экранированы, зависит от фактического свойства. (иногда ,
или же ;
сбегает, иногда нет).
Я попробовал ваше решение - оно работает, за исключением того факта, что оно также свернуло несколько строк, длина которых была менее 75 октетов. Поэтому я переписал код традиционно (т.е. без использования регулярных выражений - я их пропускаю), как показано ниже.
public static string FoldLines(this string value, int max, string newline = "\r\n")
{
var lines = value.Split(new string[]{newline}, System.StringSplitOptions.RemoveEmptyEntries);
using (var ms = new System.IO.MemoryStream(value.Length))
{
var crlf = Encoding.UTF8.GetBytes(newline); //CRLF
var crlfs = Encoding.UTF8.GetBytes(string.Format("{0} ", newline)); //CRLF and SPACE
foreach (var line in lines)
{
var bytes = Encoding.UTF8.GetBytes(line);
var len = Encoding.UTF8.GetByteCount(line);
if (len <= max)
{
ms.Write(bytes, 0, len);
ms.Write(crlf, 0, crlf.Length);
}
else
{
var blen = len / max; //calculate block length
var rlen = len % max; //calculate remaining length
var b = 0;
while (b < blen)
{
ms.Write(bytes, (b++) * max, max);
ms.Write(crlfs, 0, crlfs.Length);
}
if (rlen > 0)
{
ms.Write(bytes, blen * max, rlen);
ms.Write(crlf, 0, crlf.Length);
}
}
}
return Encoding.UTF8.GetString(ms.ToArray());
}
}
Примечания:
- Я старался изо всех сил быть элегантным - то есть я разбирал строку не по символам, а по блокам октетов (определяется max).
- Эту функцию лучше всего вызывать в результирующем объекте VCALENDAR, чтобы все строки содержимого проверялись на складывание и при необходимости переносились.
Экранирование специальных литералов выполняется только в свойствах, связанных с TEXT, таких как DESCRIPTION, SUMMARY и т. Д. Они реализованы в следующих методах расширения:
public static string Replace(this string value, IEnumerable<Tuple<string, string>> pairs) { foreach (var pair in pairs) value = value.Replace(pair.Item1, pair.Item2); return value; } public static string EscapeStrings(this string value) { return value.Replace(new List<Tuple<string, string>> { new Tuple<string, string>(@"\", "\\\\"), new Tuple<string, string>(";", @"\;"), new Tuple<string, string>(",", @"\,"), new Tuple<string, string>("\r\n", @"\n"), }); }
Решение reexmonkey записывает 76 символов в середине сложенных строк, потому что оно не вычитает лишний пробел, добавленный с помощью crlfs
Я переписал функцию складывания, чтобы исправить это:
public static string FoldLines(string value, int max, string newline = "\r\n")
{
var lines = value.Split(new string[] { newline }, System.StringSplitOptions.RemoveEmptyEntries);
using (var ms = new System.IO.MemoryStream(value.Length))
{
var crlf = Encoding.UTF8.GetBytes(newline); //CRLF
var crlfs = Encoding.UTF8.GetBytes(string.Format("{0} ", newline)); //CRLF and SPACE
foreach (var line in lines)
{
var bytes = Encoding.UTF8.GetBytes(line);
var len = Encoding.UTF8.GetByteCount(line);
if (len <= max)
{
ms.Write(bytes, 0, len);
ms.Write(crlf, 0, crlf.Length);
}
else
{
var offset = 0; //current offset position
var count = max; //characters to take
while (offset + count < len)
{
ms.Write(bytes, offset, count);
ms.Write(crlfs, 0, crlfs.Length);
offset += count;
count = max - 1;
}
count = len - offset; //remaining characters
if (count > 0)
{
ms.Write(bytes, offset, count);
ms.Write(crlf, 0, crlf.Length);
}
}
}
return Encoding.UTF8.GetString(ms.ToArray());
}
}
Также я добавил дополнительный Tuple в функцию EscapeStrings:
public static string ReplaceText(string value, IEnumerable<Tuple<string, string>> pairs)
{
foreach (var pair in pairs) value = value.Replace(pair.Item1, pair.Item2);
return value;
}
public static string EscapeStrings(string value)
{
return ReplaceText(value, new List <Tuple<string, string>>
{
new Tuple<string, string>(@"\", "\\\\"),
new Tuple<string, string>(";", @"\;"),
new Tuple<string, string>(",", @"\,"),
new Tuple<string, string>("\r\n", @"\n"),
new Tuple<string, string>("\n", @"\n"),
});
}