Tengo html con elementos anidados (en su mayoría solo elementos div yp ) Necesito devolver el mismo html, pero con una determinada cantidad de letras. Obviamente, el recuento de letras no debe enumerar a través de etiquetas html, sino solo contar las letras de InnerText de cada elemento html. El resultado de HTML debe preservar la estructura adecuada: cualquier etiqueta de cierre para mantener el formato HTML válido.
Ejemplo de entrada:
<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>
Dada la int length = 16
la salida debería verse así:
<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>
Observe que el número de letras (incluidos los espacios) es 16. El siguiente <div>
se elimina ya que el recuento de letras ha alcanzado la length
variable. Tenga en cuenta que la salida html sigue siendo válida.
He intentado lo siguiente, pero eso realmente no funciona. El resultado no es el esperado: algunos elementos html se repiten.
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();
}
ACTUALIZAR
@SergeBelov proporcionó una solución que funciona para la primera entrada de muestra, sin embargo, las pruebas posteriores presentaron un problema con una entrada como la que se muestra a continuación.
Ejemplo de entrada # 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>
Dada esa variable int maxLength = 7;
una salida debe ser igual a algunos mo . No funciona así debido a este código donde ParentNode = null
:
lastNode
.Node
.ParentNode
.ReplaceChild(HtmlNode.CreateNode(lastNodeText.InnerText.Substring(0, lastNode.NodeLength - lastNode.TotalLength + maxLength)), lastNode.Node);
La creación de un nuevo HtmlNode no parece ayudar porque su propiedad InnterText es de solo lectura.
El siguiente programa de consola pequeño ilustra un posible enfoque, que es:
ACTUALIZACIÓN: Esto todavía debería funcionar con un nodo de texto siendo el primero; probablemente, se requiere un Trim()
para eliminar el espacio en blanco de la misma como se muestra a continuación.
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);
}