/* ==================================================================
* Created [2009-4-27 下午11:32:55] by Jon.King
* ==================================================================
* TSS
* ==================================================================
* mailTo:jinpujun@hotmail.com
* Copyright (c) Jon.King, 2009-2012
* ==================================================================
*/
package com.jinhe.tss.portal.engine.releasehtml;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.filters.LinkRegexFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import com.jinhe.tss.core.exception.BusinessException;
import com.jinhe.tss.core.util.FileHelper;
import com.jinhe.tss.portal.EnvionmentVariables;
/**
* <p> IssueRobot.java </p>
* 发布机器人。
* 从首页开始发布整个站点。
*
* TODO 存在的问题:1、页面多了会缓存溢出,考虑多线程
* 2、新开一个HttpUrlConnection,里面的token值如何去掉?
*/
public class PerfectMagicRobot extends SimpleRobot{
private final static String RESOURCES_PATH_NAME = "resources";
// 整站发布时后缀统一为htm,防止和单个发布出来的页面冲突
protected final static String HTM_FILE_SUFFIX = ".htm";
private final static String PAGE_ISSUING_TAG = "processing";
/**
* Portal页面访问路径 对 发布后的静态路径,发布过程中(发布路径未生成)前,值一律为”processing“
*/
private Map<String, String> urlMapping = new HashMap<String, String>();
/**
* 发布成功生成的html页面名
*/
private List<String> htmlFiles = new ArrayList<String>();
public PerfectMagicRobot(Long portalId, String indexPage){
super(indexPage);
}
/**
* 从首页开始静态发布
*/
public void start() {
//执行抓取页面过程
try{
//从首页开始发布
excute(EnvionmentVariables.getContextPath() + "/" + indexPage);
for( String htmlFile : htmlFiles ){
replaceUrl(htmlFile); // 替换页面中的链接
}
}catch (ParserException e) {
throw new BusinessException("用htmlParser解析时候出错", e);
}
}
/**
* 执行页面的抓取。
* 首先抓取页面内容到本地,然后再将页面中在抓取页面中的地址。
* 递归循环,一直抓取下去。
*
* 以下几种情况的链接地址除外:
* 1."http://"打头的,这个一般为门户以外的链接
* 2、javascript打头的,这种目前难以处理
* 3、"#"打头的或者为空的
* @param href
* @throws ParserException
*/
private void excute(String href) throws ParserException {
if(href == null || href.startsWith("http://")
|| href.startsWith("javascript")|| href.startsWith("#"))
return;
String pageUrl = (String) urlMapping.get(href);
if (pageUrl != null)
return;
//将要发布的地址设置为正在发布状态,如此其它地方有相同的地址时可以跳过,以免重复发布
urlMapping.put(href, PAGE_ISSUING_TAG);
String url;
if(!href.startsWith(EnvionmentVariables.getContextPath()))
url = contextPath + EnvionmentVariables.getContextPath() + "/" + href;
else
url = contextPath + href;
//生成文件名
String htmlFileName = issuePath + genPageName(url, HTML_FILE_SUFFIX);
//下载页面
IssueHelper.saveUrlAsLocalFile(url, htmlFileName);
//处理页面里的动态地址,发布出相应的静态页面。
Parser parser = new Parser(htmlFileName);
parser.setEncoding("GBK");
NodeList list = parser.parse(new LinkRegexFilter(""));
for (NodeIterator it = list.elements(); it.hasMoreNodes();) {
LinkTag linkNode = (LinkTag) it.nextNode();
excute(linkNode.getAttribute("href"));
}
htmlFiles.add(htmlFileName);
urlMapping.put(href, htmlFileName);
}
/**
* 替换各种类型的地址,包括<a href=""></a>, <img src=""/>, script, css, 普通link等
* @param htmlFilePath
* @throws ParserException
*/
private void replaceUrl(String htmlFilePath) throws ParserException{
StringBuffer sb = new StringBuffer();
Parser parser = new Parser (htmlFilePath);
parser.setEncoding("GBK");
NodeList list = parser.parse(null);
for(NodeIterator outIter = list.elements(); outIter.hasMoreNodes();){
Node bigNode = outIter.nextNode();
replaceDynamicUrl(bigNode);
replaceOtherResourcesUrl(bigNode);
sb.append(bigNode.toHtml());
}
if(sb.indexOf("<style>") == -1)
return;
/**
* <style>
* ......
* #subMenu {
* BACKGROUND-IMAGE: url(/pms/pms/model/portlet/bannerjdh30/submenu_bg.gif); BORDER-BOTTOM: #fff
* }
* ......
</style>
*/
String styleCode = replaceUrlInStyleCode(sb.substring(sb.indexOf("<style>") + 8, sb.indexOf("</style>")));
sb.delete(sb.indexOf("<style>") + 8, sb.indexOf("</style>"));
sb.insert(sb.indexOf("<style>") + 8, styleCode);
FileHelper.writeFile(new File(htmlFilePath), sb.toString());
}
/**
* 动态内容发布成静态后,将html中的动态的链接地址换成相应生成html页面地址。
* @param htmlFilePath
*/
private void replaceDynamicUrl(Node bigNode) throws ParserException{
NodeList linkElements = new NodeList();
bigNode.collectInto(linkElements, new LinkRegexFilter(""));
for(NodeIterator inIter = linkElements.elements(); inIter.hasMoreNodes();){
LinkTag linkNode = (LinkTag) inIter.nextNode();
String url = linkNode.getAttribute("href");
String pageUrl = (String) urlMapping.get(url);
if(pageUrl != null){
linkNode.setAttribute("href", pageUrl);
}
}
}
/**
* 将html中所有的图片、js、css、flash等链接地址改掉,同时将相应的文件复制到发布的文件夹的资源文件夹下
* css: <link href="/pms/pms/model/portal/11804954453901181187246562_41/css.css" rel="stylesheet" type="text/css">
* js : <script language="javascript" src=""/pms/core/js/common.js""></script>
* pic: <img src="/pms/pms/model/decorator/tb74/bg_menu.jpg" border="0">
* or
* js: picTitle.src="/pms/pms/model/decorator/tb74/bg_menu.jpg";
* or
* css: url("/pms/pms/model/portlet/zb70/bg_login.jpg");
*
* CMS:
* <IMG src="http://localhost:8088/cms/download.fun?id=123&seqNo=1">
* <PARAM NAME="Movie" VALUE="http://localhost:8088/cms/download.fun?id=124&seqNo=1">
*
* 处理js,css文件地址
* 处理pms组件用到的图片,flash等地址
* 处理CMS文章的图片以及附件地址(包括CMS本地的和网络上的地址)
*
* TODO 处理UMS(可能比如用户头像等)的地址 及
* <td style="background-image:url(/pms/pms/model/decorator/shyzhjwzhlbxshq28/title_tbbd.jpg)>
*
* @param bigNode
* @throws ParserException
*/
private void replaceOtherResourcesUrl(Node bigNode) throws ParserException {
List<String> list = doReplace(bigNode, "link", "href");
doReplace(bigNode, "script", "src");
doReplace(bigNode, "img", "src");
doReplace(bigNode, "td", "background");
doReplace(bigNode, "tr", "background");
doReplace(bigNode, "table", "background");
doReplace(bigNode, "PARAM", "VALUE");
doReplace(bigNode, "embed", "src");
//处理外挂css文件中引用到的图片地址
for( String cssFilePath : list ){
File cssFile = new File(cssFilePath);
String cssContent = FileHelper.readFile(cssFile);
FileHelper.writeFile(cssFile, replaceUrlInStyleCode(cssContent));
}
}
/**
* 处理html中指定名称的标签下的相应属性名(该属性和文件相关)。
* 下载属性对应的附近,并替换成下载后的地址。
*
* @param bigNode html页面
* @param tagName 标签,想table,td,tr,script,img等。
* @param attributeName 标签的属性名 像background、src、href、VALUE等
* @return
* @throws ParserException
*/
private List<String> doReplace(Node bigNode, String tagName, String attributeName) throws ParserException{
List<String> list = new ArrayList<String>();
NodeList linkElements = new NodeList();
bigNode.collectInto(linkElements, new TagNameFilter(tagName));
for(NodeIterator iter = linkElements.elements(); iter.hasMoreNodes();){
TagNode tagNode = (TagNode) iter.nextNode();
String src = tagNode.getAttribute(attributeName);
if(src == null)
continue;
String fileName = null, sourceUrl = null;
if(src.startsWith("/")){ //PMS应用里的文件
fileName = src.substring(1);
sourceUrl = contextPath + "/" + fileName;
}else if(src.startsWith("http://")){ //PMS应用外的文件
fileName = System.currentTimeMillis() + src.substring(src.lastIndexOf("."));
sourceUrl = src;
}
String localFile = issuePath + RESOURCES_PATH_NAME + "/" + fileName;
if(IssueHelper.saveUrlAsLocalFile(sourceUrl, localFile))
tagNode.setAttribute(attributeName, RESOURCES_PATH_NAME + "/" + fileName);
list.add(localFile);
}
return list;
}
/**
* 替换页面中样式style code中的用到的文件(一般为图片)的地址,需要遍历整段style代码。
* 格式为 BACKGROUND-IMAGE: url(/pms/pms/model/portlet/bannerjdh30/submenu_bg.gif);
*
* TODO 当链接过来的css外挂脚本中可能是类似:url(menu_bg.gif),也就是图片根css文件同目录下的。
*
* @param styleCode
* @return
*/
private String replaceUrlInStyleCode(String styleCode){
StringBuffer sb = new StringBuffer();
int previewIndex = 0;
int currentIndex = 0;
while((currentIndex = styleCode.indexOf("url(", previewIndex)) != -1){
currentIndex += 4;
sb.append(styleCode.substring(previewIndex, currentIndex));
previewIndex = styleCode.indexOf(")", currentIndex);
String url = styleCode.substring(currentIndex, previewIndex);
//去掉url中头和尾的单引号或双引号
if(url.startsWith("'") || url.startsWith("\"")){
url = url.substring(1, url.length() - 1);
}
String localFile = issuePath + "/" + RESOURCES_PATH_NAME + "/" + url;
IssueHelper.saveUrlAsLocalFile(contextPath + "/" + url, localFile);
sb.append(RESOURCES_PATH_NAME + "/" + url);
}
sb.append(styleCode.substring(previewIndex));
return sb.toString();
}
}