У меня есть html с вложенными элементами (в основном просто с элементами div и p ) Мне нужно вернуть тот же html, но подстроить заданное количество букв. Очевидно, что число букв не должно перечисляться через теги html, а только подсчет букв InnerText каждого элемента html. Результат Html должен сохранять правильную структуру - любые закрывающие теги, чтобы оставаться действительным html.
Пример ввода:
<div>
<p>some text</p>
<p>some more text some more text some more text some more text some more text</p>
<div>
<p>some more text some more text some more text some more text some more text</p>
<p>some more text some more text some more text some more text some more text</p>
</div>
</div>
Учитывая int length = 16
результат должен выглядеть следующим образом:
<div>
<p>some text</p> // 9 characters in the InnerText here
<p>some mo</p> // 7 characters in the InnerText here; 9 + 7 = 16;
</div>
Обратите внимание, что количество букв (включая пробелы) равно 16. Последующий <div>
исключается, так как количество букв достигает переменной length
. Обратите внимание, что выходной html остается в силе.
Я пробовал следующее, но на самом деле это не работает. Результат не так ожидаемый: некоторые элементы html повторяются.
public static string SubstringHtml(this string html, int length)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
int totalLength = 0;
StringBuilder output = new StringBuilder();
foreach (var node in doc.DocumentNode.Descendants())
{
totalLength += node.InnerText.Length;
if(totalLength >= length)
{
int difference = totalLength - length;
string lastPiece = node.InnerText.ToString().Substring(0, difference);
output.Append(lastPiece);
break;
}
else
{
output.Append(node.InnerHtml);
}
}
return output.ToString();
}
ОБНОВИТЬ
@SergeBelov предоставил решение, которое работает для первого ввода образца, однако дальнейшее тестирование показало проблему со входом, подобным приведенному ниже.
Пример ввода №2:
some more text some more text
<div>
<p>some text</p>
<p>some more text some more text some more text some more text some more text</
</div>
Учитывая, что переменная int maxLength = 7;
выход должен быть равен некоторому моменту . Это не работает так из-за этого кода, где ParentNode = null
:
lastNode
.Node
.ParentNode
.ReplaceChild(HtmlNode.CreateNode(lastNodeText.InnerText.Substring(0, lastNode.NodeLength - lastNode.TotalLength + maxLength)), lastNode.Node);
Создание нового HtmlNode, похоже, не помогает, потому что его свойство InnterText является readonly.
Небольшая консольная программа ниже иллюстрирует один из возможных подходов:
UPDATE: это должно по-прежнему работать с первым текстовым узлом; вероятно, Trim()
требуется для удаления пробела из него, как показано ниже.
static void Main(string[] args)
{
int maxLength = 9;
string input = @"
some more text some more text
<div>
<p>some text</p>
<p>some more text some more text some more text some more text some more text</
</div>";
var doc = new HtmlDocument();
doc.LoadHtml(input);
// Get text nodes with the appropriate running total
var acc = 0;
var nodes = doc.DocumentNode
.Descendants()
.Where(n => n.NodeType == HtmlNodeType.Text && n.InnerText.Trim().Length > 0)
.Select(n =>
{
var length = n.InnerText.Trim().Length;
acc += length;
return new { Node = n, TotalLength = acc, NodeLength = length };
})
.TakeWhile(n => (n.TotalLength - n.NodeLength) < maxLength)
.ToList();
// Select element nodes we intend to keep
var nodesToKeep = nodes
.SelectMany(n => n.Node.AncestorsAndSelf()
.Where(m => m.NodeType == HtmlNodeType.Element));
// Select and remove element nodes we don't need
var nodesToDrop = doc.DocumentNode
.Descendants()
.Where(m => m.NodeType == HtmlNodeType.Element)
.Except(nodesToKeep)
.ToList();
foreach (var r in nodesToDrop)
r.Remove();
// Shorten the last node as required
var lastNode = nodes.Last();
var lastNodeText = lastNode.Node;
var text = lastNodeText.InnerText.Trim().Substring(0,
lastNode.NodeLength - lastNode.TotalLength + maxLength);
lastNodeText
.ParentNode
.ReplaceChild(HtmlNode.CreateNode(text), lastNodeText);
doc.Save(Console.Out);
}