C# 保留(或恢复)文本内容中的空白

C# 保留(或恢复)文本内容中的空白,c#,anglesharp,C#,Anglesharp,使用AngleSharp处理一些HTML并提取元素的文本内容以供以后挖掘,我遇到了AngleSharp去除HTML标记的方式的问题。例如,我有一段类似这样的HTML(不包括换行符和制表符): 结果会是这样的: "...blah, blah, action.Typical blah, blah..." IEnumerable<string> ExtractChildNodes(INode node) { if (node.HasChildNodes) {

使用AngleSharp处理一些HTML并提取元素的文本内容以供以后挖掘,我遇到了AngleSharp去除HTML标记的方式的问题。例如,我有一段类似这样的HTML(不包括换行符和制表符):

结果会是这样的:

"...blah, blah, action.Typical blah, blah..."
IEnumerable<string> ExtractChildNodes(INode node)
{
    if (node.HasChildNodes)
    {
        foreach (var c in node.ChildNodes)
        {
            foreach (var r in ExtractChildNodes(c))
            {
                yield return r;
            }
        }
    }
    else
    {
        yield return node.TextContent;
    }
}
单词
action
Typical
被粉碎在一起,没有任何空格(因为它们之间只有html标记)。这让我的文本内容标记化工作受挫,因为
action。典型的
被视为一个单词而不是两个单词

当然,我可以运行搜索并替换(可能使用正则表达式),类似于
(\S)\(\S)
,然后用
$1替换它$2
但这需要像
www.somecompany.com
这样的东西,然后将其分为
www
somecompany
com
,我可能想保留这一点(或者如果
www
com
本身不太可能有用)。我可以排除带有多个点的单词,但网址可能显示为
somecompany.com
(没有
www
),或者您可能会遇到类似
somebody@somecompany.com


有没有一个强有力的方法来解决这个问题?要在标记被剥离后至少保留一个空间?

因此,解决这一问题的最佳方法似乎是递归根元素的
子节点(而不是缺少文本节点的
子节点),然后再次将它们全部连接起来。因此,鉴于:

var rootElem = myDoc.GetElementById("someId");
我可以创建如下函数:

"...blah, blah, action.Typical blah, blah..."
IEnumerable<string> ExtractChildNodes(INode node)
{
    if (node.HasChildNodes)
    {
        foreach (var c in node.ChildNodes)
        {
            foreach (var r in ExtractChildNodes(c))
            {
                yield return r;
            }
        }
    }
    else
    {
        yield return node.TextContent;
    }
}
这应该给我:

动作
典型
之间留有空格


这似乎不仅适用于某些情况,如
单词
,还适用于一些自动关闭标签,如
单词
,甚至
一些
单词
,使用正则表达式或类似的东西将是一种巨大的痛苦。

您描述的方式很有效,除了一些您已经遇到的场景(例如,自动关闭标签)。因此,我提议如下:

  • 文本节点将按字面意思获取
  • 元素在其子节点w.r.t上迭代。
    • 如果两个相邻元素产生内容,则插入一个空格
    • 如果没有子节点,则查看元素是否特殊(如br),并放置一些代表性字符串(如换行符)
    • 否则,例如,如果文本节点与元素相邻,则不会插入文本
因此,总体而言,以下实现应该完成这项工作:

String Stringify(INode node)
{
    switch (node.NodeType)
    {
        case NodeType.Text:
            return node.TextContent;

        case NodeType.Element:
            if (node.HasChildNodes)
            {
                var sb = new StringBuilder();
                var isElement = false;

                foreach (var child in node.ChildNodes)
                {
                    var isPreviousElement = isElement;
                    var content = Stringify(child);
                    isElement = child.NodeType == NodeType.Element;

                    if (!String.IsNullOrEmpty(content) && isElement && isPreviousElement)
                    {
                        sb.Append(' ');
                    }

                    sb.Append(content);
                }

                return sb.ToString();
            }

            switch (node.NodeName.ToLowerInvariant())
            {
                case "br": return "\n";
            }

            goto default;

        default:
            return String.Empty;

    }
}

这种实现的优点是,您可以根据自己的需要对其进行调整。例如,对于
br
之类的标记,您可以轻松地输出空格而不是换行符。

这真的是您正在解析的HTML吗(字面上)?因为如果是这样的话,AngleSharp肯定会在“action.”和“typical”之间有空格(事实上是一个新行和一些空格字符)。@FlorianRappl:实际上不是,我格式化它是为了清晰,但实际上它是一行,没有换行符或制表符。我想那部分有点混乱。我将在问题中澄清。是的,我猜是这样。正如我所说:否则,您将看到文本节点的文本。我将提供另一种可能对你有用的方法/答案。
"...blah, blah, action. Typical blah, blah..."
String Stringify(INode node)
{
    switch (node.NodeType)
    {
        case NodeType.Text:
            return node.TextContent;

        case NodeType.Element:
            if (node.HasChildNodes)
            {
                var sb = new StringBuilder();
                var isElement = false;

                foreach (var child in node.ChildNodes)
                {
                    var isPreviousElement = isElement;
                    var content = Stringify(child);
                    isElement = child.NodeType == NodeType.Element;

                    if (!String.IsNullOrEmpty(content) && isElement && isPreviousElement)
                    {
                        sb.Append(' ');
                    }

                    sb.Append(content);
                }

                return sb.ToString();
            }

            switch (node.NodeName.ToLowerInvariant())
            {
                case "br": return "\n";
            }

            goto default;

        default:
            return String.Empty;

    }
}