1. /*
  2. * @(#)HTMLWriter.java 1.24 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 javax.swing.text.*;
  9. import java.io.Writer;
  10. import java.util.Stack;
  11. import java.util.Enumeration;
  12. import java.util.Vector;
  13. import java.io.IOException;
  14. import java.util.StringTokenizer;
  15. import java.util.NoSuchElementException;
  16. /**
  17. * This is a writer for HTMLDocuments.
  18. *
  19. * @author Sunita Mani
  20. * @version 1.24, 11/29/01
  21. */
  22. public class HTMLWriter extends AbstractWriter {
  23. /*
  24. * Stores all elements for which end tags have to
  25. * be emitted.
  26. */
  27. private Stack blockElementStack = new Stack();
  28. private boolean inContent = false;
  29. private boolean inPre = false;
  30. /** When inPre is true, this will indicate the end offset of the pre
  31. * element. */
  32. private int preEndOffset;
  33. private boolean inTextArea = false;
  34. private boolean newlineOutputed = false;
  35. private boolean completeDoc;
  36. /*
  37. * Stores all embedded tags. i.e tags that are
  38. * stored as attributes in other tags. For example,
  39. * <b>, <i>, <font>, <a>. Basically tags that
  40. * are essentially character level attributes.
  41. */
  42. private Vector tags = new Vector(10);
  43. /**
  44. * Values for the tags.
  45. */
  46. private Vector tagValues = new Vector(10);
  47. /**
  48. * Used when writing out a string.
  49. */
  50. private char[] tempChars;
  51. /**
  52. * Used when indenting. Will contain the spaces.
  53. */
  54. private char[] indentChars;
  55. /**
  56. * Used when writing out content.
  57. */
  58. private Segment segment;
  59. /*
  60. * This is used in closeOutUnwantedEmbeddedTags.
  61. */
  62. private Vector tagsToRemove = new Vector(10);
  63. /**
  64. * Set to true after the head has been output.
  65. */
  66. private boolean wroteHead;
  67. //
  68. // The following are temporary until new API can be added.
  69. //
  70. /** This class does its own writing. */
  71. private Writer out;
  72. /** If this is true when outputting a string and the length exceeds
  73. * maxLineLength a newline will be output. */
  74. private int maxLineLength = 80;
  75. private int currLength;
  76. private int indentLevel = 0;
  77. private int indentSpace = 2;
  78. // If (indentLevel * indentSpace) becomes >= maxLineLength, this will
  79. // get incremened instead of indentLevel to avoid indenting going greater
  80. // than line length.
  81. private int offsetIndent = 0;
  82. /** String used for end of line. If the Document has the property
  83. * EndOfLineStringProperty, it will be used for newlines. Otherwise
  84. * the System property line.separator will be used. */
  85. private String newline;
  86. /** If this is true, the line is empty. indent() does not make this
  87. * true. */
  88. private boolean isLineEmpty;
  89. private int startOffset;
  90. private int endOffset;
  91. /**
  92. * Creates a new HTMLWriter.
  93. *
  94. * @param a Writer
  95. * @param an HTMLDocument
  96. *
  97. */
  98. public HTMLWriter(Writer w, HTMLDocument doc) {
  99. this(w, doc, 0, doc.getLength());
  100. }
  101. /**
  102. * Creates a new HTMLWriter.
  103. *
  104. * @param a Writer
  105. * @param an HTMLDocument
  106. * @param pos The location in the document to fetch the
  107. * content.
  108. * @param len The amount to write out.
  109. */
  110. public HTMLWriter(Writer w, HTMLDocument doc, int pos, int len) {
  111. super(w, doc, pos, len);
  112. completeDoc = (pos == 0 && len == doc.getLength());
  113. this.maxLineLength = 80;
  114. this.out = w;
  115. Object docNewline = doc.getProperty(DefaultEditorKit.
  116. EndOfLineStringProperty);
  117. if (docNewline instanceof String) {
  118. newline = (String)docNewline;
  119. }
  120. else {
  121. try {
  122. newline = System.getProperty("line.separator");
  123. } catch (SecurityException se) {}
  124. if (newline == null) {
  125. // Should not get here, but if we do it means we could not
  126. // find a newline string, use \n in this case.
  127. newline = "\n";
  128. }
  129. }
  130. startOffset = pos;
  131. endOffset = pos + len;
  132. }
  133. /**
  134. * This is method that iterates over the the
  135. * Element tree and controls the writing out of
  136. * all the tags and its attributes.
  137. *
  138. * @exception IOException on any I/O error
  139. * @exception BadLocationException if pos represents an invalid
  140. * location within the document.
  141. *
  142. */
  143. public void write() throws IOException, BadLocationException {
  144. ElementIterator it = getElementIterator();
  145. Element current = null;
  146. Element next = null;
  147. wroteHead = false;
  148. tempChars = null;
  149. currLength = 0;
  150. isLineEmpty = true;
  151. if (segment == null) {
  152. segment = new Segment();
  153. }
  154. inPre = false;
  155. while ((next = it.next()) != null) {
  156. if (!inRange(next)) {
  157. continue;
  158. }
  159. if (current != null) {
  160. /*
  161. if next is child of current increment indent
  162. */
  163. if (indentNeedsIncrementing(current, next)) {
  164. incrIndent();
  165. } else if (current.getParentElement() != next.getParentElement()) {
  166. /*
  167. next and current are not siblings
  168. so emit end tags for items on the stack until the
  169. item on top of the stack, is the parent of the
  170. next.
  171. */
  172. Element top = (Element)blockElementStack.peek();
  173. while (top != next.getParentElement()) {
  174. /*
  175. pop() will return top.
  176. */
  177. blockElementStack.pop();
  178. if (!synthesizedElement(top)) {
  179. if (!matchNameAttribute(top.getAttributes(), HTML.Tag.PRE)) {
  180. decrIndent();
  181. }
  182. endTag(top);
  183. }
  184. top = (Element)blockElementStack.peek();
  185. }
  186. } else if (current.getParentElement() == next.getParentElement()) {
  187. /*
  188. if next and current are siblings the indent level
  189. is correct. But, we need to make sure that if current is
  190. on the stack, we pop it off, and put out its end tag.
  191. */
  192. Element top = (Element)blockElementStack.peek();
  193. if (top == current) {
  194. blockElementStack.pop();
  195. endTag(top);
  196. }
  197. }
  198. }
  199. if (!next.isLeaf() || isFormElementWithContent(next.getAttributes())) {
  200. blockElementStack.push(next);
  201. startTag(next);
  202. } else {
  203. emptyTag(next);
  204. }
  205. current = next;
  206. }
  207. /* Emit all remaining end tags */
  208. /* A null parameter ensures that all embedded tags
  209. currently in the tags vector have their
  210. corresponding end tags written out.
  211. */
  212. closeOutUnwantedEmbeddedTags(null);
  213. while (!blockElementStack.empty()) {
  214. current = (Element)blockElementStack.pop();
  215. if (!synthesizedElement(current)) {
  216. if (!matchNameAttribute(current.getAttributes(), HTML.Tag.PRE)) {
  217. decrIndent();
  218. }
  219. endTag(current);
  220. }
  221. }
  222. if (completeDoc) {
  223. writeAdditionalComments();
  224. }
  225. segment.array = null;
  226. }
  227. /**
  228. * Writes out the attribute set. Ignores all
  229. * attributes with a key of type HTML.Tag,
  230. * attributes with a key of type StyleConstants,
  231. * and attributes with a key of type
  232. * HTML.Attribute.ENDTAG.
  233. *
  234. * @param an AttributeSet.
  235. * @exception IOException on any I/O error
  236. *
  237. */
  238. protected void writeAttributes(AttributeSet attr) throws IOException {
  239. // translate css attributes to html
  240. convAttr.removeAttributes(convAttr);
  241. convertToHTML32(attr, convAttr);
  242. Enumeration names = convAttr.getAttributeNames();
  243. while (names.hasMoreElements()) {
  244. Object name = names.nextElement();
  245. if (name instanceof HTML.Tag ||
  246. name instanceof StyleConstants ||
  247. name == HTML.Attribute.ENDTAG) {
  248. continue;
  249. }
  250. write(" " + name + "=\"" + convAttr.getAttribute(name) + "\"");
  251. }
  252. }
  253. /**
  254. * Writes out all empty elements i.e tags that have no
  255. * corresponding end tag.
  256. *
  257. * @param an Element.
  258. * @exception IOException on any I/O error
  259. * @exception BadLocationException if pos represents an invalid
  260. * location within the document.
  261. */
  262. protected void emptyTag(Element elem) throws BadLocationException, IOException {
  263. if (!inContent && !inPre) {
  264. indent();
  265. }
  266. AttributeSet attr = elem.getAttributes();
  267. closeOutUnwantedEmbeddedTags(attr);
  268. writeEmbeddedTags(attr);
  269. if (matchNameAttribute(attr, HTML.Tag.CONTENT)) {
  270. inContent = true;
  271. text(elem);
  272. } else if (matchNameAttribute(attr, HTML.Tag.COMMENT)) {
  273. comment(elem);
  274. } else {
  275. boolean isBlock = isBlockTag(elem.getAttributes());
  276. if (inContent && isBlock ) {
  277. writeNewline();
  278. indent();
  279. }
  280. Object nameTag = (attr != null) ? attr.getAttribute
  281. (StyleConstants.NameAttribute) : null;
  282. Object endTag = (attr != null) ? attr.getAttribute
  283. (HTML.Attribute.ENDTAG) : null;
  284. boolean outputEndTag = false;
  285. // If an instance of an UNKNOWN Tag, or an instance of a
  286. // tag that is only visible during editing
  287. //
  288. if (nameTag != null && endTag != null &&
  289. (endTag instanceof String) &&
  290. ((String)endTag).equals("true")) {
  291. outputEndTag = true;
  292. }
  293. if (completeDoc && matchNameAttribute(attr, HTML.Tag.HEAD)) {
  294. if (outputEndTag) {
  295. // Write out any styles.
  296. writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
  297. }
  298. wroteHead = true;
  299. }
  300. write('<');
  301. if (outputEndTag) {
  302. write('/');
  303. }
  304. write(elem.getName());
  305. writeAttributes(attr);
  306. write('>');
  307. if (matchNameAttribute(attr, HTML.Tag.TITLE) && !outputEndTag) {
  308. Document doc = elem.getDocument();
  309. String title = (String)doc.getProperty(Document.TitleProperty);
  310. write(title);
  311. } else if (!inContent || isBlock) {
  312. writeNewline();
  313. if (isBlock && inContent) {
  314. indent();
  315. }
  316. }
  317. }
  318. }
  319. /**
  320. * Determines if the HTML.Tag associated with the
  321. * element is a block tag.
  322. *
  323. * @param AttributeSet.
  324. * @return true if tag is block tag, false otherwise.
  325. */
  326. protected boolean isBlockTag(AttributeSet attr) {
  327. Object o = attr.getAttribute(StyleConstants.NameAttribute);
  328. if (o instanceof HTML.Tag) {
  329. HTML.Tag name = (HTML.Tag) o;
  330. return name.isBlock();
  331. }
  332. return false;
  333. }
  334. /**
  335. * Writes out a start tag for the element.
  336. * Ignores all synthesized elements.
  337. *
  338. * @param an Element.
  339. * @exception IOException on any I/O error
  340. */
  341. protected void startTag(Element elem) throws IOException, BadLocationException {
  342. if (synthesizedElement(elem)) {
  343. return;
  344. }
  345. // Determine the name, as an HTML.Tag.
  346. AttributeSet attr = elem.getAttributes();
  347. Object nameAttribute = attr.getAttribute(StyleConstants.NameAttribute);
  348. HTML.Tag name;
  349. if (nameAttribute instanceof HTML.Tag) {
  350. name = (HTML.Tag)nameAttribute;
  351. }
  352. else {
  353. name = null;
  354. }
  355. if (name == HTML.Tag.PRE) {
  356. inPre = true;
  357. preEndOffset = elem.getEndOffset();
  358. }
  359. // write out end tags for item on stack
  360. closeOutUnwantedEmbeddedTags(attr);
  361. if (inContent) {
  362. writeNewline();
  363. inContent = false;
  364. newlineOutputed = false;
  365. }
  366. if (completeDoc && name == HTML.Tag.BODY && !wroteHead) {
  367. // If the head has not been output, output it and the styles.
  368. wroteHead = true;
  369. indent();
  370. write("<head>");
  371. writeNewline();
  372. incrIndent();
  373. writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
  374. decrIndent();
  375. writeNewline();
  376. indent();
  377. write("</head>");
  378. writeNewline();
  379. }
  380. indent();
  381. write('<');
  382. write(elem.getName());
  383. writeAttributes(attr);
  384. write('>');
  385. if (name != HTML.Tag.PRE) {
  386. writeNewline();
  387. }
  388. if (name == HTML.Tag.TEXTAREA) {
  389. textAreaContent(elem.getAttributes());
  390. } else if (name == HTML.Tag.SELECT) {
  391. selectContent(elem.getAttributes());
  392. } else if (completeDoc && name == HTML.Tag.BODY) {
  393. // Write out the maps, which is not stored as Elements in
  394. // the Document.
  395. writeMaps(((HTMLDocument)getDocument()).getMaps());
  396. }
  397. else if (name == HTML.Tag.HEAD) {
  398. wroteHead = true;
  399. }
  400. }
  401. /**
  402. * Writes out text that is contained in a TEXTAREA form
  403. * element.
  404. *
  405. * @param AttributeSet
  406. * @exception IOException on any I/O error
  407. * @exception BadLocationException if pos represents an invalid
  408. * location within the document.
  409. */
  410. protected void textAreaContent(AttributeSet attr) throws BadLocationException, IOException {
  411. Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
  412. if (doc != null && doc.getLength() > 0) {
  413. if (segment == null) {
  414. segment = new Segment();
  415. }
  416. doc.getText(0, doc.getLength(), segment);
  417. if (segment.count > 0) {
  418. inTextArea = true;
  419. incrIndent();
  420. indent();
  421. write(segment.array, segment.offset, segment.count, true,
  422. true);
  423. writeNewline();
  424. inTextArea = false;
  425. decrIndent();
  426. }
  427. }
  428. }
  429. /**
  430. * Writes out text. If a range is specified when the constructor
  431. * is invoked, then only the appropriate range of text is written
  432. * out.
  433. *
  434. * @param an Element.
  435. * @exception IOException on any I/O error
  436. * @exception BadLocationException if pos represents an invalid
  437. * location within the document.
  438. */
  439. protected void text(Element elem) throws BadLocationException, IOException {
  440. int start = Math.max(startOffset, elem.getStartOffset());
  441. int end = Math.min(endOffset, elem.getEndOffset());
  442. if (start < end) {
  443. if (segment == null) {
  444. segment = new Segment();
  445. }
  446. getDocument().getText(start, end - start, segment);
  447. newlineOutputed = false;
  448. if (segment.count > 0) {
  449. if (segment.array[segment.offset + segment.count - 1] == '\n'){
  450. newlineOutputed = true;
  451. }
  452. if (inPre && end == preEndOffset) {
  453. if (segment.count > 1) {
  454. segment.count--;
  455. }
  456. else {
  457. return;
  458. }
  459. }
  460. write(segment.array, segment.offset, segment.count, !inPre,
  461. true);
  462. }
  463. }
  464. }
  465. /**
  466. * Writes out the content of the SELECT form element.
  467. *
  468. * @param AttributeSet associcated with the form element.
  469. * @exception IOException on any I/O error
  470. */
  471. protected void selectContent(AttributeSet attr) throws IOException {
  472. Object model = attr.getAttribute(StyleConstants.ModelAttribute);
  473. incrIndent();
  474. if (model instanceof OptionListModel) {
  475. OptionListModel listModel = (OptionListModel)model;
  476. int size = listModel.getSize();
  477. for (int i = 0; i < size; i++) {
  478. Option option = (Option)listModel.getElementAt(i);
  479. writeOption(option);
  480. }
  481. } else if (model instanceof OptionComboBoxModel) {
  482. OptionComboBoxModel comboBoxModel = (OptionComboBoxModel)model;
  483. int size = comboBoxModel.getSize();
  484. for (int i = 0; i < size; i++) {
  485. Option option = (Option)comboBoxModel.getElementAt(i);
  486. writeOption(option);
  487. }
  488. }
  489. decrIndent();
  490. }
  491. /**
  492. * Writes out the content of the Option form element.
  493. * @param Option.
  494. * @exception IOException on any I/O error
  495. *
  496. */
  497. protected void writeOption(Option option) throws IOException {
  498. indent();
  499. write('<');
  500. write("option ");
  501. if (option.getValue() != null) {
  502. write("value="+ option.getValue());
  503. }
  504. if (option.isSelected()) {
  505. write(" selected");
  506. }
  507. write('>');
  508. if (option.getLabel() != null) {
  509. write(option.getLabel());
  510. }
  511. writeNewline();
  512. }
  513. /**
  514. * Writes out an end tag for the element.
  515. *
  516. * @param an Element.
  517. * @exception IOException on any I/O error
  518. */
  519. protected void endTag(Element elem) throws IOException {
  520. if (synthesizedElement(elem)) {
  521. return;
  522. }
  523. if (matchNameAttribute(elem.getAttributes(), HTML.Tag.PRE)) {
  524. inPre = false;
  525. }
  526. // write out end tags for item on stack
  527. closeOutUnwantedEmbeddedTags(elem.getAttributes());
  528. if (inContent) {
  529. if (!newlineOutputed) {
  530. writeNewline();
  531. }
  532. newlineOutputed = false;
  533. inContent = false;
  534. }
  535. indent();
  536. write('<');
  537. write('/');
  538. write(elem.getName());
  539. write('>');
  540. writeNewline();
  541. }
  542. /**
  543. * Writes out comments.
  544. *
  545. * @param an element.
  546. * @exception IOException on any I/O error
  547. * @exception BadLocationException if pos represents an invalid
  548. * location within the document.
  549. */
  550. protected void comment(Element elem) throws BadLocationException, IOException {
  551. AttributeSet as = elem.getAttributes();
  552. if (matchNameAttribute(as, HTML.Tag.COMMENT)) {
  553. Object comment = as.getAttribute(HTML.Attribute.COMMENT);
  554. if (comment instanceof String) {
  555. writeComment((String)comment);
  556. }
  557. else {
  558. writeComment(null);
  559. }
  560. }
  561. }
  562. /**
  563. * Writes out comment string.
  564. *
  565. * @param string the comment.
  566. * @exception IOException on any I/O error
  567. * @exception BadLocationException if pos represents an invalid
  568. * location within the document.
  569. */
  570. void writeComment(String string) throws IOException {
  571. write("<!--");
  572. if (string != null) {
  573. write(string);
  574. }
  575. write("-->");
  576. writeNewline();
  577. }
  578. /**
  579. * Writes out any additional comments (comments outside of the body)
  580. * stored under the property HTMLDocument.AdditionalComments.
  581. */
  582. void writeAdditionalComments() throws IOException {
  583. Object comments = getDocument().getProperty
  584. (HTMLDocument.AdditionalComments);
  585. if (comments instanceof Vector) {
  586. Vector v = (Vector)comments;
  587. for (int counter = 0, maxCounter = v.size(); counter < maxCounter;
  588. counter++) {
  589. writeComment(v.elementAt(counter).toString());
  590. }
  591. }
  592. }
  593. /**
  594. * This method returns true, if the element is a
  595. * synthesized element. Currently we are only testing
  596. * for the p-implied tag.
  597. */
  598. protected boolean synthesizedElement(Element elem) {
  599. if (matchNameAttribute(elem.getAttributes(), HTML.Tag.IMPLIED)) {
  600. return true;
  601. }
  602. return false;
  603. }
  604. /**
  605. * This method return true if the StyleConstants.NameAttribute is
  606. * equal to the tag that is passed in as a parameter.
  607. */
  608. protected boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
  609. Object o = attr.getAttribute(StyleConstants.NameAttribute);
  610. if (o instanceof HTML.Tag) {
  611. HTML.Tag name = (HTML.Tag) o;
  612. if (name == tag) {
  613. return true;
  614. }
  615. }
  616. return false;
  617. }
  618. /**
  619. * This method searches for embedded tags in the AttributeSet
  620. * and writes them out. It also stores these tags in a vector
  621. * so that when appropriate the corresponding end tags can be
  622. * written out.
  623. *
  624. * @exception IOException on any I/O error
  625. */
  626. protected void writeEmbeddedTags(AttributeSet attr) throws IOException {
  627. // translate css attributes to html
  628. attr = convertToHTML(attr, oConvAttr);
  629. Enumeration names = attr.getAttributeNames();
  630. while (names.hasMoreElements()) {
  631. Object name = names.nextElement();
  632. if (name instanceof HTML.Tag) {
  633. HTML.Tag tag = (HTML.Tag)name;
  634. if (tag == HTML.Tag.FORM || tags.contains(tag)) {
  635. continue;
  636. }
  637. write('<');
  638. write(tag.toString());
  639. Object o = attr.getAttribute(tag);
  640. if (o != null && o instanceof AttributeSet) {
  641. writeAttributes((AttributeSet)o);
  642. }
  643. write('>');
  644. tags.addElement(tag);
  645. tagValues.addElement(o);
  646. }
  647. }
  648. }
  649. /**
  650. * This method searches the attribute set for a tag, both of which
  651. * are passed in as a parameter. Returns true if no match is found
  652. * and false otherwise.
  653. */
  654. private boolean noMatchForTagInAttributes(AttributeSet attr, HTML.Tag t,
  655. Object tagValue) {
  656. if (attr != null && attr.isDefined(t)) {
  657. Object newValue = attr.getAttribute(t);
  658. if ((tagValue == null) ? (newValue == null) :
  659. (newValue != null && tagValue.equals(newValue))) {
  660. return false;
  661. }
  662. }
  663. return true;
  664. }
  665. /**
  666. * This method searches the attribute set and for each tag
  667. * that is stored in the tag vector. If the tag isnt found,
  668. * then the tag is removed from the vector and a corresponding
  669. * end tag is written out.
  670. *
  671. * @exception IOException on any I/O error
  672. */
  673. protected void closeOutUnwantedEmbeddedTags(AttributeSet attr) throws IOException {
  674. tagsToRemove.removeAllElements();
  675. // translate css attributes to html
  676. attr = convertToHTML(attr, null);
  677. HTML.Tag t;
  678. Object tValue;
  679. int firstIndex = -1;
  680. int size = tags.size();
  681. // First, find all the tags that need to be removed.
  682. for (int i = size - 1; i >= 0; i--) {
  683. t = (HTML.Tag)tags.elementAt(i);
  684. tValue = tagValues.elementAt(i);
  685. if ((attr == null) || noMatchForTagInAttributes(attr, t, tValue)) {
  686. firstIndex = i;
  687. tagsToRemove.addElement(t);
  688. }
  689. }
  690. if (firstIndex != -1) {
  691. // Then close them out.
  692. boolean removeAll = ((size - firstIndex) == tagsToRemove.size());
  693. for (int i = size - 1; i >= firstIndex; i--) {
  694. t = (HTML.Tag)tags.elementAt(i);
  695. if (removeAll || tagsToRemove.contains(t)) {
  696. tags.removeElementAt(i);
  697. tagValues.removeElementAt(i);
  698. }
  699. write('<');
  700. write('/');
  701. write(t.toString());
  702. write('>');
  703. }
  704. // Have to output any tags after firstIndex that still remaing,
  705. // as we closed them out, but they should remain open.
  706. size = tags.size();
  707. for (int i = firstIndex; i < size; i++) {
  708. t = (HTML.Tag)tags.elementAt(i);
  709. write('<');
  710. write(t.toString());
  711. Object o = tagValues.elementAt(i);
  712. if (o != null && o instanceof AttributeSet) {
  713. writeAttributes((AttributeSet)o);
  714. }
  715. write('>');
  716. }
  717. }
  718. }
  719. /**
  720. * Determines if the element associated with the attributeset
  721. * is a TEXTAREA or SELECT. If true, returns true else
  722. * false
  723. */
  724. private boolean isFormElementWithContent(AttributeSet attr) {
  725. if (matchNameAttribute(attr, HTML.Tag.TEXTAREA) ||
  726. matchNameAttribute(attr, HTML.Tag.SELECT)) {
  727. return true;
  728. }
  729. return false;
  730. }
  731. /**
  732. * This method determines whether a the indentation needs to be
  733. * incremented. Basically, if next is a child of current, and
  734. * next is NOT a synthesized element, the indent level will be
  735. * incremented. If there is a parent-child relationship and "next"
  736. * is a synthesized element, then its children must be indented.
  737. * This state is maintained by the indentNext boolean.
  738. *
  739. * @return boolean that returns true if indent level
  740. * needs incrementing.
  741. */
  742. private boolean indentNext = false;
  743. private boolean indentNeedsIncrementing(Element current, Element next) {
  744. if ((next.getParentElement() == current) && !inPre) {
  745. if (indentNext) {
  746. indentNext = false;
  747. return true;
  748. } else if (synthesizedElement(next)) {
  749. indentNext = true;
  750. } else if (!synthesizedElement(current)){
  751. return true;
  752. }
  753. }
  754. return false;
  755. }
  756. /**
  757. * Outputs the maps as elements. Maps are not stored as elements in
  758. * the document, and as such this is used to output them.
  759. */
  760. void writeMaps(Enumeration maps) throws IOException {
  761. if (maps != null) {
  762. while(maps.hasMoreElements()) {
  763. Map map = (Map)maps.nextElement();
  764. String name = map.getName();
  765. incrIndent();
  766. indent();
  767. write("<map");
  768. if (name != null) {
  769. write(" name=\"");
  770. write(name);
  771. write("\">");
  772. }
  773. else {
  774. write('>');
  775. }
  776. writeNewline();
  777. incrIndent();
  778. // Output the areas
  779. AttributeSet[] areas = map.getAreas();
  780. if (areas != null) {
  781. for (int counter = 0, maxCounter = areas.length;
  782. counter < maxCounter; counter++) {
  783. indent();
  784. write("<area");
  785. writeAttributes(areas[counter]);
  786. write("></area>");
  787. writeNewline();
  788. }
  789. }
  790. decrIndent();
  791. indent();
  792. write("</map>");
  793. writeNewline();
  794. decrIndent();
  795. }
  796. }
  797. }
  798. /**
  799. * Outputs the styles as a single element. Styles are not stored as
  800. * elements, but part of the document. For the time being styles are
  801. * written out as a comment, inside a style tag.
  802. */
  803. void writeStyles(StyleSheet sheet) throws IOException {
  804. if (sheet != null) {
  805. Enumeration styles = sheet.getStyleNames();
  806. if (styles != null) {
  807. boolean outputStyle = false;
  808. while (styles.hasMoreElements()) {
  809. String name = (String)styles.nextElement();
  810. // Don't write out the default style.
  811. if (!StyleContext.DEFAULT_STYLE.equals(name) &&
  812. writeStyle(name, sheet.getStyle(name), outputStyle)) {
  813. outputStyle = true;
  814. }
  815. }
  816. if (outputStyle) {
  817. writeStyleEndTag();
  818. }
  819. }
  820. }
  821. }
  822. /**
  823. * Outputs the named style. <code>outputStyle</code> indicates
  824. * whether or not a style has been output yet. This will return
  825. * true if a style is written.
  826. */
  827. boolean writeStyle(String name, Style style, boolean outputStyle)
  828. throws IOException{
  829. boolean didOutputStyle = false;
  830. Enumeration attributes = style.getAttributeNames();
  831. if (attributes != null) {
  832. while (attributes.hasMoreElements()) {
  833. Object attribute = attributes.nextElement();
  834. if (attribute instanceof CSS.Attribute) {
  835. String value = style.getAttribute(attribute).toString();
  836. if (value != null) {
  837. if (!outputStyle) {
  838. writeStyleStartTag();
  839. outputStyle = true;
  840. }
  841. if (!didOutputStyle) {
  842. didOutputStyle = true;
  843. indent();
  844. write(name);
  845. write(" {");
  846. }
  847. else {
  848. write(";");
  849. }
  850. write(' ');
  851. write(attribute.toString());
  852. write(": ");
  853. write(value);
  854. }
  855. }
  856. }
  857. }
  858. if (didOutputStyle) {
  859. write(" }");
  860. writeNewline();
  861. }
  862. return didOutputStyle;
  863. }
  864. void writeStyleStartTag() throws IOException {
  865. indent();
  866. write("<style type=\"text/css\">");
  867. incrIndent();
  868. writeNewline();
  869. indent();
  870. write("<!--");
  871. incrIndent();
  872. writeNewline();
  873. }
  874. void writeStyleEndTag() throws IOException {
  875. decrIndent();
  876. indent();
  877. write("-->");
  878. writeNewline();
  879. decrIndent();
  880. indent();
  881. write("</style>");
  882. writeNewline();
  883. indent();
  884. }
  885. // --- conversion support ---------------------------
  886. /**
  887. * Convert the give set of attributes to be html for
  888. * the purpose of writing them out. Any keys that
  889. * have been converted will not appear in the resultant
  890. * set. Any keys not converted will appear in the
  891. * resultant set the same as the received set.<p>
  892. * This will put the converted values into <code>to</code>, unless
  893. * it is null in which case a temporary AttributeSet will be returned.
  894. */
  895. AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
  896. if (to == null) {
  897. to = convAttr;
  898. }
  899. to.removeAttributes(to);
  900. if (writeCSS) {
  901. convertToHTML40(from, to);
  902. } else {
  903. convertToHTML32(from, to);
  904. }
  905. return to;
  906. }
  907. /**
  908. * If true, the writer will emit CSS attributes in preference
  909. * to HTML tags/attributes (i.e. It will emit an HTML 4.0
  910. * style).
  911. */
  912. private boolean writeCSS = false;
  913. /**
  914. * Buffer for the purpose of attribute conversion
  915. */
  916. private MutableAttributeSet convAttr = new SimpleAttributeSet();
  917. /**
  918. * Buffer for the purpose of attribute conversion. This can be
  919. * used if convAttr is being used.
  920. */
  921. private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
  922. /**
  923. * Create an older style of HTML attributes. This will
  924. * convert character level attributes that have a StyleConstants
  925. * mapping over to an HTML tag/attribute. Other CSS attributes
  926. * will be placed in an HTML style attribute.
  927. */
  928. private static void convertToHTML32(AttributeSet from, MutableAttributeSet to) {
  929. if (from == null) {
  930. return;
  931. }
  932. Enumeration keys = from.getAttributeNames();
  933. String value = "";
  934. while (keys.hasMoreElements()) {
  935. Object key = keys.nextElement();
  936. if (key instanceof CSS.Attribute) {
  937. if ((key == CSS.Attribute.FONT_FAMILY) ||
  938. (key == CSS.Attribute.FONT_SIZE) ||
  939. (key == CSS.Attribute.COLOR)) {
  940. createFontAttribute((CSS.Attribute)key, from, to);
  941. } else if (key == CSS.Attribute.FONT_WEIGHT) {
  942. // add a bold tag is weight is bold
  943. CSS.FontWeight weightValue = (CSS.FontWeight)
  944. from.getAttribute(CSS.Attribute.FONT_WEIGHT);
  945. if ((weightValue != null) && (weightValue.getValue() > 400)) {
  946. to.addAttribute(HTML.Tag.B, SimpleAttributeSet.EMPTY);
  947. }
  948. } else if (key == CSS.Attribute.FONT_STYLE) {
  949. String s = from.getAttribute(key).toString();
  950. if (s.indexOf("italic") >= 0) {
  951. to.addAttribute(HTML.Tag.I, SimpleAttributeSet.EMPTY);
  952. }
  953. } else if (key == CSS.Attribute.TEXT_DECORATION) {
  954. String decor = from.getAttribute(key).toString();
  955. if (decor.indexOf("underline") >= 0) {
  956. to.addAttribute(HTML.Tag.U, SimpleAttributeSet.EMPTY);
  957. }
  958. if (decor.indexOf("line-through") >= 0) {
  959. to.addAttribute(HTML.Tag.STRIKE, SimpleAttributeSet.EMPTY);
  960. }
  961. } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
  962. String vAlign = from.getAttribute(key).toString();
  963. if (vAlign.indexOf("sup") >= 0) {
  964. to.addAttribute(HTML.Tag.SUP, SimpleAttributeSet.EMPTY);
  965. }
  966. if (vAlign.indexOf("sub") >= 0) {
  967. to.addAttribute(HTML.Tag.SUB, SimpleAttributeSet.EMPTY);
  968. }
  969. } else if (key == CSS.Attribute.TEXT_ALIGN) {
  970. to.addAttribute(HTML.Attribute.ALIGN,
  971. from.getAttribute(key).toString());
  972. } else {
  973. // default is to store in a HTML style attribute
  974. if (value.length() > 0) {
  975. value = value + "; ";
  976. }
  977. value = value + key + ": " + from.getAttribute(key);
  978. }
  979. } else {
  980. to.addAttribute(key, from.getAttribute(key));
  981. }
  982. }
  983. if (value.length() > 0) {
  984. to.addAttribute(HTML.Attribute.STYLE, value);
  985. }
  986. }
  987. /**
  988. * Create/update an HTML <font> tag attribute. The
  989. * value of the attribute should be a MutableAttributeSet so
  990. * that the attributes can be updated as they are discovered.
  991. */
  992. private static void createFontAttribute(CSS.Attribute a, AttributeSet from,
  993. MutableAttributeSet to) {
  994. MutableAttributeSet fontAttr = (MutableAttributeSet)
  995. to.getAttribute(HTML.Tag.FONT);
  996. if (fontAttr == null) {
  997. fontAttr = new SimpleAttributeSet();
  998. to.addAttribute(HTML.Tag.FONT, fontAttr);
  999. }
  1000. // edit the parameters to the font tag
  1001. String htmlValue = from.getAttribute(a).toString();
  1002. if (a == CSS.Attribute.FONT_FAMILY) {
  1003. fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
  1004. } else if (a == CSS.Attribute.FONT_SIZE) {
  1005. fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
  1006. } else if (a == CSS.Attribute.COLOR) {
  1007. fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
  1008. }
  1009. }
  1010. /**
  1011. * Copies the given AttributeSet to a new set, converting
  1012. * any CSS attributes found to arguments of an HTML style
  1013. * attribute.
  1014. */
  1015. private static void convertToHTML40(AttributeSet from, MutableAttributeSet to) {
  1016. Enumeration keys = from.getAttributeNames();
  1017. String value = "";
  1018. while (keys.hasMoreElements()) {
  1019. Object key = keys.nextElement();
  1020. if (key instanceof CSS.Attribute) {
  1021. value = value + " " + key + "=" + from.getAttribute(key) + ";";
  1022. } else {
  1023. to.addAttribute(key, from.getAttribute(key));
  1024. }
  1025. }
  1026. if (value.length() > 0) {
  1027. to.addAttribute(HTML.Attribute.STYLE, value);
  1028. }
  1029. }
  1030. /**
  1031. * Enables subclasses to specify how many spaces an indent
  1032. * maps to. When indentation takes place, the indent level
  1033. * is multiplied by this mapping. The default is 2.
  1034. *
  1035. * @param an int representing the space to indent mapping.
  1036. */
  1037. protected void setIndentSpace(int space) {
  1038. indentSpace = space;
  1039. }
  1040. /**
  1041. * Increments the indent level.
  1042. */
  1043. protected void incrIndent() {
  1044. // Only increment to a certain point.
  1045. if (offsetIndent > 0) {
  1046. offsetIndent++;
  1047. }
  1048. else {
  1049. if (++indentLevel * indentSpace >= maxLineLength) {
  1050. offsetIndent++;
  1051. --indentLevel;
  1052. }
  1053. }
  1054. }
  1055. /**
  1056. * Decrements the indent level.
  1057. */
  1058. protected void decrIndent() {
  1059. if (offsetIndent > 0) {
  1060. --offsetIndent;
  1061. }
  1062. else {
  1063. indentLevel--;
  1064. }
  1065. }
  1066. /**
  1067. * Enables subclasses to set the number of characters they
  1068. * want written per line. The default is 100.
  1069. *
  1070. * @param the maximum line length.
  1071. */
  1072. protected void setLineLength(int l) {
  1073. maxLineLength = l;
  1074. }
  1075. //
  1076. // This subclasses the writing methods to only break a string when
  1077. // canBreakString is true.
  1078. // In a future release it is likely AbstractWriter will get this
  1079. // functionality
  1080. //
  1081. /**
  1082. * Conveneice method for write(char, false).
  1083. */
  1084. protected void write(char ch) throws IOException {
  1085. write(ch, false);
  1086. }
  1087. /**
  1088. * Convenience method for write(String, false).
  1089. */
  1090. protected void write(String content) throws IOException {
  1091. write(content, false);
  1092. }
  1093. /**
  1094. * Writes out a character. If the character is
  1095. * a newline then it resets the current length to
  1096. * 0. If the current length equals the maximum
  1097. * line length, then a newline is outputed and the
  1098. * current length is reset to 0.
  1099. *
  1100. * @param a char.
  1101. * @exception IOException on any I/O error
  1102. */
  1103. private void write(char ch, boolean content) throws IOException {
  1104. if (ch == NEWLINE) {
  1105. currLength = 0;
  1106. out.write(newline);
  1107. isLineEmpty = true;
  1108. } else {
  1109. isLineEmpty = false;
  1110. out.write(ch);
  1111. ++currLength;
  1112. if (content && currLength >= maxLineLength) {
  1113. writeNewline();
  1114. }
  1115. }
  1116. }
  1117. /**
  1118. * Writes out a string.
  1119. *
  1120. * @param String representing the content.
  1121. * @exception IOException on any I/O error
  1122. */
  1123. private void write(String content, boolean isContent) throws IOException {
  1124. int size = content.length();
  1125. if (tempChars == null || tempChars.length < size) {
  1126. tempChars = new char[size];
  1127. }
  1128. content.getChars(0, size, tempChars, 0);
  1129. write(tempChars, 0, size, isContent, isContent);
  1130. }
  1131. /**
  1132. * Write a portion of an array of characters.
  1133. */
  1134. private void write(char[] chars, int startIndex, int length,
  1135. boolean isContent,
  1136. boolean replaceCharacterEntities) throws IOException {
  1137. if (!isContent) {
  1138. // We can not break str, just track if a newline
  1139. // is in it.
  1140. int lastIndex = startIndex;
  1141. int endIndex = startIndex + length;
  1142. int newlineIndex = indexOf(chars, '\n', startIndex, endIndex);
  1143. while (newlineIndex != -1) {
  1144. if (newlineIndex > lastIndex) {
  1145. if (replaceCharacterEntities) {
  1146. write(chars, lastIndex, newlineIndex - lastIndex);
  1147. }
  1148. else {
  1149. out.write(chars, lastIndex, newlineIndex - lastIndex);
  1150. }
  1151. }
  1152. writeNewline();
  1153. lastIndex = newlineIndex + 1;
  1154. newlineIndex = indexOf(chars, '\n', lastIndex, endIndex);
  1155. }
  1156. if (lastIndex < endIndex) {
  1157. currLength += (endIndex - lastIndex);
  1158. if (replaceCharacterEntities) {
  1159. write(chars, lastIndex, endIndex - lastIndex);
  1160. }
  1161. else {
  1162. out.write(chars, lastIndex, endIndex - lastIndex);
  1163. }
  1164. }
  1165. }
  1166. else {
  1167. // We can break chars if the length exceeds maxLineLength.
  1168. int lastIndex = startIndex;
  1169. int endIndex = startIndex + length;
  1170. if (currLength >= maxLineLength && !isLineEmpty) {
  1171. // This can happen if some tags have been written out.
  1172. writeNewline();
  1173. }
  1174. while (lastIndex < endIndex) {
  1175. int newlineIndex = indexOf(chars, '\n', lastIndex, endIndex);
  1176. boolean needsNewline = false;
  1177. if (newlineIndex != -1 && (currLength +
  1178. (newlineIndex - lastIndex)) < maxLineLength) {
  1179. if (newlineIndex > lastIndex) {
  1180. write(chars, lastIndex, newlineIndex - lastIndex);
  1181. }
  1182. lastIndex = newlineIndex + 1;
  1183. needsNewline = true;
  1184. }
  1185. else if (newlineIndex == -1 && (currLength +
  1186. (endIndex - lastIndex)) < maxLineLength) {
  1187. if (endIndex > lastIndex) {
  1188. write(chars, lastIndex, endIndex - lastIndex);
  1189. }
  1190. currLength += (endIndex - lastIndex);
  1191. lastIndex = endIndex;
  1192. }
  1193. else {
  1194. // Need to break chars, find a place to split chars at,
  1195. // from lastIndex to endIndex,
  1196. // or maxLineLength - currLength whichever is smaller
  1197. int breakPoint = -1;
  1198. int maxBreak = Math.min(endIndex - lastIndex,
  1199. maxLineLength - currLength - 1);
  1200. int counter = 0;
  1201. while (counter < maxBreak) {
  1202. if (Character.isWhitespace(chars[counter +
  1203. lastIndex])) {
  1204. breakPoint = counter;
  1205. }
  1206. counter++;
  1207. }
  1208. if (breakPoint != -1) {
  1209. // Found a place to break at.
  1210. breakPoint += lastIndex + 1;
  1211. write(chars, lastIndex, breakPoint - lastIndex);
  1212. lastIndex = breakPoint;
  1213. }
  1214. else {
  1215. // No where good to break.
  1216. if (isLineEmpty) {
  1217. // If the current output line is empty, find the
  1218. // next whitespace, or write out the whole string.
  1219. // maxBreak will be negative if current line too
  1220. // long.
  1221. counter = Math.max(0, maxBreak);
  1222. maxBreak = endIndex - lastIndex;
  1223. while (counter < maxBreak) {
  1224. if (Character.isWhitespace(chars[counter +
  1225. lastIndex])) {
  1226. breakPoint = counter;
  1227. break;
  1228. }
  1229. counter++;
  1230. }
  1231. if (breakPoint == -1) {
  1232. write(chars, lastIndex, endIndex - lastIndex);
  1233. breakPoint = endIndex;
  1234. }
  1235. else {
  1236. breakPoint += lastIndex;
  1237. if (chars[breakPoint] == NEWLINE) {
  1238. write(chars, lastIndex, breakPoint++ -
  1239. lastIndex);
  1240. }
  1241. else {
  1242. write(chars, lastIndex, ++breakPoint -
  1243. lastIndex);
  1244. }
  1245. }
  1246. lastIndex = breakPoint;
  1247. }
  1248. // else Iterate through again.
  1249. }
  1250. // Force a newline since line length too long.
  1251. needsNewline = true;
  1252. }
  1253. if (needsNewline || lastIndex < endIndex) {
  1254. writeNewline();
  1255. if (lastIndex < endIndex) {
  1256. indent();
  1257. }
  1258. }
  1259. }
  1260. }
  1261. }
  1262. /**
  1263. * Writes a portion of an array of characters. This does no
  1264. * book keeping before writing out the characters. It will map
  1265. * any characters outside of ascii to character level entities. You
  1266. * should not normally call this, rather call write that takes
  1267. * a boolean, or write that takes a String.
  1268. */
  1269. private void write(char[] chars, int start, int length) throws
  1270. IOException {
  1271. int last = start;
  1272. isLineEmpty = false;
  1273. length += start;
  1274. for (int counter = start; counter < length; counter++) {
  1275. // This will change, we need better support character level
  1276. // entities.
  1277. switch(chars[counter]) {
  1278. // Character level entities.
  1279. case '<':
  1280. if (counter > last) {
  1281. out.write(chars, last, counter - last);
  1282. }
  1283. last = counter + 1;
  1284. out.write("<");
  1285. break;
  1286. case '>':
  1287. if (counter > last) {
  1288. out.write(chars, last, counter - last);
  1289. }
  1290. last = counter + 1;
  1291. out.write(">");
  1292. break;
  1293. case '&':
  1294. if (counter > last) {
  1295. out.write(chars, last, counter - last);
  1296. }
  1297. last = counter + 1;
  1298. out.write("&");
  1299. break;
  1300. case '"':
  1301. if (counter > last) {
  1302. out.write(chars, last, counter - last);
  1303. }
  1304. last = counter + 1;
  1305. out.write(""");
  1306. break;
  1307. // Special characters
  1308. case '\n':
  1309. case '\t':
  1310. case '\r':
  1311. break;
  1312. default:
  1313. if (chars[counter] < ' ' || chars[counter] > 127) {
  1314. if (counter > last) {
  1315. out.write(chars, last, counter - last);
  1316. }
  1317. last = counter + 1;
  1318. // If the character is outside of ascii, write the
  1319. // numeric value.
  1320. out.write("&#");
  1321. out.write(String.valueOf((int)chars[counter]));
  1322. out.write(';');
  1323. }
  1324. break;
  1325. }
  1326. }
  1327. if (last < length) {
  1328. out.write(chars, last, length - last);
  1329. }
  1330. }
  1331. /**
  1332. * Writes a newline.
  1333. */
  1334. private void writeNewline() throws IOException {
  1335. out.write(newline);
  1336. isLineEmpty = true;
  1337. currLength = 0;
  1338. }
  1339. /**
  1340. * Does indentation. The number of spaces written
  1341. * out is indent level times the space to map mapping.
  1342. *
  1343. * @exception IOException on any I/O error
  1344. */
  1345. protected void indent() throws IOException {
  1346. int max = indentLevel * indentSpace;
  1347. if (indentChars == null || max > indentChars.length) {
  1348. indentChars = new char[max];
  1349. for (int counter = 0; counter < max; counter++) {
  1350. indentChars[counter] = ' ';
  1351. }
  1352. }
  1353. out.write(indentChars, 0, max);
  1354. currLength += max;
  1355. }
  1356. /**
  1357. * Support method to locate an occurence of a particular character.
  1358. */
  1359. private int indexOf(char[] chars, char sChar, int startIndex,
  1360. int endIndex) {
  1361. while(startIndex < endIndex) {
  1362. if (chars[startIndex] == sChar) {
  1363. return startIndex;
  1364. }
  1365. startIndex++;
  1366. }
  1367. return -1;
  1368. }
  1369. }