1. package org.jr.text;
  2. /**
  3. * Copyright: Copyright (c) 2002-2004
  4. * Company: JavaResearch(http://www.javaresearch.org)
  5. * 最后更新日期:2004年11月30日
  6. * @author Cherami
  7. */
  8. import java.io.*;
  9. import java.util.*;
  10. import org.jr.util.*;
  11. /**
  12. * 将java源代码转换为HTML格式并且可以设置风格的工具类。
  13. * 这个类应该是一个可以反复使用的类,不需生成太多的类实例。
  14. * 用完以后重新设置其他的风格就可以继续使用。
  15. * 一般情况下构造完一个实例后要设置需要的参数,否则虽然对代码进行了处理,但是没有任何效果。
  16. * 当然也可以启用默认模式。
  17. * @since 0.5
  18. */
  19. public class JavaToHTML {
  20. public static final int NONTYPE = -1;
  21. public static final int WORD = 0;
  22. public static final int QUOTE = 1;
  23. public static final int LINE_COMMENT = 2;
  24. public static final int COMMENT = 3;
  25. protected static final TreeSet defaultReservedWords = TreeSetUtil.
  26. ArrayToTreeSet(StringUtil.split("abstract,break,byte,boolean,catch,case,class,char,continue,default,double,do,else,extends,false,final,float,for,finally,if,import,implements,int,interface,instanceof,long,length,native,new,null,package,private,protected,public,final,return,switch,synchronized,short,static,super,try,true,this,throw,throws,threadsafe,transient,void,volatile,while"));
  27. protected static final TreeSet defaultClassNames = ClassUtil.getAllClassname();
  28. protected static final HTMLStyle defaultReservedWordsStyle = new HTMLStyle(
  29. "<b><font color='#0000ff'>",
  30. "</font></b>");
  31. protected static final HTMLStyle defaultCommentStyle = new HTMLStyle(
  32. "<i><font color='#339900'>", "</font></i>");
  33. protected static final HTMLStyle defaultClassNameStyle = new HTMLStyle(
  34. "<font color='#ff0000'>", "</font>");
  35. protected static final HTMLStyle defaultQuoteStyle = new HTMLStyle(
  36. "<font color='#ff33ff'>", "</font>");
  37. protected TreeSet reservedWords;
  38. protected TreeSet classNames;
  39. protected HTMLStyle reservedWordsStyle;
  40. protected HTMLStyle commentStyle;
  41. protected HTMLStyle classNameStyle;
  42. protected HTMLStyle quoteStyle;
  43. protected int currentType = NONTYPE;
  44. protected boolean showLineNumber = true;
  45. protected String currentLine;
  46. protected int currentIndex;
  47. protected ArrayList importedClasses=new ArrayList();
  48. protected boolean writePre=true;
  49. private final static int HEAD = 0;
  50. private final static int LINE = 1;
  51. private final static int END = 2;
  52. private static String LINE_SEPARATOR=Constants.LINE_SEPARATOR;
  53. /**
  54. * 构造一个JavaToHTML。
  55. * @since 0.5
  56. */
  57. public JavaToHTML() {
  58. reservedWords = new TreeSet();
  59. classNames = new TreeSet();
  60. reservedWordsStyle = new HTMLStyle();
  61. commentStyle = new HTMLStyle();
  62. classNameStyle = new HTMLStyle();
  63. quoteStyle = new HTMLStyle();
  64. }
  65. /**
  66. * 根据指定的参数构造一个JavaToHTML。
  67. * @param useDefault 是否使用缺省设置
  68. * @since 0.5
  69. */
  70. public JavaToHTML(boolean useDefault) {
  71. if (useDefault == true) {
  72. useDefaultSet();
  73. }
  74. else {
  75. reservedWords = new TreeSet();
  76. classNames = new TreeSet();
  77. reservedWordsStyle = new HTMLStyle();
  78. commentStyle = new HTMLStyle();
  79. classNameStyle = new HTMLStyle();
  80. quoteStyle = new HTMLStyle();
  81. }
  82. }
  83. /**
  84. * 设置使用转换器的缺省设置。
  85. * @since 0.5
  86. */
  87. public void useDefaultSet() {
  88. reservedWords = defaultReservedWords;
  89. classNames = defaultClassNames;
  90. reservedWordsStyle = defaultReservedWordsStyle;
  91. commentStyle = defaultCommentStyle;
  92. classNameStyle = defaultClassNameStyle;
  93. quoteStyle = defaultQuoteStyle;
  94. }
  95. /**
  96. * 设置是否显示行号。
  97. * @param lineNumberVisible 是否显示行号,true的时候显示行号
  98. * @since 0.5
  99. */
  100. public void setLineNumberVisible(boolean lineNumberVisible) {
  101. showLineNumber = lineNumberVisible;
  102. }
  103. /**
  104. * 设置保留字。
  105. * @param reservedWords 保留字
  106. * @since 0.5
  107. */
  108. public void setReservedWords(TreeSet reservedWords) {
  109. this.reservedWords = reservedWords;
  110. }
  111. /**
  112. * 设置已知类的类名。
  113. * @param classNames 已知类的类名
  114. * @since 0.5
  115. */
  116. public void setClassNames(TreeSet classNames) {
  117. this.classNames = classNames;
  118. }
  119. /**
  120. * 设置保留字的风格。
  121. * @param reservedWordsStyle 保留字的风格
  122. * @since 0.5
  123. */
  124. public void setReservedWordsStyle(HTMLStyle reservedWordsStyle) {
  125. this.reservedWordsStyle = reservedWordsStyle;
  126. }
  127. /**
  128. * 设置注释的风格。
  129. * @param commentStyle 注释的风格
  130. * @since 0.5
  131. */
  132. public void setCommentStyle(HTMLStyle commentStyle) {
  133. this.commentStyle = commentStyle;
  134. }
  135. /**
  136. * 设置已知类名的风格。
  137. * @param classNameStyle 已知类名的风格
  138. * @since 0.5
  139. */
  140. public void setClassNameStyle(HTMLStyle classNameStyle) {
  141. this.classNameStyle = classNameStyle;
  142. }
  143. /**
  144. * 设置字符串的风格。
  145. * @param quoteStyle 字符串的风格
  146. * @since 0.5
  147. */
  148. public void setQuoteStyle(HTMLStyle quoteStyle) {
  149. this.quoteStyle = quoteStyle;
  150. }
  151. /**
  152. * 设置是否将代码的输出用<PRE>标签包含起来。
  153. * @param writePre true则用<PRE>标签包含
  154. */
  155. public void setWritePre(boolean writePre) {
  156. this.writePre = writePre;
  157. }
  158. /**
  159. * 将HTML标签的'<'和'>'转换为对应的转义文本"<"和">"。
  160. * @param source 原始文本
  161. * @return 转换后的结果
  162. * @since 0.5
  163. */
  164. public static String escapeHTMLtag(String source) {
  165. String result = source.replaceAll("<", "<");
  166. result = result.replaceAll(">", ">");
  167. return result;
  168. }
  169. /**
  170. * 将单词转换为具有风格的结果。
  171. * 在0.6版中,根据java2html项目的要求提高此方法的访问权限为protected便于子类覆盖。
  172. * @param word 原始的单词
  173. * @return 转换后的结果
  174. * @since 0.5
  175. */
  176. protected String convertWord(String word) {
  177. if (reservedWords.contains(word)) {
  178. return reservedWordsStyle.getBegin() + word + reservedWordsStyle.getEnd();
  179. }
  180. else if (classNames.contains(word)) {
  181. return classNameStyle.getBegin() + word + classNameStyle.getEnd();
  182. }
  183. else {
  184. return word;
  185. }
  186. }
  187. /**
  188. * 将一行内容转换为具有风格的内容。
  189. * @param line 一行文本
  190. * @return 转换后的结果
  191. */
  192. private String convertLine(String line) {
  193. if (line.length() == 0) {
  194. return "";
  195. }
  196. addImportedContent(line);
  197. currentLine=line;
  198. StringBuffer lineBuffer = new StringBuffer(line.length() * 2);
  199. StringBuffer wordBuffer = new StringBuffer();
  200. int start = 0;
  201. int end = line.length() - 1;
  202. char[] contents = line.toCharArray();
  203. //循环判断一行中的最后一个字符前的字符状况
  204. for (int i = start; i < end; i++) {
  205. //如果没有当前类型
  206. if (currentType == NONTYPE) {
  207. //如果是合法的java字符开始标识符则将类型设置为单词类型并将当前字符添加到单词缓冲区
  208. if (Character.isJavaIdentifierStart(contents[i])) {
  209. currentType = WORD;
  210. wordBuffer.append(contents[i]);
  211. continue;
  212. }
  213. //如果是可能的注释开始
  214. else if (contents[i] == '/') {
  215. char nextChar = contents[i + 1];
  216. switch (nextChar) {
  217. //如果下一个字符是'*'则表明是多行注释,将类型设置为多行字符并添加对应的字体颜色及字符
  218. case '*':
  219. currentType = COMMENT;
  220. lineBuffer.append(commentStyle.getBegin());
  221. lineBuffer.append(contents[i]);
  222. continue;
  223. //如果下一个字符是'/'则表明是单行注释,添加对应的字体颜色并直接取字符串的剩余内容
  224. case '/':
  225. lineBuffer.append(commentStyle.getBegin());
  226. lineBuffer.append(line.substring(i));
  227. lineBuffer.append(commentStyle.getEnd());
  228. return lineBuffer.toString();
  229. //不是注释的开始则直接添加内容
  230. default:
  231. lineBuffer.append(contents[i]);
  232. continue;
  233. }
  234. }
  235. //如果是双引号
  236. else if (contents[i] == '\"') {
  237. //如果是最开始的位置或者其前一个字符不是'\'则表明是一个引用的开始,设置类型并添加对应的字体颜色及字符
  238. if (i == 0 || contents[i - 1] != '\\') {
  239. currentType = QUOTE;
  240. lineBuffer.append(quoteStyle.getBegin());
  241. lineBuffer.append(contents[i]);
  242. continue;
  243. }
  244. //否则不是引用的内容,添加内容
  245. else {
  246. lineBuffer.append(contents[i]);
  247. continue;
  248. }
  249. }
  250. //如果不是以上情况则表明是其他的杂项内容,不进行处理,将字符添加到缓冲区
  251. else {
  252. lineBuffer.append(contents[i]);
  253. continue;
  254. }
  255. }
  256. //如果当前类型是单词
  257. else if (currentType == WORD) {
  258. //如果是一个java标识符的合法部分表明单词没有结束,将当前字符添加到单词缓冲区
  259. if (Character.isJavaIdentifierPart(contents[i])) {
  260. wordBuffer.append(contents[i]);
  261. continue;
  262. }
  263. //否则表明单词结束,将单词缓冲区的内容进行转换并添加到缓冲区并继续判断后面部分的语法成分
  264. else {
  265. currentIndex=i-wordBuffer.length();
  266. lineBuffer.append(convertWord(wordBuffer.toString()));
  267. wordBuffer = new StringBuffer();
  268. //如果是可能的注释开始
  269. if (contents[i] == '/') {
  270. char nextChar = contents[i + 1];
  271. switch (nextChar) {
  272. //如果下一个字符是'*'则表明是多行注释,将类型设置为多行字符并添加对应的字体颜色及字符
  273. case '*':
  274. currentType = COMMENT;
  275. lineBuffer.append(commentStyle.getBegin());
  276. lineBuffer.append(contents[i]);
  277. continue;
  278. //如果下一个字符是'/'则表明是单行注释,添加对应的字体颜色并直接取字符串的剩余内容
  279. case '/':
  280. lineBuffer.append(commentStyle.getBegin());
  281. lineBuffer.append(line.substring(i));
  282. lineBuffer.append(commentStyle.getEnd());
  283. currentType = NONTYPE;
  284. return lineBuffer.toString();
  285. }
  286. }
  287. //如果是双引号则表明是一个引用的开始,设置类型并添加对应的字体颜色及字符
  288. else if (contents[i] == '\"') {
  289. currentType = QUOTE;
  290. lineBuffer.append(quoteStyle.getBegin());
  291. lineBuffer.append(contents[i]);
  292. continue;
  293. }
  294. //如果不是以上情况则表明是其他的杂项内容,不进行处理,将字符添加到缓冲区
  295. else {
  296. currentType = NONTYPE;
  297. lineBuffer.append(contents[i]);
  298. continue;
  299. }
  300. }
  301. }
  302. //如果当前类型是引用
  303. else if (currentType == QUOTE) {
  304. //如果当前字符不是引号或者如果是引号但是他的前一个字符是'\'则表明是引用的内容,添加内容
  305. if (contents[i] != '\"' || contents[i - 1] == '\\') {
  306. lineBuffer.append(contents[i]);
  307. continue;
  308. }
  309. //否则表明是引用的结束,添加内容并结束字体设置
  310. else {
  311. lineBuffer.append(contents[i]);
  312. lineBuffer.append(quoteStyle.getEnd());
  313. currentType = NONTYPE;
  314. continue;
  315. }
  316. }
  317. //如果当前类型是多行注释
  318. else if (currentType == COMMENT) {
  319. //如果是第一个字符则表明是多行注释的继续的一行,添加字体设置
  320. if (i == 0) {
  321. lineBuffer.append(commentStyle.getBegin());
  322. }
  323. //如果当前字符是'/'并且他的前一个字符是'*'则表明是注释的结束,添加内容并结束字体设置
  324. if (contents[i] == '/' && i != 0 && contents[i - 1] == '*') {
  325. lineBuffer.append(contents[i]);
  326. lineBuffer.append(commentStyle.getEnd());
  327. currentType = NONTYPE;
  328. continue;
  329. }
  330. //否则表明不是注释的结束,添加内容
  331. else {
  332. lineBuffer.append(contents[i]);
  333. continue;
  334. }
  335. }
  336. }
  337. //对最后一个字符进行处理并结束本行的转换
  338. char endChar = contents[end];
  339. //如果是没有任何类型,最后一个字符不能构成任何特殊类,直接添加内容
  340. if (currentType == NONTYPE) {
  341. lineBuffer.append(endChar);
  342. return lineBuffer.toString();
  343. }
  344. //如果是单词
  345. else if (currentType == WORD) {
  346. //如果最后一个字符是标识符的一部分,添加内容并结束风格设置
  347. if (Character.isJavaIdentifierPart(endChar)) {
  348. wordBuffer.append(endChar);
  349. currentIndex=line.length()-wordBuffer.length();
  350. lineBuffer.append(convertWord(wordBuffer.toString()));
  351. wordBuffer = new StringBuffer();
  352. }
  353. //否则表明单词已经结束,转换单词后作为普通内容添加。
  354. else {
  355. currentIndex=line.length()-wordBuffer.length()-1;//XXXXXX
  356. lineBuffer.append(convertWord(wordBuffer.toString()));
  357. wordBuffer = new StringBuffer();
  358. lineBuffer.append(endChar);
  359. }
  360. currentType = NONTYPE;
  361. return lineBuffer.toString();
  362. }
  363. //如果是引用,由于字符串不能跨行,因此结束字符串引用不会有问题。
  364. else if (currentType == QUOTE) {
  365. lineBuffer.append(endChar);
  366. lineBuffer.append(quoteStyle.getEnd());
  367. currentType = NONTYPE;
  368. return lineBuffer.toString();
  369. }
  370. else {
  371. //如果最后一个字符是'/'并且他的前一个字符是'*'则表明是注释的结束,添加内容并结束字体设置
  372. if (endChar == '/' && contents[end - 1] == '*') {
  373. lineBuffer.append(endChar);
  374. lineBuffer.append(commentStyle.getEnd());
  375. currentType = NONTYPE;
  376. }
  377. //否则表明不是注释的结束,添加内容
  378. else {
  379. lineBuffer.append(endChar);
  380. lineBuffer.append(commentStyle.getEnd());
  381. }
  382. return lineBuffer.toString();
  383. }
  384. }
  385. public static void setLineSeparater(String lineSeparator) {
  386. LINE_SEPARATOR=lineSeparator;
  387. }
  388. /**
  389. * 将指定的缓冲阅读器中的内容读出进行转换并将转换后的结果写入记录器。
  390. * 之所以需要缓冲阅读器是因为在内部是以行为单位进行处理的。
  391. * @param reader 阅读器
  392. * @param writer 记录器
  393. * @since 0.5
  394. */
  395. public void convert(BufferedReader reader, Writer writer) {
  396. try {
  397. importedClasses.removeAll(importedClasses);
  398. if (writePre) {
  399. writer.write("<pre>");
  400. }
  401. writeLineNumber(writer, HEAD);
  402. String line = reader.readLine();
  403. while (line != null) {
  404. writeLineNumber(writer, LINE);
  405. writer.write(convertLine(escapeHTMLtag(line)));
  406. writer.write(LINE_SEPARATOR);
  407. line = reader.readLine();
  408. }
  409. writeLineNumber(writer, END);
  410. if (writePre) {
  411. writer.write("</pre>");
  412. }
  413. }
  414. catch (IOException e) {
  415. }
  416. }
  417. /**
  418. * 记录器写入行号控制部分。
  419. * @param writer 记录器
  420. * @param type 行号控制类型
  421. * @throws IOException
  422. */
  423. private void writeLineNumber(Writer writer, int type) throws IOException {
  424. if (showLineNumber == true) {
  425. switch (type) {
  426. case HEAD:
  427. writer.write("<ol>");
  428. break;
  429. case LINE:
  430. writer.write("<li>");
  431. break;
  432. case END:
  433. writer.write("</ol>");
  434. break;
  435. }
  436. }
  437. }
  438. private void addImportedContent(String line) {
  439. if (line.length() == 0) {
  440. return;
  441. }
  442. line=line.trim();
  443. if (line.startsWith("import ")) {
  444. StringBuffer buffer=new StringBuffer();
  445. boolean hasStart=false;
  446. for (int i="import ".length();i<line.length();i++) {
  447. char c=line.charAt(i);
  448. if (hasStart==false) {
  449. if (Character.isJavaIdentifierStart(c)) {
  450. buffer.append(c);
  451. hasStart=true;
  452. } else {
  453. continue;
  454. }
  455. } else {
  456. if (Character.isJavaIdentifierPart(c)||c=='.') {
  457. buffer.append(c);
  458. } else {
  459. break;
  460. }
  461. }
  462. }
  463. if (hasStart) {
  464. importedClasses.add(buffer.toString().replace('.','/'));
  465. }
  466. }
  467. }
  468. }