1. /*
  2. * @(#)MinimalHTMLWriter.java 1.9 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.html;
  8. import java.io.Writer;
  9. import java.io.IOException;
  10. import java.util.Enumeration;
  11. import java.awt.Color;
  12. import javax.swing.text.*;
  13. /**
  14. * MinimalHTMLWriter is a fallback writer used by the
  15. * HTMLEditorKit to write out HTML for a document that
  16. * is a not produced by the EditorKit.
  17. *
  18. * The format for the document is:
  19. * <html>
  20. * <head>
  21. * <style>
  22. * <!-- list of named styles
  23. * p.normal {
  24. * font-family: SansSerif;
  25. * margin-height: 0;
  26. * font-size: 14
  27. * }
  28. * -->
  29. * </style>
  30. * </head>
  31. * <body>
  32. * <p style=normal>
  33. * <b>bold, italic and underline attributes
  34. * of the run are emitted as html tags.
  35. * The remaining attributes are emitted as
  36. * part style attribute of a <font> tag.
  37. * The syntax is similar to inline styles.</b>
  38. * </p>
  39. * </body>
  40. * </html>
  41. *
  42. * @author Sunita Mani
  43. * @version 1.9, 11/29/01
  44. */
  45. public class MinimalHTMLWriter extends AbstractWriter {
  46. /**
  47. * These static finals are used to
  48. * tweak & query the fontMask about which
  49. * of these tags need to be generated, or
  50. * terminated.
  51. */
  52. private static final int BOLD = 0x01;
  53. private static final int ITALIC = 0x02;
  54. private static final int UNDERLINE = 0x04;
  55. // Used to map StyleConstants to CSS.
  56. private static final CSS css = new CSS();
  57. private int fontMask = 0;
  58. int startOffset = 0;
  59. int endOffset = 0;
  60. /**
  61. * stores the attributes of the previous run.
  62. * Used to compare with the current run's
  63. * attributeset. If identical, then a
  64. * <font> tag is not emitted.
  65. */
  66. private AttributeSet fontAttributes;
  67. /**
  68. * Creates a new MinimalHTMLWriter.
  69. *
  70. * @param a Writer
  71. * @param an StyledDocument
  72. *
  73. */
  74. public MinimalHTMLWriter(Writer w, StyledDocument doc) {
  75. super(w, doc);
  76. }
  77. /**
  78. * Creates a new MinimalHTMLWriter.
  79. *
  80. * @param a Writer
  81. * @param an StyledDocument
  82. * @param pos The location in the document to fetch the
  83. * content.
  84. * @param len The amount to write out.
  85. *
  86. */
  87. public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len) {
  88. super(w, doc, pos, len);
  89. }
  90. /**
  91. * This method is responsible for generating html output
  92. * from a StyledDocument.
  93. *
  94. * @exception IOException on any I/O error
  95. * @exception BadLocationException if pos represents an invalid
  96. * location within the document.
  97. *
  98. */
  99. public void write() throws IOException, BadLocationException {
  100. writeStartTag("<html>");
  101. writeHeader();
  102. writeBody();
  103. writeEndTag("</html>");
  104. }
  105. /**
  106. * This method writes out all the attributes that are for the
  107. * following types:
  108. * StyleConstants.ParagraphConstants
  109. * StyleConstants.CharacterConstants
  110. * StyleConstants.FontConstants
  111. * StyleConstants.ColorConstants.
  112. * The attribute name and value are separated by a colon
  113. * And each pair is separatd by a semicolon.
  114. *
  115. * @exception IOException on any I/O error
  116. */
  117. protected void writeAttributes(AttributeSet attr) throws IOException {
  118. Enumeration attributeNames = attr.getAttributeNames();
  119. while (attributeNames.hasMoreElements()) {
  120. Object name = attributeNames.nextElement();
  121. if ((name instanceof StyleConstants.ParagraphConstants) ||
  122. (name instanceof StyleConstants.CharacterConstants) ||
  123. (name instanceof StyleConstants.FontConstants) ||
  124. (name instanceof StyleConstants.ColorConstants)) {
  125. indent();
  126. write(name.toString());
  127. write(':');
  128. write(css.styleConstantsValueToCSSValue
  129. ((StyleConstants)name, attr.getAttribute(name)).
  130. toString());
  131. write(';');
  132. write(NEWLINE);
  133. }
  134. }
  135. }
  136. /**
  137. * This method is responsible for writing text out.
  138. *
  139. * @exception IOException on any I/O error
  140. */
  141. protected void text(Element elem) throws IOException, BadLocationException {
  142. String contentStr = getText(elem);
  143. if ((contentStr.length() > 0) &&
  144. (contentStr.charAt(contentStr.length()-1) == NEWLINE)) {
  145. contentStr = contentStr.substring(0, contentStr.length()-1);
  146. }
  147. if (contentStr.length() > 0) {
  148. write(contentStr);
  149. }
  150. }
  151. /**
  152. * This method writes out a start tag approrirately
  153. * indented. It also increments the indent level.
  154. *
  155. * @exception IOException on any I/O error
  156. */
  157. protected void writeStartTag(String tag) throws IOException {
  158. indent();
  159. write(tag);
  160. write(NEWLINE);
  161. incrIndent();
  162. }
  163. /**
  164. * This method writes out a end tag approrirately
  165. * indented. It also decrements the indent level.
  166. *
  167. * @exception IOException on any I/O error
  168. */
  169. protected void writeEndTag(String endTag) throws IOException {
  170. decrIndent();
  171. indent();
  172. write(endTag);
  173. write(NEWLINE);
  174. }
  175. /**
  176. * This method writes out the <head> and <style>
  177. * tags. It Then invokes writeStyles() to write
  178. * out all the named styles as the content of the
  179. * <style> tag. The content is surrounded by
  180. * valid html comment markers to ensure that the
  181. * document is viewable in applications/browsers
  182. * that do not support the tag.
  183. *
  184. * @exception IOException on any I/O error
  185. */
  186. protected void writeHeader() throws IOException {
  187. writeStartTag("<head>");
  188. writeStartTag("<style>");
  189. writeStartTag("<!--");
  190. writeStyles();
  191. writeEndTag("-->");
  192. writeEndTag("</style>");
  193. writeEndTag("</head>");
  194. }
  195. /**
  196. * This method writes out all the named styles as the
  197. * content of the <style> tag.
  198. *
  199. * @exception IOException on any I/O error
  200. */
  201. protected void writeStyles() throws IOException {
  202. /*
  203. * Access to DefaultStyledDocument done to workaround
  204. * a missing API in styled document to access the
  205. * stylenames.
  206. */
  207. DefaultStyledDocument styledDoc = ((DefaultStyledDocument)getDocument());
  208. Enumeration styleNames = styledDoc.getStyleNames();
  209. while (styleNames.hasMoreElements()) {
  210. Style s = styledDoc.getStyle((String)styleNames.nextElement());
  211. /** PENDING: Once the name attribute is removed
  212. from the list we check check for 0. **/
  213. if (s.getAttributeCount() == 1 &&
  214. s.isDefined(StyleConstants.NameAttribute)) {
  215. continue;
  216. }
  217. indent();
  218. write("p." + s.getName());
  219. write(" {\n");
  220. incrIndent();
  221. writeAttributes(s);
  222. decrIndent();
  223. indent();
  224. write("}\n");
  225. }
  226. }
  227. /**
  228. * This method iterates over the elements in the document
  229. * and processes elements based on whether they are
  230. * branch elements or leaf elements. It specially handles
  231. * leaf elements that are text.
  232. *
  233. * @exception IOException on any I/O error
  234. */
  235. protected void writeBody() throws IOException, BadLocationException {
  236. ElementIterator it = getElementIterator();
  237. /*
  238. This will be a section element for a styled document.
  239. We represent this element in html as the body tags.
  240. Therefore we ignore it.
  241. */
  242. it.current();
  243. Element next = null;
  244. writeStartTag("<body>");
  245. boolean inContent = false;
  246. while((next = it.next()) != null) {
  247. if (!inRange(next)) {
  248. continue;
  249. }
  250. if (next instanceof AbstractDocument.BranchElement) {
  251. if (inContent) {
  252. writeEndParagraph();
  253. inContent = false;
  254. fontMask = 0;
  255. }
  256. writeStartParagraph(next);
  257. } else if (isText(next)) {
  258. writeContent(next, !inContent);
  259. inContent = true;
  260. } else {
  261. writeLeaf(next);
  262. inContent = true;
  263. }
  264. }
  265. if (inContent) {
  266. writeEndParagraph();
  267. }
  268. writeEndTag("</body>");
  269. }
  270. /**
  271. * This method handles emiting an end tag for a <p>
  272. * tag. Prior to writing out the tag, it ensures
  273. * that all other tags that have been opened are
  274. * appropriately closed off.
  275. *
  276. * @exception IOException on any I/O error
  277. */
  278. protected void writeEndParagraph() throws IOException {
  279. writeEndMask(fontMask);
  280. if (inFontTag()) {
  281. endFontTag();
  282. } else {
  283. write(NEWLINE);
  284. }
  285. writeEndTag("</p>");
  286. }
  287. /**
  288. * This method emits the start tag for a paragraph. If
  289. * the paragraph has a named style associated with it,
  290. * then it also generates a class attribute for the
  291. * <p> tag and set's its value to be the name of the
  292. * style.
  293. *
  294. * @exception IOException on any I/O error
  295. */
  296. protected void writeStartParagraph(Element elem) throws IOException {
  297. AttributeSet attr = elem.getAttributes();
  298. Object resolveAttr = attr.getAttribute(StyleConstants.ResolveAttribute);
  299. if (resolveAttr instanceof StyleContext.NamedStyle) {
  300. writeStartTag("<p class=" + ((StyleContext.NamedStyle)resolveAttr).getName() + ">");
  301. } else {
  302. writeStartTag("<p>");
  303. }
  304. }
  305. /**
  306. * Responsible for writing out other non text leaf
  307. * elements.
  308. *
  309. * @exception IOException on any I/O error
  310. */
  311. protected void writeLeaf(Element elem) throws IOException {
  312. indent();
  313. if (elem.getName() == StyleConstants.IconElementName) {
  314. writeImage(elem);
  315. } else if (elem.getName() == StyleConstants.ComponentElementName) {
  316. writeComponent(elem);
  317. }
  318. }
  319. /**
  320. * Responsible for handling Icon Elements. This method is
  321. * deliberatly unimplemented. How to implement it is more
  322. * an issue of policy -- for example, if one was to generate
  323. * an <img> tag, the question does arise about how one would
  324. * represent the src attribute, i.e location of the image.
  325. * In certain cases it could be a url, in others it could
  326. * be read from a stream.
  327. *
  328. * @param an element fo type StyleConstants.IconElementName
  329. */
  330. protected void writeImage(Element elem) throws IOException {
  331. }
  332. /**
  333. * Responsible for handling Component Elements. How this
  334. * method is implemented is a matter of policy. Hence left
  335. * unimplemented.
  336. */
  337. protected void writeComponent(Element elem) throws IOException {
  338. }
  339. /**
  340. * Returns true if the element is a text element.
  341. *
  342. */
  343. protected boolean isText(Element elem) {
  344. return (elem.getName() == AbstractDocument.ContentElementName);
  345. }
  346. /**
  347. * This method handles writing out text. It invokes methods
  348. * that are responsible for writing out its attribute set
  349. * in a manner that is html compliant.
  350. *
  351. * @exception IOException on any I/O error
  352. * @exception BadLocationException if pos represents an invalid
  353. * location within the document.
  354. */
  355. protected void writeContent(Element elem, boolean needsIndenting)
  356. throws IOException, BadLocationException {
  357. AttributeSet attr = elem.getAttributes();
  358. writeNonHTMLAttributes(attr);
  359. if (needsIndenting) {
  360. indent();
  361. }
  362. writeHTMLTags(attr);
  363. text(elem);
  364. }
  365. /**
  366. * This method is responsible for generating
  367. * bold <b>, italics <i> and <u> tags for the
  368. * text based on its attribute settings.
  369. *
  370. * @exception IOException on any I/O error
  371. */
  372. protected void writeHTMLTags(AttributeSet attr) throws IOException {
  373. int oldMask = fontMask;
  374. setFontMask(attr);
  375. int endMask = 0;
  376. int startMask = 0;
  377. if ((oldMask & BOLD) != 0) {
  378. if ((fontMask & BOLD) == 0) {
  379. endMask |= BOLD;
  380. }
  381. } else if ((fontMask & BOLD) != 0) {
  382. startMask |= BOLD;
  383. }
  384. if ((oldMask & ITALIC) != 0) {
  385. if ((fontMask & ITALIC) == 0) {
  386. endMask |= ITALIC;
  387. }
  388. } else if ((fontMask & ITALIC) != 0) {
  389. startMask |= ITALIC;
  390. }
  391. if ((oldMask & UNDERLINE) != 0) {
  392. if ((fontMask & UNDERLINE) == 0) {
  393. endMask |= UNDERLINE;
  394. }
  395. } else if ((fontMask & UNDERLINE) != 0) {
  396. startMask |= UNDERLINE;
  397. }
  398. writeEndMask(endMask);
  399. writeStartMask(startMask);
  400. }
  401. /**
  402. * This method tweaks the appropriate bits of fontMask
  403. * to reflect whether the text is to be displayed in
  404. * bold, italics and/or with an underline.
  405. *
  406. */
  407. private void setFontMask(AttributeSet attr) {
  408. if (StyleConstants.isBold(attr)) {
  409. fontMask |= BOLD;
  410. }
  411. if (StyleConstants.isItalic(attr)) {
  412. fontMask |= ITALIC;
  413. }
  414. if (StyleConstants.isUnderline(attr)) {
  415. fontMask |= UNDERLINE;
  416. }
  417. }
  418. /**
  419. * Writes out start tags <u>, <i> and <b> based on
  420. * the mask settings.
  421. *
  422. * @exception IOException on any I/O error
  423. */
  424. private void writeStartMask(int mask) throws IOException {
  425. if (mask != 0) {
  426. if ((mask & UNDERLINE) != 0) {
  427. write("<u>");
  428. }
  429. if ((mask & ITALIC) != 0) {
  430. write("<i>");
  431. }
  432. if ((mask & BOLD) != 0) {
  433. write("<b>");
  434. }
  435. }
  436. }
  437. /**
  438. * Writes out end tags for <u>, <i> and <b> based on
  439. * the mask settings.
  440. *
  441. * @exception IOException on any I/O error
  442. */
  443. private void writeEndMask(int mask) throws IOException {
  444. if (mask != 0) {
  445. if ((mask & BOLD) != 0) {
  446. write("</b>");
  447. }
  448. if ((mask & ITALIC) != 0) {
  449. write("</i>");
  450. }
  451. if ((mask & UNDERLINE) != 0) {
  452. write("</u>");
  453. }
  454. }
  455. }
  456. /**
  457. * This method is responsible for writing out the remaining
  458. * character level attributes (i,e attributes other than bold
  459. * italics and underlie) in an html compliant way. Given that
  460. * attributes like font family, font size etc.. have no direct
  461. * mapping to html tags, a <font> tag is generated and its
  462. * style attribute is set to contain the list of remaining
  463. * attributes just like inline styles.
  464. *
  465. * @exception IOException on any I/O error
  466. */
  467. protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException {
  468. String style = "";
  469. String separator = "; ";
  470. if (inFontTag() && fontAttributes.isEqual(attr)) {
  471. return;
  472. }
  473. Color color = (Color)attr.getAttribute(StyleConstants.Foreground);
  474. if (color != null) {
  475. style += "color: " + css.styleConstantsValueToCSSValue
  476. ((StyleConstants)StyleConstants.Foreground,
  477. color) + separator;
  478. }
  479. Integer size = (Integer)attr.getAttribute(StyleConstants.FontSize);
  480. if (size != null) {
  481. style += "font-size: " + size.intValue() + separator;
  482. }
  483. String family = (String)attr.getAttribute(StyleConstants.FontFamily);
  484. if (family != null) {
  485. style += "font-family: " + family + separator;
  486. }
  487. if (style.length() > 0) {
  488. startFontTag(style);
  489. fontAttributes = attr;
  490. }
  491. }
  492. /**
  493. * Returns true if we are currently in a <font> tag.
  494. */
  495. protected boolean inFontTag() {
  496. return (fontAttributes != null);
  497. }
  498. /**
  499. * Writes out an end tag for the <font> tag.
  500. * @exception IOException on any I/O error
  501. */
  502. protected void endFontTag() throws IOException {
  503. write(NEWLINE);
  504. writeEndTag("</font>");
  505. fontAttributes = null;
  506. }
  507. /**
  508. * Writes out a start tag for the <font> tag.
  509. * Given that font tags cannot be nested, if
  510. * we are already in a font tag, it closes out
  511. * the enclosing tag, before writing out a
  512. * new start tag.
  513. *
  514. * @exception IOException on any I/O error
  515. */
  516. protected void startFontTag(String style) throws IOException {
  517. boolean callIndent = false;
  518. if (inFontTag()) {
  519. endFontTag();
  520. callIndent = true;
  521. }
  522. writeStartTag("<font style=\"" + style + "\">");
  523. if (callIndent) {
  524. indent();
  525. }
  526. }
  527. }