`
mmdev
  • 浏览: 13243415 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

将HTML转化为TEXT的Java类

阅读更多

为了支持全文检索,有必要将HTML格式的文章转化为纯文本格式,因此我设计了一个基本的WebFormatter类,提供一个简单的public static String html2text(String html),将HTML格式转化为Text:

/*
* File: WebFormatter.java
* Created on 2005-6-24
* Author: Liao Xuefeng, asklxf@163.com
* Copyright (C) 2005, Liao Xuefeng.
*/
package com.mboker.blog.web.util;

import java.util.*;
import java.text.SimpleDateFormat;

/**
* Do some format on web display.
*
* @author Xuefeng
*/
public class WebFormatter {

public static String html2text(String html) {
StringBuffer sb = new StringBuffer(html.length());
char[] data = html.toCharArray();
int start = 0;
boolean previousIsPre = false;
Token token = null;
for(;;) {
token = parse(data, start, previousIsPre);
if(token==null)
break;
previousIsPre = token.isPreTag();
sb = sb.append(token.getText());
start += token.getLength();
}
return sb.toString();
}

private static Token parse(char[] data, int start, boolean previousIsPre) {
if(start>=data.length)
return null;
// try to read next char:
char c = data[start];
if(c=='<') {
// this is a tag or comment or script:
int end_index = indexOf(data, start+1, '>');
if(end_index==(-1)) {
// the left is all text!
return new Token(Token.TOKEN_TEXT, data, start, data.length, previousIsPre);
}
String s = new String(data, start, end_index-start+1);
// now we got s="<...>":
if(s.startsWith("<!--")) { // this is a comment!
int end_comment_index = indexOf(data, start+1, "-->");
if(end_comment_index==(-1)) {
// illegal end, but treat as comment:
return new Token(Token.TOKEN_COMMENT, data, start, data.length, previousIsPre);
}
else
return new Token(Token.TOKEN_COMMENT, data, start, end_comment_index+3, previousIsPre);
}
String s_lowerCase = s.toLowerCase();
if(s_lowerCase.startsWith("<script")) { // this is a script:
int end_script_index = indexOf(data, start+1, "</script>");
if(end_script_index==(-1))
// illegal end, but treat as script:
return new Token(Token.TOKEN_SCRIPT, data, start, data.length, previousIsPre);
else
return new Token(Token.TOKEN_SCRIPT, data, start, end_script_index+9, previousIsPre);
}
else { // this is a tag:
return new Token(Token.TOKEN_TAG, data, start, start+s.length(), previousIsPre);
}
}
// this is a text:
int next_tag_index = indexOf(data, start+1, '<');
if(next_tag_index==(-1))
return new Token(Token.TOKEN_TEXT, data, start, data.length, previousIsPre);
return new Token(Token.TOKEN_TEXT, data, start, next_tag_index, previousIsPre);
}

private static int indexOf(char[] data, int start, String s) {
char[] ss = s.toCharArray();
// TODO: performance can improve!
for(int i=start; i<(data.length-ss.length); i++) {
// compare from data[i] with ss[0]:
boolean match = true;
for(int j=0; j<ss.length; j++) {
if(data[i+j]!=ss[j]) {
match = false;
break;
}
}
if(match)
return i;
}
return (-1);
}

private static int indexOf(char[] data, int start, char c) {
for(int i=start; i<data.length; i++) {
if(data[i]==c)
return i;
}
return (-1);
}

}

class Token {

public static final int TOKEN_TEXT = 0; // html text.
public static final int TOKEN_COMMENT = 1; // comment like <!-- comments... -->
public static final int TOKEN_TAG = 2; // tag like <pre>, <font>, etc.
public static final int TOKEN_SCRIPT = 3;

private static final char[] TAG_BR = "<br".toCharArray();
private static final char[] TAG_P = "<p".toCharArray();
private static final char[] TAG_LI = "<li".toCharArray();
private static final char[] TAG_PRE = "<pre".toCharArray();
private static final char[] TAG_HR = "<hr".toCharArray();

private static final char[] END_TAG_TD = "</td>".toCharArray();
private static final char[] END_TAG_TR = "</tr>".toCharArray();
private static final char[] END_TAG_LI = "</li>".toCharArray();

private static final Map SPECIAL_CHARS = new HashMap();

private int type;
private String html; // original html
private String text = null; // text!
private int length = 0; // html length
private boolean isPre = false; // isPre tag?

static {
SPECIAL_CHARS.put("&quot;", "\"");
SPECIAL_CHARS.put("&lt;", "<");
SPECIAL_CHARS.put("&gt;", ">");
SPECIAL_CHARS.put("&amp;", "&");
SPECIAL_CHARS.put("&reg;", "(r)");
SPECIAL_CHARS.put("&copy;", "(c)");
SPECIAL_CHARS.put("&nbsp;", " ");
SPECIAL_CHARS.put("&pound;", "?");
}

public Token(int type, char[] data, int start, int end, boolean previousIsPre) {
this.type = type;
this.length = end - start;
this.html = new String(data, start, length);
System.out.println("[Token] html=" + html + ".");
parseText(previousIsPre);
System.out.println("[Token] text=" + text + ".");
}

public int getLength() {
return length;
}

public boolean isPreTag() {
return isPre;
}

private void parseText(boolean previousIsPre) {
if(type==TOKEN_TAG) {
char[] cs = html.toCharArray();
if(compareTag(TAG_BR, cs) || compareTag(TAG_P, cs))
text = "\n";
else if(compareTag(TAG_LI, cs))
text = "\n* ";
else if(compareTag(TAG_PRE, cs))
isPre = true;
else if(compareTag(TAG_HR, cs))
text = "\n--------\n";
else if(compareString(END_TAG_TD, cs))
text = "\t";
else if(compareString(END_TAG_TR, cs) || compareString(END_TAG_LI, cs))
text = "\n";
}
// text token:
else if(type==TOKEN_TEXT) {
text = toText(html, previousIsPre);
}
}

public String getText() {
return text==null ? "" : text;
}

private String toText(String html, final boolean isPre) {
char[] cs = html.toCharArray();
StringBuffer buffer = new StringBuffer(cs.length);
int start = 0;
boolean continueSpace = false;
char current, next;
for(;;) {
if(start>=cs.length)
break;
current = cs[start]; // read current char
if(start+1<cs.length) // and next char
next = cs[start+1];
else
next = '\0';
if(current==' ') {
if(isPre || !continueSpace)
buffer = buffer.append(' ');
continueSpace = true;
// continue loop:
start++;
continue;
}
// not ' ', so:
if(current=='\r' && next=='\n') {
if(isPre)
buffer = buffer.append('\n');
// continue loop:
start+=2;
continue;
}
if(current=='\n' || current=='\r') {
if(isPre)
buffer = buffer.append('\n');
// continue loop:
start++;
continue;
}
// cannot continue space:
continueSpace = false;
if(current=='&') {
// maybe special char:
int length = readUtil(cs, start, ';', 10);
if(length==(-1)) { // just '&':
buffer = buffer.append('&');
// continue loop:
start++;
continue;
}
else { // check if special character:
String spec = new String(cs, start, length);
String specChar = (String)SPECIAL_CHARS.get(spec);
if(specChar!=null) { // special chars!
buffer = buffer.append(specChar);
// continue loop:
start+=length;
continue;
}
else { // check if like '&#1234':
if(next=='#') { // maybe a char
String num = new String(cs, start+2, length-3);
try {
int code = Integer.parseInt(num);
if(code>0 && code<65536) { // this is a special char:
buffer = buffer.append((char)code);
// continue loop:
start++;
continue;
}
}
catch(Exception e) {}
// just normal char:
buffer = buffer.append("&#");
// continue loop:
start+=2;
continue;
}
else { // just '&':
buffer = buffer.append('&');
// continue loop:
start++;
continue;
}
}
}
}
else { // just a normal char!
buffer = buffer.append(current);
// continue loop:
start++;
continue;
}
}
return buffer.toString();
}

// read from cs[start] util meet the specified char 'util',
// or null if not found:
private int readUtil(final char[] cs, final int start, final char util, final int maxLength) {
int end = start+maxLength;
if(end>cs.length)
end = cs.length;
for(int i=start; i<start+maxLength; i++) {
if(cs[i]==util) {
return i-start+1;
}
}
return (-1);
}

// compare standard tag "<input" with tag "<INPUT value=aa>"
private boolean compareTag(final char[] ori_tag, char[] tag) {
if(ori_tag.length>=tag.length)
return false;
for(int i=0; i<ori_tag.length; i++) {
if(Character.toLowerCase(tag[i])!=ori_tag[i])
return false;
}
// the following char should not be a-z:
if(tag.length>ori_tag.length) {
char c = Character.toLowerCase(tag[ori_tag.length]);
if(c<'a' || c>'z')
return true;
return false;
}
return true;
}

private boolean compareString(final char[] ori, char[] comp) {
if(ori.length>comp.length)
return false;
for(int i=0; i<ori.length; i++) {
if(Character.toLowerCase(comp[i])!=ori[i])
return false;
}
return true;
}

public String toString() {
return html;
}
}

注意,请先将html中的<body>...</body>部分提取出来,再交给WebFormatter处理,因为html->text转换实质是删除所有标签(某些标签如<br>被转化为'\n')、Script和注释,对于JavaScript生成的动态内容(例如document.write)无能为力。

分享到:
评论

相关推荐

    混合场景下大规模 GPU 集群构建与实践.pdf

    混合场景下大规模 GPU 集群构建与实践.pdf

    29 螺栓组联接成本优化设计.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    走向现代化数据分析架构:趋势与挑战.pdf

    走向现代化数据分析架构:趋势与挑战.pdf

    基于Matlab极化天线和目标之间的信号传输建模 matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    08-1 圆锥齿轮行星机构运动分析.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    金融大数据存储实践.pdf

    金融大数据存储实践.pdf

    (源码)基于SpringCloudAlibaba的系统管理平台.zip

    # 基于Spring Cloud Alibaba的系统管理平台 ## 项目简介 本项目是一个基于Spring Cloud Alibaba框架的系统管理平台,采用前后端分离的模式,集成了微服务架构。前端基于RuoYiVue框架,后端采用Spring Boot、Spring Cloud Alibaba技术栈。注册中心和配置中心选型Nacos,权限认证使用Redis,流量控制框架选型Sentinel。 ## 项目的主要特性和功能 1. 用户管理完成系统用户配置,支持用户增删改查、权限分配等功能。 2. 部门管理配置系统组织机构(公司、部门、小组),支持树结构展现和数据权限。 3. 岗位管理配置系统用户所属担任职务。 4. 菜单管理配置系统菜单,操作权限,按钮权限标识等。 5. 角色管理角色菜单权限分配、设置角色按机构进行数据范围权限划分。 6. 字典管理对系统中经常使用的一些较为固定的数据进行维护。 7. 参数管理对系统动态配置常用参数。

    这是来自于的c++考试题目

    这是来自于的c++考试题目

    (源码)基于Python和KissFFT的音频处理系统.zip

    # 基于Python和KissFFT的音频处理系统 ## 项目简介 本项目是一个基于Python和KissFFT库的音乐音频处理系统。它利用Python的简洁语法和强大的数据处理能力,结合KissFFT库的高效傅里叶变换算法,实现对音频数据的分析、处理和转换。项目主要目标是处理音乐音频数据,进行特征提取、频谱分析、频率转换等操作,以便于后续的音乐生成、分析和识别等任务。 ## 项目的主要特性和功能 音频数据加载与预处理项目支持从MIDI文件加载音乐数据,并提供数据过滤、编码等预处理功能,以便于后续处理。 FFT算法实现利用KissFFT库实现快速傅里叶变换(FFT)算法,用于音频信号的频谱分析和频率转换。 音频数据可视化提供可视化工具,用于展示音频信号的频谱分布、音符稀疏性等信息。 模型训练与评估支持模型训练与评估,包括自编码器、LSTM等模型,用于音乐生成、预测等任务。

    PADSLayout 常用封装库 ORCAD 常用原理图元器件库

    PADSLayout 常用封装库 ORCAD 常用原理图元器件库 常用的都用,喜欢的朋友们抓紧拿下

    云原生数据库管控探索和实践.pdf

    云原生数据库管控探索和实践.pdf

    yolo算法-人脸情绪数据集-1020张图像带标签-积极的.zip

    yolo系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值

    基于Matlab的变压器短路故障仿真模型

    在本文中,我们利用Matlab软件建立了一个简单的电力系统模型,该模型由电力系统模块及测量模块组成,能够模拟变压器故障,对不同故障进行仿真波形分析。

    毕业设计&课设_基于 RK3588 的边缘预警项目(C++)(含多种功能,有详细架构、编译及使用说明).zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    33 梯形截面管道结构尺寸的优化设计.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    Java期末大作业指南-涵盖矩阵乘法与GUI应用开发-综合评估

    内容概要:本文档为大学课程Java程序设计的期末大作业指导书,涵盖了多个不同难度的任务,旨在测试学生的Java编程能力以及对Java GUI的设计理解和运用水平。任务包括使用for循环计算指定数学表达式的值、实现两个已定义矩阵相乘的操作、利用Java GUI构建简易的功能型应用程序——一个可以执行基本算术操作的计算器、以及一个小而有趣的游戏——网球接球挑战游戏。通过解决这些问题,学生将加深对Java编程环境及其相关API的理解和掌握,提高自身的编码实践能力和创新思考空间。 适合人群:适用于正在进行Java学习阶段的学生、对提升个人编程技能感兴趣的初学者或希望巩固基础的专业开发者。 使用场景及目标:本项目作为一次全面考察学生技术实力的大作业形式存在,旨在促使同学们回顾所学知识并在实践中深化记忆。此外,还可以作为一种教学工具来帮助新手快速入门Java语言和图形界面设计的相关技巧。 其他说明:需要注意的是,在解答过程中遇到困难的同学可以通过QQ讨论组向教师求助;然而,为了确保作业的公平公正,教师不会直接给出解决方案的具体方法论。

    C#ASP.NET养老院老人信息管理系统源码数据库 SQL2008源码类型 WebForm

    ASP.NET养老院老人信息管理系统源码 这是一款非常优秀的养老院老人信息管理系统。程序功能齐全,可选择性强。 管理员后台: 1、人员信息管理:老人信息管理、管理员信息管理、员工信息管理 2、公寓信息管理:添加公寓楼信息、管理公寓楼信息、添加公寓信息、管理公寓信息 3、公寓安排管理:添加公寓安排、公寓安排管理、缴费管理 4、健康管理信息管理:添加健康管理信息、管理健康管理信息 5、紧急联络人管理 6、意见信息管理 7、公告信息管理:添加公告信息、管理公告信息 8、出入信息管理:请假信息管理、访客记录管理、退住情况 员工后台: 1、人员信息管理:老人信息管理 2、健康管理信息管理:管理健康管理信息 3、紧急联络人管理 4、意见信息管理 5、公告信息管理:管理公告信息 6、出入信息管理:请假信息管理、访客记录管理、退住情况 老人后台: 1、修改个人信息 2、修改登陆密码 3、意见信息管理:发布意见信息、意见信息管理 4、查看公告信息

    毕业设计&课设_基于用户协同过滤的商品推荐系统:含多种管理功能,用 Java 等技术实现.zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    Kettle 是Kettle E.T.T.L. Envirnonment只取首字母的缩写,这意味着它被设计用来帮助你实现你的

    什么是 Pentaho Pentaho Data Integration (PDI) 提供抽取、转换和加载 (ETL) 功能,使用统一的方式实现了一系列复杂的ETL过程。 如果你第一次接触 Pentaho,你可能会看到或听到 Pentaho 数据集成称为“Kettle”。Pentaho Data Integration 最初是一个名为“Kettle”的开源项目。术语 KETTLE 是一个递归术语,代表 Kettle Extraction Transformation Transport Load Environment。Pentaho 收购 Kettle 后,更名为 Pentaho Data Integration。其他 PDI 组件(例如Spoon、Pan和Kitchen)的名称最初是为了支持 ETL 产品的“烹饪”隐喻。 PDI 常见用途 PDI 客户端(也称为 Spoon)是一个桌面应用程序,使你能够构建转换以及安排和运行作业。 PDI 客户端的常见用途包括: 不同数据库和应用之间的数据迁移。 充分利用云、集群和大规模并行处理环境将大量数据集加载到数据库中。 数据清洗,步

Global site tag (gtag.js) - Google Analytics