1. /*
  2. * @(#)MinimalHTMLWriter.java 1.14 03/12/19
  3. *
  4. * Copyright 2004 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.*;
  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. * <pre>
  20. * <html>
  21. * <head>
  22. * <style>
  23. * <!-- list of named styles
  24. * p.normal {
  25. * font-family: SansSerif;
  26. * margin-height: 0;
  27. * font-size: 14
  28. * }
  29. * -->
  30. * </style>
  31. * </head>
  32. * <body>
  33. * <p style=normal>
  34. * <b>Bold, italic, and underline attributes
  35. * of the run are emitted as HTML tags.
  36. * The remaining attributes are emitted as
  37. * part of the style attribute of a <span> tag.
  38. * The syntax is similar to inline styles.</b>
  39. * </p>
  40. * </body>
  41. * </html>
  42. * </pre>
  43. *
  44. * @author Sunita Mani
  45. * @version 1.14, 12/19/03
  46. */
  47. public class MinimalHTMLWriter extends AbstractWriter {
  48. /**
  49. * These static finals are used to
  50. * tweak and query the fontMask about which
  51. * of these tags need to be generated or
  52. * terminated.
  53. */
  54. private static final int BOLD = 0x01;
  55. private static final int ITALIC = 0x02;
  56. private static final int UNDERLINE = 0x04;
  57. // Used to map StyleConstants to CSS.
  58. private static final CSS css = new CSS();
  59. private int fontMask = 0;
  60. int startOffset = 0;
  61. int endOffset = 0;
  62. /**
  63. * Stores the attributes of the previous run.
  64. * Used to compare with the current run's
  65. * attributeset. If identical, then a
  66. * <span> tag is not emitted.
  67. */
  68. private AttributeSet fontAttributes;
  69. /**
  70. * Maps from style name as held by the Document, to the archived
  71. * style name (style name written out). These may differ.
  72. */
  73. private Hashtable styleNameMapping;
  74. /**
  75. * Creates a new MinimalHTMLWriter.
  76. *
  77. * @param w Writer
  78. * @param doc StyledDocument
  79. *
  80. */
  81. public MinimalHTMLWriter(Writer w, StyledDocument doc) {
  82. super(w, doc);
  83. }
  84. /**
  85. * Creates a new MinimalHTMLWriter.
  86. *
  87. * @param w Writer
  88. * @param doc StyledDocument
  89. * @param pos The location in the document to fetch the
  90. * content.
  91. * @param len The amount to write out.
  92. *
  93. */
  94. public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len) {
  95. super(w, doc, pos, len);
  96. }
  97. /**
  98. * Generates HTML output
  99. * from a StyledDocument.
  100. *
  101. * @exception IOException on any I/O error
  102. * @exception BadLocationException if pos represents an invalid
  103. * location within the document.
  104. *
  105. */
  106. public void write() throws IOException, BadLocationException {
  107. styleNameMapping = new Hashtable();
  108. writeStartTag("<html>");
  109. writeHeader();
  110. writeBody();
  111. writeEndTag("</html>");
  112. }
  113. /**
  114. * Writes out all the attributes for the
  115. * following types:
  116. * StyleConstants.ParagraphConstants,
  117. * StyleConstants.CharacterConstants,
  118. * StyleConstants.FontConstants,
  119. * StyleConstants.ColorConstants.
  120. * The attribute name and value are separated by a colon.
  121. * Each pair is separated by a semicolon.
  122. *
  123. * @exception IOException on any I/O error
  124. */
  125. protected void writeAttributes(AttributeSet attr) throws IOException {
  126. Enumeration attributeNames = attr.getAttributeNames();
  127. while (attributeNames.hasMoreElements()) {
  128. Object name = attributeNames.nextElement();
  129. if ((name instanceof StyleConstants.ParagraphConstants) ||
  130. (name instanceof StyleConstants.CharacterConstants) ||
  131. (name instanceof StyleConstants.FontConstants) ||
  132. (name instanceof StyleConstants.ColorConstants)) {
  133. indent();
  134. write(name.toString());
  135. write(':');
  136. write(css.styleConstantsValueToCSSValue
  137. ((StyleConstants)name, attr.getAttribute(name)).
  138. toString());
  139. write(';');
  140. write(NEWLINE);
  141. }
  142. }
  143. }
  144. /**
  145. * Writes out text.
  146. *
  147. * @exception IOException on any I/O error
  148. */
  149. protected void text(Element elem) throws IOException, BadLocationException {
  150. String contentStr = getText(elem);
  151. if ((contentStr.length() > 0) &&
  152. (contentStr.charAt(contentStr.length()-1) == NEWLINE)) {
  153. contentStr = contentStr.substring(0, contentStr.length()-1);
  154. }
  155. if (contentStr.length() > 0) {
  156. write(contentStr);
  157. }
  158. }
  159. /**
  160. * Writes out a start tag appropriately
  161. * indented. Also increments the indent level.
  162. *
  163. * @exception IOException on any I/O error
  164. */
  165. protected void writeStartTag(String tag) throws IOException {
  166. indent();
  167. write(tag);
  168. write(NEWLINE);
  169. incrIndent();
  170. }
  171. /**
  172. * Writes out an end tag appropriately
  173. * indented. Also decrements the indent level.
  174. *
  175. * @exception IOException on any I/O error
  176. */
  177. protected void writeEndTag(String endTag) throws IOException {
  178. decrIndent();
  179. indent();
  180. write(endTag);
  181. write(NEWLINE);
  182. }
  183. /**
  184. * Writes out the <head> and <style>
  185. * tags, and then invokes writeStyles() to write
  186. * out all the named styles as the content of the
  187. * <style> tag. The content is surrounded by
  188. * valid HTML comment markers to ensure that the
  189. * document is viewable in applications/browsers
  190. * that do not support the tag.
  191. *
  192. * @exception IOException on any I/O error
  193. */
  194. protected void writeHeader() throws IOException {
  195. writeStartTag("<head>");
  196. writeStartTag("<style>");
  197. writeStartTag("<!--");
  198. writeStyles();
  199. writeEndTag("-->");
  200. writeEndTag("</style>");
  201. writeEndTag("</head>");
  202. }
  203. /**
  204. * Writes out all the named styles as the
  205. * content of the <style> tag.
  206. *
  207. * @exception IOException on any I/O error
  208. */
  209. protected void writeStyles() throws IOException {
  210. /*
  211. * Access to DefaultStyledDocument done to workaround
  212. * a missing API in styled document to access the
  213. * stylenames.
  214. */
  215. DefaultStyledDocument styledDoc = ((DefaultStyledDocument)getDocument());
  216. Enumeration styleNames = styledDoc.getStyleNames();
  217. while (styleNames.hasMoreElements()) {
  218. Style s = styledDoc.getStyle((String)styleNames.nextElement());
  219. /** PENDING: Once the name attribute is removed
  220. from the list we check check for 0. **/
  221. if (s.getAttributeCount() == 1 &&
  222. s.isDefined(StyleConstants.NameAttribute)) {
  223. continue;
  224. }
  225. indent();
  226. write("p." + addStyleName(s.getName()));
  227. write(" {\n");
  228. incrIndent();
  229. writeAttributes(s);
  230. decrIndent();
  231. indent();
  232. write("}\n");
  233. }
  234. }
  235. /**
  236. * Iterates over the elements in the document
  237. * and processes elements based on whether they are
  238. * branch elements or leaf elements. This method specially handles
  239. * leaf elements that are text.
  240. *
  241. * @exception IOException on any I/O error
  242. */
  243. protected void writeBody() throws IOException, BadLocationException {
  244. ElementIterator it = getElementIterator();
  245. /*
  246. This will be a section element for a styled document.
  247. We represent this element in HTML as the body tags.
  248. Therefore we ignore it.
  249. */
  250. it.current();
  251. Element next = null;
  252. writeStartTag("<body>");
  253. boolean inContent = false;
  254. while((next = it.next()) != null) {
  255. if (!inRange(next)) {
  256. continue;
  257. }
  258. if (next instanceof AbstractDocument.BranchElement) {
  259. if (inContent) {
  260. writeEndParagraph();
  261. inContent = false;
  262. fontMask = 0;
  263. }
  264. writeStartParagraph(next);
  265. } else if (isText(next)) {
  266. writeContent(next, !inContent);
  267. inContent = true;
  268. } else {
  269. writeLeaf(next);
  270. inContent = true;
  271. }
  272. }
  273. if (inContent) {
  274. writeEndParagraph();
  275. }
  276. writeEndTag("</body>");
  277. }
  278. /**
  279. * Emits an end tag for a <p>
  280. * tag. Before writing out the tag, this method ensures
  281. * that all other tags that have been opened are
  282. * appropriately closed off.
  283. *
  284. * @exception IOException on any I/O error
  285. */
  286. protected void writeEndParagraph() throws IOException {
  287. writeEndMask(fontMask);
  288. if (inFontTag()) {
  289. endSpanTag();
  290. } else {
  291. write(NEWLINE);
  292. }
  293. writeEndTag("</p>");
  294. }
  295. /**
  296. * Emits the start tag for a paragraph. If
  297. * the paragraph has a named style associated with it,
  298. * then this method also generates a class attribute for the
  299. * <p> tag and sets its value to be the name of the
  300. * style.
  301. *
  302. * @exception IOException on any I/O error
  303. */
  304. protected void writeStartParagraph(Element elem) throws IOException {
  305. AttributeSet attr = elem.getAttributes();
  306. Object resolveAttr = attr.getAttribute(StyleConstants.ResolveAttribute);
  307. if (resolveAttr instanceof StyleContext.NamedStyle) {
  308. writeStartTag("<p class=" + mapStyleName(((StyleContext.NamedStyle)resolveAttr).getName()) + ">");
  309. } else {
  310. writeStartTag("<p>");
  311. }
  312. }
  313. /**
  314. * Responsible for writing out other non-text leaf
  315. * elements.
  316. *
  317. * @exception IOException on any I/O error
  318. */
  319. protected void writeLeaf(Element elem) throws IOException {
  320. indent();
  321. if (elem.getName() == StyleConstants.IconElementName) {
  322. writeImage(elem);
  323. } else if (elem.getName() == StyleConstants.ComponentElementName) {
  324. writeComponent(elem);
  325. }
  326. }
  327. /**
  328. * Responsible for handling Icon Elements;
  329. * deliberately unimplemented. How to implement this method is
  330. * an issue of policy. For example, if you're generating
  331. * an <img> tag, how should you
  332. * represent the src attribute (the location of the image)?
  333. * In certain cases it could be a URL, in others it could
  334. * be read from a stream.
  335. *
  336. * @param elem element of type StyleConstants.IconElementName
  337. */
  338. protected void writeImage(Element elem) throws IOException {
  339. }
  340. /**
  341. * Responsible for handling Component Elements;
  342. * deliberately unimplemented.
  343. * How this method is implemented is a matter of policy.
  344. */
  345. protected void writeComponent(Element elem) throws IOException {
  346. }
  347. /**
  348. * Returns true if the element is a text element.
  349. *
  350. */
  351. protected boolean isText(Element elem) {
  352. return (elem.getName() == AbstractDocument.ContentElementName);
  353. }
  354. /**
  355. * Writes out the attribute set
  356. * in an HTML-compliant manner.
  357. *
  358. * @exception IOException on any I/O error
  359. * @exception BadLocationException if pos represents an invalid
  360. * location within the document.
  361. */
  362. protected void writeContent(Element elem, boolean needsIndenting)
  363. throws IOException, BadLocationException {
  364. AttributeSet attr = elem.getAttributes();
  365. writeNonHTMLAttributes(attr);
  366. if (needsIndenting) {
  367. indent();
  368. }
  369. writeHTMLTags(attr);
  370. text(elem);
  371. }
  372. /**
  373. * Generates
  374. * bold <b>, italic <i>, and <u> tags for the
  375. * text based on its attribute settings.
  376. *
  377. * @exception IOException on any I/O error
  378. */
  379. protected void writeHTMLTags(AttributeSet attr) throws IOException {
  380. int oldMask = fontMask;
  381. setFontMask(attr);
  382. int endMask = 0;
  383. int startMask = 0;
  384. if ((oldMask & BOLD) != 0) {
  385. if ((fontMask & BOLD) == 0) {
  386. endMask |= BOLD;
  387. }
  388. } else if ((fontMask & BOLD) != 0) {
  389. startMask |= BOLD;
  390. }
  391. if ((oldMask & ITALIC) != 0) {
  392. if ((fontMask & ITALIC) == 0) {
  393. endMask |= ITALIC;
  394. }
  395. } else if ((fontMask & ITALIC) != 0) {
  396. startMask |= ITALIC;
  397. }
  398. if ((oldMask & UNDERLINE) != 0) {
  399. if ((fontMask & UNDERLINE) == 0) {
  400. endMask |= UNDERLINE;
  401. }
  402. } else if ((fontMask & UNDERLINE) != 0) {
  403. startMask |= UNDERLINE;
  404. }
  405. writeEndMask(endMask);
  406. writeStartMask(startMask);
  407. }
  408. /**
  409. * Tweaks the appropriate bits of fontMask
  410. * to reflect whether the text is to be displayed in
  411. * bold, italic, and/or with an underline.
  412. *
  413. */
  414. private void setFontMask(AttributeSet attr) {
  415. if (StyleConstants.isBold(attr)) {
  416. fontMask |= BOLD;
  417. }
  418. if (StyleConstants.isItalic(attr)) {
  419. fontMask |= ITALIC;
  420. }
  421. if (StyleConstants.isUnderline(attr)) {
  422. fontMask |= UNDERLINE;
  423. }
  424. }
  425. /**
  426. * Writes out start tags <u>, <i>, and <b> based on
  427. * the mask settings.
  428. *
  429. * @exception IOException on any I/O error
  430. */
  431. private void writeStartMask(int mask) throws IOException {
  432. if (mask != 0) {
  433. if ((mask & UNDERLINE) != 0) {
  434. write("<u>");
  435. }
  436. if ((mask & ITALIC) != 0) {
  437. write("<i>");
  438. }
  439. if ((mask & BOLD) != 0) {
  440. write("<b>");
  441. }
  442. }
  443. }
  444. /**
  445. * Writes out end tags for <u>, <i>, and <b> based on
  446. * the mask settings.
  447. *
  448. * @exception IOException on any I/O error
  449. */
  450. private void writeEndMask(int mask) throws IOException {
  451. if (mask != 0) {
  452. if ((mask & BOLD) != 0) {
  453. write("</b>");
  454. }
  455. if ((mask & ITALIC) != 0) {
  456. write("</i>");
  457. }
  458. if ((mask & UNDERLINE) != 0) {
  459. write("</u>");
  460. }
  461. }
  462. }
  463. /**
  464. * Writes out the remaining
  465. * character-level attributes (attributes other than bold,
  466. * italic, and underline) in an HTML-compliant way. Given that
  467. * attributes such as font family and font size have no direct
  468. * mapping to HTML tags, a <span> tag is generated and its
  469. * style attribute is set to contain the list of remaining
  470. * attributes just like inline styles.
  471. *
  472. * @exception IOException on any I/O error
  473. */
  474. protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException {
  475. String style = "";
  476. String separator = "; ";
  477. if (inFontTag() && fontAttributes.isEqual(attr)) {
  478. return;
  479. }
  480. boolean first = true;
  481. Color color = (Color)attr.getAttribute(StyleConstants.Foreground);
  482. if (color != null) {
  483. style += "color: " + css.styleConstantsValueToCSSValue
  484. ((StyleConstants)StyleConstants.Foreground,
  485. color);
  486. first = false;
  487. }
  488. Integer size = (Integer)attr.getAttribute(StyleConstants.FontSize);
  489. if (size != null) {
  490. if (!first) {
  491. style += separator;
  492. }
  493. style += "font-size: " + size.intValue() + "pt";
  494. first = false;
  495. }
  496. String family = (String)attr.getAttribute(StyleConstants.FontFamily);
  497. if (family != null) {
  498. if (!first) {
  499. style += separator;
  500. }
  501. style += "font-family: " + family;
  502. first = false;
  503. }
  504. if (style.length() > 0) {
  505. if (fontMask != 0) {
  506. writeEndMask(fontMask);
  507. fontMask = 0;
  508. }
  509. startSpanTag(style);
  510. fontAttributes = attr;
  511. }
  512. else if (fontAttributes != null) {
  513. writeEndMask(fontMask);
  514. fontMask = 0;
  515. endSpanTag();
  516. }
  517. }
  518. /**
  519. * Returns true if we are currently in a <font> tag.
  520. */
  521. protected boolean inFontTag() {
  522. return (fontAttributes != null);
  523. }
  524. /**
  525. * This is no longer used, instead <span> will be written out.
  526. * <p>
  527. * Writes out an end tag for the <font> tag.
  528. *
  529. * @exception IOException on any I/O error
  530. */
  531. protected void endFontTag() throws IOException {
  532. write(NEWLINE);
  533. writeEndTag("</font>");
  534. fontAttributes = null;
  535. }
  536. /**
  537. * This is no longer used, instead <span> will be written out.
  538. * <p>
  539. * Writes out a start tag for the <font> tag.
  540. * Because font tags cannot be nested,
  541. * this method closes out
  542. * any enclosing font tag before writing out a
  543. * new start tag.
  544. *
  545. * @exception IOException on any I/O error
  546. */
  547. protected void startFontTag(String style) throws IOException {
  548. boolean callIndent = false;
  549. if (inFontTag()) {
  550. endFontTag();
  551. callIndent = true;
  552. }
  553. writeStartTag("<font style=\"" + style + "\">");
  554. if (callIndent) {
  555. indent();
  556. }
  557. }
  558. /**
  559. * Writes out a start tag for the <font> tag.
  560. * Because font tags cannot be nested,
  561. * this method closes out
  562. * any enclosing font tag before writing out a
  563. * new start tag.
  564. *
  565. * @exception IOException on any I/O error
  566. */
  567. private void startSpanTag(String style) throws IOException {
  568. boolean callIndent = false;
  569. if (inFontTag()) {
  570. endSpanTag();
  571. callIndent = true;
  572. }
  573. writeStartTag("<span style=\"" + style + "\">");
  574. if (callIndent) {
  575. indent();
  576. }
  577. }
  578. /**
  579. * Writes out an end tag for the <span> tag.
  580. *
  581. * @exception IOException on any I/O error
  582. */
  583. private void endSpanTag() throws IOException {
  584. write(NEWLINE);
  585. writeEndTag("</span>");
  586. fontAttributes = null;
  587. }
  588. /**
  589. * Adds the style named <code>style</code> to the style mapping. This
  590. * returns the name that should be used when outputting. CSS does not
  591. * allow the full Unicode set to be used as a style name.
  592. */
  593. private String addStyleName(String style) {
  594. if (styleNameMapping == null) {
  595. return style;
  596. }
  597. StringBuffer sb = null;
  598. for (int counter = style.length() - 1; counter >= 0; counter--) {
  599. if (!isValidCharacter(style.charAt(counter))) {
  600. if (sb == null) {
  601. sb = new StringBuffer(style);
  602. }
  603. sb.setCharAt(counter, 'a');
  604. }
  605. }
  606. String mappedName = (sb != null) ? sb.toString() : style;
  607. while (styleNameMapping.get(mappedName) != null) {
  608. mappedName = mappedName + 'x';
  609. }
  610. styleNameMapping.put(style, mappedName);
  611. return mappedName;
  612. }
  613. /**
  614. * Returns the mapped style name corresponding to <code>style</code>.
  615. */
  616. private String mapStyleName(String style) {
  617. if (styleNameMapping == null) {
  618. return style;
  619. }
  620. String retValue = (String)styleNameMapping.get(style);
  621. return (retValue == null) ? style : retValue;
  622. }
  623. private boolean isValidCharacter(char character) {
  624. return ((character >= 'a' && character <= 'z') ||
  625. (character >= 'A' && character <= 'Z'));
  626. }
  627. }