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