背景

在做网页数据分析的时候,我们关注的部分是内容,可以过滤掉HTML标签、Javascript、CSS等代码。

目标输入

<b>Hello World.</b><br/><p><i>Is there anyone out there?</i><p>

输出结果

Hello World. Is there anyone out there?

开发工具

Html Agility Pack
http://html-agility-pack.net/

实现方案1:(过滤规则严谨,保留HTML版式,推荐使用!

//small but important modification to class https://github.com/zzzprojects/html-agility-pack/blob/master/src/Samples/Html2Txt/HtmlConvert.cs
public static class HtmlToText
{ public static string Convert(string path)
{
HtmlDocument doc = new HtmlDocument();
doc.Load(path);
return ConvertDoc(doc);
} public static string ConvertHtml(string html)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
return ConvertDoc(doc);
} public static string ConvertDoc (HtmlDocument doc)
{
using (StringWriter sw = new StringWriter())
{
ConvertTo(doc.DocumentNode, sw);
sw.Flush();
return sw.ToString();
}
} internal static void ConvertContentTo(HtmlNode node, TextWriter outText, PreceedingDomTextInfo textInfo)
{
foreach (HtmlNode subnode in node.ChildNodes)
{
ConvertTo(subnode, outText, textInfo);
}
}
public static void ConvertTo(HtmlNode node, TextWriter outText)
{
ConvertTo(node, outText, new PreceedingDomTextInfo(false));
}
internal static void ConvertTo(HtmlNode node, TextWriter outText, PreceedingDomTextInfo textInfo)
{
string html;
switch (node.NodeType)
{
case HtmlNodeType.Comment:
// don't output comments
break;
case HtmlNodeType.Document:
ConvertContentTo(node, outText, textInfo);
break;
case HtmlNodeType.Text:
// script and style must not be output
string parentName = node.ParentNode.Name;
if ((parentName == "script") || (parentName == "style"))
{
break;
}
// get text
html = ((HtmlTextNode)node).Text;
// is it in fact a special closing node output as text?
if (HtmlNode.IsOverlappedClosingElement(html))
{
break;
}
// check the text is meaningful and not a bunch of whitespaces
if (html.Length == )
{
break;
}
if (!textInfo.WritePrecedingWhiteSpace || textInfo.LastCharWasSpace)
{
html= html.TrimStart();
if (html.Length == ) { break; }
textInfo.IsFirstTextOfDocWritten.Value = textInfo.WritePrecedingWhiteSpace = true;
}
outText.Write(HtmlEntity.DeEntitize(Regex.Replace(html.TrimEnd(), @"\s{2,}", " ")));
if (textInfo.LastCharWasSpace = char.IsWhiteSpace(html[html.Length - ]))
{
outText.Write(' ');
}
break;
case HtmlNodeType.Element:
string endElementString = null;
bool isInline;
bool skip = false;
int listIndex = ;
switch (node.Name)
{
case "nav":
skip = true;
isInline = false;
break;
case "body":
case "section":
case "article":
case "aside":
case "h1":
case "h2":
case "header":
case "footer":
case "address":
case "main":
case "div":
case "p": // stylistic - adjust as you tend to use
if (textInfo.IsFirstTextOfDocWritten)
{
outText.Write("\r\n");
}
endElementString = "\r\n";
isInline = false;
break;
case "br":
outText.Write("\r\n");
skip = true;
textInfo.WritePrecedingWhiteSpace = false;
isInline = true;
break;
case "a":
if (node.Attributes.Contains("href"))
{
string href = node.Attributes["href"].Value.Trim();
if (node.InnerText.IndexOf(href, StringComparison.InvariantCultureIgnoreCase)==-)
{
endElementString = "<" + href + ">";
}
}
isInline = true;
break;
case "li":
if(textInfo.ListIndex>)
{
outText.Write("\r\n{0}.\t", textInfo.ListIndex++);
}
else
{
outText.Write("\r\n*\t"); //using '*' as bullet char, with tab after, but whatever you want eg "\t->", if utf-8 0x2022
}
isInline = false;
break;
case "ol":
listIndex = ;
goto case "ul";
case "ul": //not handling nested lists any differently at this stage - that is getting close to rendering problems
endElementString = "\r\n";
isInline = false;
break;
case "img": //inline-block in reality
if (node.Attributes.Contains("alt"))
{
outText.Write('[' + node.Attributes["alt"].Value);
endElementString = "]";
}
if (node.Attributes.Contains("src"))
{
outText.Write('<' + node.Attributes["src"].Value + '>');
}
isInline = true;
break;
default:
isInline = true;
break;
}
if (!skip && node.HasChildNodes)
{
ConvertContentTo(node, outText, isInline ? textInfo : new PreceedingDomTextInfo(textInfo.IsFirstTextOfDocWritten){ ListIndex = listIndex });
}
if (endElementString != null)
{
outText.Write(endElementString);
}
break;
}
}
}
internal class PreceedingDomTextInfo
{
public PreceedingDomTextInfo(BoolWrapper isFirstTextOfDocWritten)
{
IsFirstTextOfDocWritten = isFirstTextOfDocWritten;
}
public bool WritePrecedingWhiteSpace {get;set;}
public bool LastCharWasSpace { get; set; }
public readonly BoolWrapper IsFirstTextOfDocWritten;
public int ListIndex { get; set; }
}
internal class BoolWrapper
{
public BoolWrapper() { }
public bool Value { get; set; }
public static implicit operator bool(BoolWrapper boolWrapper)
{
return boolWrapper.Value;
}
public static implicit operator BoolWrapper(bool boolWrapper)
{
return new BoolWrapper{ Value = boolWrapper };
}
}

实现方案2:(过滤规则不严谨,适用于结构简单的HTML)

public static string StripHTML(string HTMLText, bool decode = true)
{
Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase);
var stripped = reg.Replace(HTMLText, "");
return decode ? HttpUtility.HtmlDecode(stripped) : stripped;
}

参考资料

https://stackoverflow.com/a/25178738
https://stackoverflow.com/a/732110

[C#] - 从 HTML 代码中 转换 / 提取 可读文字(PlainText)的方法的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议——建议49:在Dispose模式中应提取一个受保护的虚方法

    建议49:在Dispose模式中应提取一个受保护的虚方法 在标准的Dispose模式中,真正的IDisposable接口的Dispose方法并没有做实际的清理工作,它其实是调用了下面的这个带bool参 ...

  2. Cocos2dx 代码中包含中文导致编译错误的问题解决方法

    从网上下载一个cocos2dx的源码,是IOS版本的,我将其迁移到windows 7下 ,用VS2010编译,出现一堆的C2001错误: 1>d:\cocos2d-x-2.2.6\mygame\ ...

  3. 在带(继承)TextView的控件中,在代码中动态更改TextView的文字颜色

    今天由于公司项目需求,须要实现一种类似tab的选项卡,当时直接想到的就是使用RadioGroup和RadioButton来实现. 这种方法全然没问题.可是在后来的开发过程中,却遇到了一些困扰非常久的小 ...

  4. 在java代码中,用xslt处理xml文件

    http://blog.csdn.net/zhou_lei/article/details/2661735 ********************************************** ...

  5. java代码中fastjson生成字符串和解析字符串的方法和javascript文件中字符串和json数组之间的转换方法

    1.java代码中fastjson生成字符串和解析字符串的方法 List<TemplateFull> templateFulls = new ArrayList<TemplateFu ...

  6. Java基础学习总结(81)——如何尽可能的减少Java代码中bug

    Java编程语言的人气自然无需质疑,从Web应用到Android应用,这款语言已经被广泛用于开发各类应用及代码中的复杂功能. 不过在编写代码时,bug永远是困扰每一位从业者的头号难题.在今天的文章中, ...

  7. 实际案例:在现有代码中通过async/await实现并行

    一项新技术或者一个新特性,只有你用它解决实际问题后,才能真正体会到它的魅力,真正理解它.也期待大家能够多分享解一些解决实际问题的内容. 在我们遭遇“黑色30秒”问题的过程中,切身体会到了异步的巨大作用 ...

  8. 在现有代码中通过async/await实现并行

    在现有代码中通过async/await实现并行 一项新技术或者一个新特性,只有你用它解决实际问题后,才能真正体会到它的魅力,真正理解它.也期待大家能够多分享解一些解决实际问题的内容. 在我们遭遇“黑色 ...

  9. struts2框架之OGNL表达式概述(在代码中使用OGNL表达式)

    1. OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写 * 所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个对象关联的其它对象 * 通 ...

随机推荐

  1. linux服务器时间乱码问题解决

    问题现象如下: [root@ip-171-21-36-129 testcase]# date 2019Ū 08Ղ 02ɕ чǚϥ 09:44:48 UTC 解决步骤: 1.执行命令:vi /etc/s ...

  2. sudo 命令

    su命令和su -命令最大的本质区别就是: 前者只是切换了root身份,但Shell环境仍然是普通用户的Shell: 而后者连用户和Shell环境一起切换成root身份了.只有切换了Shell环境才不 ...

  3. Redis哨兵日志说明

    一.说明

  4. ICEM-点火器

    原视频下载地址:https://pan.baidu.com/s/1hrU75So 密码: k6nc

  5. angr工具的安装

    一.安装 python2 或 python3: pip install angr 目前官方不再维护python2版本,所以python2装的版本偏低,且很可能没有后续更新. 如果你选择python2版 ...

  6. Ubuntu 18.04.1 安装java8

    ###下载tar.gz 点击选择接收协议 下载完文件之后,将文件从Windows复制到ubuntu上,可以用xShell,putty,git.这里用git 下载安装git之后,再任意位置右击,选择 g ...

  7. 浅谈Linux环境下Socket选项的设置

    0.前言 TCP/IP协议栈是Linux内核的重要组成部分和网络编程的基石,虽然Linux和BSD有很大的联系,但是对于某些Socket选项和内核操作仍然存在差异,因此文中适用场景均为CentOS环境 ...

  8. numpy中的mean()函数

    本文链接:https://blog.csdn.net/lilong117194/article/details/78397329mean() 函数定义:numpy.mean(a, axis, dtyp ...

  9. transition 滑动动画

    html: <!-- 组件会在 `currentTabComponent` 改变时改变 --> <transition name="slide" mode=&qu ...

  10. 蓝牙BLE: GATT Profile 简介(GATT 与 GAP)

    一. 引言 现在低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上.GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很 ...