1. /*
  2. * @(#)HTMLWriter.java 1.32 03/01/23
  3. *
  4. * Copyright 2003 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. import java.net.URL;
  17. /**
  18. * This is a writer for HTMLDocuments.
  19. *
  20. * @author Sunita Mani
  21. * @version 1.26, 02/02/00
  22. */
  23. public class HTMLWriter extends AbstractWriter {
  24. /*
  25. * Stores all elements for which end tags have to
  26. * be emitted.
  27. */
  28. private Stack blockElementStack = new Stack();
  29. private boolean inContent = false;
  30. private boolean inPre = false;
  31. /** When inPre is true, this will indicate the end offset of the pre
  32. * element. */
  33. private int preEndOffset;
  34. private boolean inTextArea = false;
  35. private boolean newlineOutputed = false;
  36. private boolean completeDoc;
  37. /*
  38. * Stores all embedded tags. Embedded tags are tags that are
  39. * stored as attributes in other tags. Generally they're
  40. * character level attributes. Examples include
  41. * <b>, <i>, <font>, and <a>.
  42. */
  43. private Vector tags = new Vector(10);
  44. /**
  45. * Values for the tags.
  46. */
  47. private Vector tagValues = new Vector(10);
  48. /**
  49. * Used when writing out content.
  50. */
  51. private Segment segment;
  52. /*
  53. * This is used in closeOutUnwantedEmbeddedTags.
  54. */
  55. private Vector tagsToRemove = new Vector(10);
  56. /**
  57. * Set to true after the head has been output.
  58. */
  59. private boolean wroteHead;
  60. /**
  61. * Set to true when entities (such as <) should be replaced.
  62. */
  63. private boolean replaceEntities;
  64. /**
  65. * Temporary buffer.
  66. */
  67. private char[] tempChars;
  68. /**
  69. * Creates a new HTMLWriter.
  70. *
  71. * @param w a Writer
  72. * @param doc an HTMLDocument
  73. *
  74. */
  75. public HTMLWriter(Writer w, HTMLDocument doc) {
  76. this(w, doc, 0, doc.getLength());
  77. }
  78. /**
  79. * Creates a new HTMLWriter.
  80. *
  81. * @param w a Writer
  82. * @param doc an HTMLDocument
  83. * @param pos the document location from which to fetch the content
  84. * @param len the amount to write out
  85. */
  86. public HTMLWriter(Writer w, HTMLDocument doc, int pos, int len) {
  87. super(w, doc, pos, len);
  88. completeDoc = (pos == 0 && len == doc.getLength());
  89. setLineLength(80);
  90. }
  91. /**
  92. * Iterates over the
  93. * Element tree and controls the writing out of
  94. * all the tags and its attributes.
  95. *
  96. * @exception IOException on any I/O error
  97. * @exception BadLocationException if pos represents an invalid
  98. * location within the document.
  99. *
  100. */
  101. public void write() throws IOException, BadLocationException {
  102. ElementIterator it = getElementIterator();
  103. Element current = null;
  104. Element next = null;
  105. wroteHead = false;
  106. setCurrentLineLength(0);
  107. replaceEntities = false;
  108. setCanWrapLines(false);
  109. if (segment == null) {
  110. segment = new Segment();
  111. }
  112. inPre = false;
  113. boolean forcedBody = false;
  114. while ((next = it.next()) != null) {
  115. if (!inRange(next)) {
  116. if (completeDoc && next.getAttributes().getAttribute(
  117. StyleConstants.NameAttribute) == HTML.Tag.BODY) {
  118. forcedBody = true;
  119. }
  120. else {
  121. continue;
  122. }
  123. }
  124. if (current != null) {
  125. /*
  126. if next is child of current increment indent
  127. */
  128. if (indentNeedsIncrementing(current, next)) {
  129. incrIndent();
  130. } else if (current.getParentElement() != next.getParentElement()) {
  131. /*
  132. next and current are not siblings
  133. so emit end tags for items on the stack until the
  134. item on top of the stack, is the parent of the
  135. next.
  136. */
  137. Element top = (Element)blockElementStack.peek();
  138. while (top != next.getParentElement()) {
  139. /*
  140. pop() will return top.
  141. */
  142. blockElementStack.pop();
  143. if (!synthesizedElement(top)) {
  144. AttributeSet attrs = top.getAttributes();
  145. if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
  146. !isFormElementWithContent(attrs)) {
  147. decrIndent();
  148. }
  149. endTag(top);
  150. }
  151. top = (Element)blockElementStack.peek();
  152. }
  153. } else if (current.getParentElement() == next.getParentElement()) {
  154. /*
  155. if next and current are siblings the indent level
  156. is correct. But, we need to make sure that if current is
  157. on the stack, we pop it off, and put out its end tag.
  158. */
  159. Element top = (Element)blockElementStack.peek();
  160. if (top == current) {
  161. blockElementStack.pop();
  162. endTag(top);
  163. }
  164. }
  165. }
  166. if (!next.isLeaf() || isFormElementWithContent(next.getAttributes())) {
  167. blockElementStack.push(next);
  168. startTag(next);
  169. } else {
  170. emptyTag(next);
  171. }
  172. current = next;
  173. }
  174. /* Emit all remaining end tags */
  175. /* A null parameter ensures that all embedded tags
  176. currently in the tags vector have their
  177. corresponding end tags written out.
  178. */
  179. closeOutUnwantedEmbeddedTags(null);
  180. if (forcedBody) {
  181. blockElementStack.pop();
  182. endTag(current);
  183. }
  184. while (!blockElementStack.empty()) {
  185. current = (Element)blockElementStack.pop();
  186. if (!synthesizedElement(current)) {
  187. AttributeSet attrs = current.getAttributes();
  188. if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
  189. !isFormElementWithContent(attrs)) {
  190. decrIndent();
  191. }
  192. endTag(current);
  193. }
  194. }
  195. if (completeDoc) {
  196. writeAdditionalComments();
  197. }
  198. segment.array = null;
  199. }
  200. /**
  201. * Writes out the attribute set. Ignores all
  202. * attributes with a key of type HTML.Tag,
  203. * attributes with a key of type StyleConstants,
  204. * and attributes with a key of type
  205. * HTML.Attribute.ENDTAG.
  206. *
  207. * @param attr an AttributeSet
  208. * @exception IOException on any I/O error
  209. *
  210. */
  211. protected void writeAttributes(AttributeSet attr) throws IOException {
  212. // translate css attributes to html
  213. convAttr.removeAttributes(convAttr);
  214. convertToHTML32(attr, convAttr);
  215. Enumeration names = convAttr.getAttributeNames();
  216. while (names.hasMoreElements()) {
  217. Object name = names.nextElement();
  218. if (name instanceof HTML.Tag ||
  219. name instanceof StyleConstants ||
  220. name == HTML.Attribute.ENDTAG) {
  221. continue;
  222. }
  223. write(" " + name + "=\"" + convAttr.getAttribute(name) + "\"");
  224. }
  225. }
  226. /**
  227. * Writes out all empty elements (all tags that have no
  228. * corresponding end tag).
  229. *
  230. * @param elem an Element
  231. * @exception IOException on any I/O error
  232. * @exception BadLocationException if pos represents an invalid
  233. * location within the document.
  234. */
  235. protected void emptyTag(Element elem) throws BadLocationException, IOException {
  236. if (!inContent && !inPre) {
  237. indent();
  238. }
  239. AttributeSet attr = elem.getAttributes();
  240. closeOutUnwantedEmbeddedTags(attr);
  241. writeEmbeddedTags(attr);
  242. if (matchNameAttribute(attr, HTML.Tag.CONTENT)) {
  243. inContent = true;
  244. text(elem);
  245. } else if (matchNameAttribute(attr, HTML.Tag.COMMENT)) {
  246. comment(elem);
  247. } else {
  248. boolean isBlock = isBlockTag(elem.getAttributes());
  249. if (inContent && isBlock ) {
  250. writeLineSeparator();
  251. indent();
  252. }
  253. Object nameTag = (attr != null) ? attr.getAttribute
  254. (StyleConstants.NameAttribute) : null;
  255. Object endTag = (attr != null) ? attr.getAttribute
  256. (HTML.Attribute.ENDTAG) : null;
  257. boolean outputEndTag = false;
  258. // If an instance of an UNKNOWN Tag, or an instance of a
  259. // tag that is only visible during editing
  260. //
  261. if (nameTag != null && endTag != null &&
  262. (endTag instanceof String) &&
  263. ((String)endTag).equals("true")) {
  264. outputEndTag = true;
  265. }
  266. if (completeDoc && matchNameAttribute(attr, HTML.Tag.HEAD)) {
  267. if (outputEndTag) {
  268. // Write out any styles.
  269. writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
  270. }
  271. wroteHead = true;
  272. }
  273. write('<');
  274. if (outputEndTag) {
  275. write('/');
  276. }
  277. write(elem.getName());
  278. writeAttributes(attr);
  279. write('>');
  280. if (matchNameAttribute(attr, HTML.Tag.TITLE) && !outputEndTag) {
  281. Document doc = elem.getDocument();
  282. String title = (String)doc.getProperty(Document.TitleProperty);
  283. write(title);
  284. } else if (!inContent || isBlock) {
  285. writeLineSeparator();
  286. if (isBlock && inContent) {
  287. indent();
  288. }
  289. }
  290. }
  291. }
  292. /**
  293. * Determines if the HTML.Tag associated with the
  294. * element is a block tag.
  295. *
  296. * @param attr an AttributeSet
  297. * @return true if tag is block tag, false otherwise.
  298. */
  299. protected boolean isBlockTag(AttributeSet attr) {
  300. Object o = attr.getAttribute(StyleConstants.NameAttribute);
  301. if (o instanceof HTML.Tag) {
  302. HTML.Tag name = (HTML.Tag) o;
  303. return name.isBlock();
  304. }
  305. return false;
  306. }
  307. /**
  308. * Writes out a start tag for the element.
  309. * Ignores all synthesized elements.
  310. *
  311. * @param elem an Element
  312. * @exception IOException on any I/O error
  313. */
  314. protected void startTag(Element elem) throws IOException, BadLocationException {
  315. if (synthesizedElement(elem)) {
  316. return;
  317. }
  318. // Determine the name, as an HTML.Tag.
  319. AttributeSet attr = elem.getAttributes();
  320. Object nameAttribute = attr.getAttribute(StyleConstants.NameAttribute);
  321. HTML.Tag name;
  322. if (nameAttribute instanceof HTML.Tag) {
  323. name = (HTML.Tag)nameAttribute;
  324. }
  325. else {
  326. name = null;
  327. }
  328. if (name == HTML.Tag.PRE) {
  329. inPre = true;
  330. preEndOffset = elem.getEndOffset();
  331. }
  332. // write out end tags for item on stack
  333. closeOutUnwantedEmbeddedTags(attr);
  334. if (inContent) {
  335. writeLineSeparator();
  336. inContent = false;
  337. newlineOutputed = false;
  338. }
  339. if (completeDoc && name == HTML.Tag.BODY && !wroteHead) {
  340. // If the head has not been output, output it and the styles.
  341. wroteHead = true;
  342. indent();
  343. write("<head>");
  344. writeLineSeparator();
  345. incrIndent();
  346. writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
  347. decrIndent();
  348. writeLineSeparator();
  349. indent();
  350. write("</head>");
  351. writeLineSeparator();
  352. }
  353. indent();
  354. write('<');
  355. write(elem.getName());
  356. writeAttributes(attr);
  357. write('>');
  358. if (name != HTML.Tag.PRE) {
  359. writeLineSeparator();
  360. }
  361. if (name == HTML.Tag.TEXTAREA) {
  362. textAreaContent(elem.getAttributes());
  363. } else if (name == HTML.Tag.SELECT) {
  364. selectContent(elem.getAttributes());
  365. } else if (completeDoc && name == HTML.Tag.BODY) {
  366. // Write out the maps, which is not stored as Elements in
  367. // the Document.
  368. writeMaps(((HTMLDocument)getDocument()).getMaps());
  369. }
  370. else if (name == HTML.Tag.HEAD) {
  371. wroteHead = true;
  372. }
  373. HTMLDocument document = null;
  374. if (name == HTML.Tag.BODY
  375. && (document = (HTMLDocument)getDocument()).hasBaseTag()) {
  376. incrIndent();
  377. indent();
  378. write("<base href = \"" + document.getBase() + "\">");
  379. writeLineSeparator();
  380. decrIndent();
  381. }
  382. }
  383. /**
  384. * Writes out text that is contained in a TEXTAREA form
  385. * element.
  386. *
  387. * @param attr an AttributeSet
  388. * @exception IOException on any I/O error
  389. * @exception BadLocationException if pos represents an invalid
  390. * location within the document.
  391. */
  392. protected void textAreaContent(AttributeSet attr) throws BadLocationException, IOException {
  393. Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
  394. if (doc != null && doc.getLength() > 0) {
  395. if (segment == null) {
  396. segment = new Segment();
  397. }
  398. doc.getText(0, doc.getLength(), segment);
  399. if (segment.count > 0) {
  400. inTextArea = true;
  401. incrIndent();
  402. indent();
  403. setCanWrapLines(true);
  404. replaceEntities = true;
  405. write(segment.array, segment.offset, segment.count);
  406. replaceEntities = false;
  407. setCanWrapLines(false);
  408. writeLineSeparator();
  409. inTextArea = false;
  410. decrIndent();
  411. }
  412. }
  413. }
  414. /**
  415. * Writes out text. If a range is specified when the constructor
  416. * is invoked, then only the appropriate range of text is written
  417. * out.
  418. *
  419. * @param elem an Element
  420. * @exception IOException on any I/O error
  421. * @exception BadLocationException if pos represents an invalid
  422. * location within the document.
  423. */
  424. protected void text(Element elem) throws BadLocationException, IOException {
  425. int start = Math.max(getStartOffset(), elem.getStartOffset());
  426. int end = Math.min(getEndOffset(), elem.getEndOffset());
  427. if (start < end) {
  428. if (segment == null) {
  429. segment = new Segment();
  430. }
  431. getDocument().getText(start, end - start, segment);
  432. newlineOutputed = false;
  433. if (segment.count > 0) {
  434. if (segment.array[segment.offset + segment.count - 1] == '\n'){
  435. newlineOutputed = true;
  436. }
  437. if (inPre && end == preEndOffset) {
  438. if (segment.count > 1) {
  439. segment.count--;
  440. }
  441. else {
  442. return;
  443. }
  444. }
  445. replaceEntities = true;
  446. setCanWrapLines(!inPre);
  447. write(segment.array, segment.offset, segment.count);
  448. setCanWrapLines(false);
  449. replaceEntities = false;
  450. }
  451. }
  452. }
  453. /**
  454. * Writes out the content of the SELECT form element.
  455. *
  456. * @param attr the AttributeSet associated with the form element
  457. * @exception IOException on any I/O error
  458. */
  459. protected void selectContent(AttributeSet attr) throws IOException {
  460. Object model = attr.getAttribute(StyleConstants.ModelAttribute);
  461. incrIndent();
  462. if (model instanceof OptionListModel) {
  463. OptionListModel listModel = (OptionListModel)model;
  464. int size = listModel.getSize();
  465. for (int i = 0; i < size; i++) {
  466. Option option = (Option)listModel.getElementAt(i);
  467. writeOption(option);
  468. }
  469. } else if (model instanceof OptionComboBoxModel) {
  470. OptionComboBoxModel comboBoxModel = (OptionComboBoxModel)model;
  471. int size = comboBoxModel.getSize();
  472. for (int i = 0; i < size; i++) {
  473. Option option = (Option)comboBoxModel.getElementAt(i);
  474. writeOption(option);
  475. }
  476. }
  477. decrIndent();
  478. }
  479. /**
  480. * Writes out the content of the Option form element.
  481. * @param option an Option
  482. * @exception IOException on any I/O error
  483. *
  484. */
  485. protected void writeOption(Option option) throws IOException {
  486. indent();
  487. write('<');
  488. write("option");
  489. // PENDING: should this be changed to check for null first?
  490. Object value = option.getAttributes().getAttribute
  491. (HTML.Attribute.VALUE);
  492. if (value != null) {
  493. write(" value="+ value);
  494. }
  495. if (option.isSelected()) {
  496. write(" selected");
  497. }
  498. write('>');
  499. if (option.getLabel() != null) {
  500. write(option.getLabel());
  501. }
  502. writeLineSeparator();
  503. }
  504. /**
  505. * Writes out an end tag for the element.
  506. *
  507. * @param elem an Element
  508. * @exception IOException on any I/O error
  509. */
  510. protected void endTag(Element elem) throws IOException {
  511. if (synthesizedElement(elem)) {
  512. return;
  513. }
  514. if (matchNameAttribute(elem.getAttributes(), HTML.Tag.PRE)) {
  515. inPre = false;
  516. }
  517. // write out end tags for item on stack
  518. closeOutUnwantedEmbeddedTags(elem.getAttributes());
  519. if (inContent) {
  520. if (!newlineOutputed) {
  521. writeLineSeparator();
  522. }
  523. newlineOutputed = false;
  524. inContent = false;
  525. }
  526. indent();
  527. write('<');
  528. write('/');
  529. write(elem.getName());
  530. write('>');
  531. writeLineSeparator();
  532. }
  533. /**
  534. * Writes out comments.
  535. *
  536. * @param elem an Element
  537. * @exception IOException on any I/O error
  538. * @exception BadLocationException if pos represents an invalid
  539. * location within the document.
  540. */
  541. protected void comment(Element elem) throws BadLocationException, IOException {
  542. AttributeSet as = elem.getAttributes();
  543. if (matchNameAttribute(as, HTML.Tag.COMMENT)) {
  544. Object comment = as.getAttribute(HTML.Attribute.COMMENT);
  545. if (comment instanceof String) {
  546. writeComment((String)comment);
  547. }
  548. else {
  549. writeComment(null);
  550. }
  551. }
  552. }
  553. /**
  554. * Writes out comment string.
  555. *
  556. * @param string the comment
  557. * @exception IOException on any I/O error
  558. * @exception BadLocationException if pos represents an invalid
  559. * location within the document.
  560. */
  561. void writeComment(String string) throws IOException {
  562. write("<!--");
  563. if (string != null) {
  564. write(string);
  565. }
  566. write("-->");
  567. writeLineSeparator();
  568. }
  569. /**
  570. * Writes out any additional comments (comments outside of the body)
  571. * stored under the property HTMLDocument.AdditionalComments.
  572. */
  573. void writeAdditionalComments() throws IOException {
  574. Object comments = getDocument().getProperty
  575. (HTMLDocument.AdditionalComments);
  576. if (comments instanceof Vector) {
  577. Vector v = (Vector)comments;
  578. for (int counter = 0, maxCounter = v.size(); counter < maxCounter;
  579. counter++) {
  580. writeComment(v.elementAt(counter).toString());
  581. }
  582. }
  583. }
  584. /**
  585. * Returns true if the element is a
  586. * synthesized element. Currently we are only testing
  587. * for the p-implied tag.
  588. */
  589. protected boolean synthesizedElement(Element elem) {
  590. if (matchNameAttribute(elem.getAttributes(), HTML.Tag.IMPLIED)) {
  591. return true;
  592. }
  593. return false;
  594. }
  595. /**
  596. * Returns true if the StyleConstants.NameAttribute is
  597. * equal to the tag that is passed in as a parameter.
  598. */
  599. protected boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
  600. Object o = attr.getAttribute(StyleConstants.NameAttribute);
  601. if (o instanceof HTML.Tag) {
  602. HTML.Tag name = (HTML.Tag) o;
  603. if (name == tag) {
  604. return true;
  605. }
  606. }
  607. return false;
  608. }
  609. /**
  610. * Searches for embedded tags in the AttributeSet
  611. * and writes them out. It also stores these tags in a vector
  612. * so that when appropriate the corresponding end tags can be
  613. * written out.
  614. *
  615. * @exception IOException on any I/O error
  616. */
  617. protected void writeEmbeddedTags(AttributeSet attr) throws IOException {
  618. // translate css attributes to html
  619. attr = convertToHTML(attr, oConvAttr);
  620. Enumeration names = attr.getAttributeNames();
  621. while (names.hasMoreElements()) {
  622. Object name = names.nextElement();
  623. if (name instanceof HTML.Tag) {
  624. HTML.Tag tag = (HTML.Tag)name;
  625. if (tag == HTML.Tag.FORM || tags.contains(tag)) {
  626. continue;
  627. }
  628. write('<');
  629. write(tag.toString());
  630. Object o = attr.getAttribute(tag);
  631. if (o != null && o instanceof AttributeSet) {
  632. writeAttributes((AttributeSet)o);
  633. }
  634. write('>');
  635. tags.addElement(tag);
  636. tagValues.addElement(o);
  637. }
  638. }
  639. }
  640. /**
  641. * Searches the attribute set for a tag, both of which
  642. * are passed in as a parameter. Returns true if no match is found
  643. * and false otherwise.
  644. */
  645. private boolean noMatchForTagInAttributes(AttributeSet attr, HTML.Tag t,
  646. Object tagValue) {
  647. if (attr != null && attr.isDefined(t)) {
  648. Object newValue = attr.getAttribute(t);
  649. if ((tagValue == null) ? (newValue == null) :
  650. (newValue != null && tagValue.equals(newValue))) {
  651. return false;
  652. }
  653. }
  654. return true;
  655. }
  656. /**
  657. * Searches the attribute set and for each tag
  658. * that is stored in the tag vector. If the tag isnt found,
  659. * then the tag is removed from the vector and a corresponding
  660. * end tag is written out.
  661. *
  662. * @exception IOException on any I/O error
  663. */
  664. protected void closeOutUnwantedEmbeddedTags(AttributeSet attr) throws IOException {
  665. tagsToRemove.removeAllElements();
  666. // translate css attributes to html
  667. attr = convertToHTML(attr, null);
  668. HTML.Tag t;
  669. Object tValue;
  670. int firstIndex = -1;
  671. int size = tags.size();
  672. // First, find all the tags that need to be removed.
  673. for (int i = size - 1; i >= 0; i--) {
  674. t = (HTML.Tag)tags.elementAt(i);
  675. tValue = tagValues.elementAt(i);
  676. if ((attr == null) || noMatchForTagInAttributes(attr, t, tValue)) {
  677. firstIndex = i;
  678. tagsToRemove.addElement(t);
  679. }
  680. }
  681. if (firstIndex != -1) {
  682. // Then close them out.
  683. boolean removeAll = ((size - firstIndex) == tagsToRemove.size());
  684. for (int i = size - 1; i >= firstIndex; i--) {
  685. t = (HTML.Tag)tags.elementAt(i);
  686. if (removeAll || tagsToRemove.contains(t)) {
  687. tags.removeElementAt(i);
  688. tagValues.removeElementAt(i);
  689. }
  690. write('<');
  691. write('/');
  692. write(t.toString());
  693. write('>');
  694. }
  695. // Have to output any tags after firstIndex that still remaing,
  696. // as we closed them out, but they should remain open.
  697. size = tags.size();
  698. for (int i = firstIndex; i < size; i++) {
  699. t = (HTML.Tag)tags.elementAt(i);
  700. write('<');
  701. write(t.toString());
  702. Object o = tagValues.elementAt(i);
  703. if (o != null && o instanceof AttributeSet) {
  704. writeAttributes((AttributeSet)o);
  705. }
  706. write('>');
  707. }
  708. }
  709. }
  710. /**
  711. * Determines if the element associated with the attributeset
  712. * is a TEXTAREA or SELECT. If true, returns true else
  713. * false
  714. */
  715. private boolean isFormElementWithContent(AttributeSet attr) {
  716. if (matchNameAttribute(attr, HTML.Tag.TEXTAREA) ||
  717. matchNameAttribute(attr, HTML.Tag.SELECT)) {
  718. return true;
  719. }
  720. return false;
  721. }
  722. /**
  723. * Determines whether a the indentation needs to be
  724. * incremented. Basically, if next is a child of current, and
  725. * next is NOT a synthesized element, the indent level will be
  726. * incremented. If there is a parent-child relationship and "next"
  727. * is a synthesized element, then its children must be indented.
  728. * This state is maintained by the indentNext boolean.
  729. *
  730. * @return boolean that's true if indent level
  731. * needs incrementing.
  732. */
  733. private boolean indentNext = false;
  734. private boolean indentNeedsIncrementing(Element current, Element next) {
  735. if ((next.getParentElement() == current) && !inPre) {
  736. if (indentNext) {
  737. indentNext = false;
  738. return true;
  739. } else if (synthesizedElement(next)) {
  740. indentNext = true;
  741. } else if (!synthesizedElement(current)){
  742. return true;
  743. }
  744. }
  745. return false;
  746. }
  747. /**
  748. * Outputs the maps as elements. Maps are not stored as elements in
  749. * the document, and as such this is used to output them.
  750. */
  751. void writeMaps(Enumeration maps) throws IOException {
  752. if (maps != null) {
  753. while(maps.hasMoreElements()) {
  754. Map map = (Map)maps.nextElement();
  755. String name = map.getName();
  756. incrIndent();
  757. indent();
  758. write("<map");
  759. if (name != null) {
  760. write(" name=\"");
  761. write(name);
  762. write("\">");
  763. }
  764. else {
  765. write('>');
  766. }
  767. writeLineSeparator();
  768. incrIndent();
  769. // Output the areas
  770. AttributeSet[] areas = map.getAreas();
  771. if (areas != null) {
  772. for (int counter = 0, maxCounter = areas.length;
  773. counter < maxCounter; counter++) {
  774. indent();
  775. write("<area");
  776. writeAttributes(areas[counter]);
  777. write("></area>");
  778. writeLineSeparator();
  779. }
  780. }
  781. decrIndent();
  782. indent();
  783. write("</map>");
  784. writeLineSeparator();
  785. decrIndent();
  786. }
  787. }
  788. }
  789. /**
  790. * Outputs the styles as a single element. Styles are not stored as
  791. * elements, but part of the document. For the time being styles are
  792. * written out as a comment, inside a style tag.
  793. */
  794. void writeStyles(StyleSheet sheet) throws IOException {
  795. if (sheet != null) {
  796. Enumeration styles = sheet.getStyleNames();
  797. if (styles != null) {
  798. boolean outputStyle = false;
  799. while (styles.hasMoreElements()) {
  800. String name = (String)styles.nextElement();
  801. // Don't write out the default style.
  802. if (!StyleContext.DEFAULT_STYLE.equals(name) &&
  803. writeStyle(name, sheet.getStyle(name), outputStyle)) {
  804. outputStyle = true;
  805. }
  806. }
  807. if (outputStyle) {
  808. writeStyleEndTag();
  809. }
  810. }
  811. }
  812. }
  813. /**
  814. * Outputs the named style. <code>outputStyle</code> indicates
  815. * whether or not a style has been output yet. This will return
  816. * true if a style is written.
  817. */
  818. boolean writeStyle(String name, Style style, boolean outputStyle)
  819. throws IOException{
  820. boolean didOutputStyle = false;
  821. Enumeration attributes = style.getAttributeNames();
  822. if (attributes != null) {
  823. while (attributes.hasMoreElements()) {
  824. Object attribute = attributes.nextElement();
  825. if (attribute instanceof CSS.Attribute) {
  826. String value = style.getAttribute(attribute).toString();
  827. if (value != null) {
  828. if (!outputStyle) {
  829. writeStyleStartTag();
  830. outputStyle = true;
  831. }
  832. if (!didOutputStyle) {
  833. didOutputStyle = true;
  834. indent();
  835. write(name);
  836. write(" {");
  837. }
  838. else {
  839. write(";");
  840. }
  841. write(' ');
  842. write(attribute.toString());
  843. write(": ");
  844. write(value);
  845. }
  846. }
  847. }
  848. }
  849. if (didOutputStyle) {
  850. write(" }");
  851. writeLineSeparator();
  852. }
  853. return didOutputStyle;
  854. }
  855. void writeStyleStartTag() throws IOException {
  856. indent();
  857. write("<style type=\"text/css\">");
  858. incrIndent();
  859. writeLineSeparator();
  860. indent();
  861. write("<!--");
  862. incrIndent();
  863. writeLineSeparator();
  864. }
  865. void writeStyleEndTag() throws IOException {
  866. decrIndent();
  867. indent();
  868. write("-->");
  869. writeLineSeparator();
  870. decrIndent();
  871. indent();
  872. write("</style>");
  873. writeLineSeparator();
  874. indent();
  875. }
  876. // --- conversion support ---------------------------
  877. /**
  878. * Convert the give set of attributes to be html for
  879. * the purpose of writing them out. Any keys that
  880. * have been converted will not appear in the resultant
  881. * set. Any keys not converted will appear in the
  882. * resultant set the same as the received set.<p>
  883. * This will put the converted values into <code>to</code>, unless
  884. * it is null in which case a temporary AttributeSet will be returned.
  885. */
  886. AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
  887. if (to == null) {
  888. to = convAttr;
  889. }
  890. to.removeAttributes(to);
  891. if (writeCSS) {
  892. convertToHTML40(from, to);
  893. } else {
  894. convertToHTML32(from, to);
  895. }
  896. return to;
  897. }
  898. /**
  899. * If true, the writer will emit CSS attributes in preference
  900. * to HTML tags/attributes (i.e. It will emit an HTML 4.0
  901. * style).
  902. */
  903. private boolean writeCSS = false;
  904. /**
  905. * Buffer for the purpose of attribute conversion
  906. */
  907. private MutableAttributeSet convAttr = new SimpleAttributeSet();
  908. /**
  909. * Buffer for the purpose of attribute conversion. This can be
  910. * used if convAttr is being used.
  911. */
  912. private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
  913. /**
  914. * Create an older style of HTML attributes. This will
  915. * convert character level attributes that have a StyleConstants
  916. * mapping over to an HTML tag/attribute. Other CSS attributes
  917. * will be placed in an HTML style attribute.
  918. */
  919. private static void convertToHTML32(AttributeSet from, MutableAttributeSet to) {
  920. if (from == null) {
  921. return;
  922. }
  923. Enumeration keys = from.getAttributeNames();
  924. String value = "";
  925. while (keys.hasMoreElements()) {
  926. Object key = keys.nextElement();
  927. if (key instanceof CSS.Attribute) {
  928. if ((key == CSS.Attribute.FONT_FAMILY) ||
  929. (key == CSS.Attribute.FONT_SIZE) ||
  930. (key == CSS.Attribute.COLOR)) {
  931. createFontAttribute((CSS.Attribute)key, from, to);
  932. } else if (key == CSS.Attribute.FONT_WEIGHT) {
  933. // add a bold tag is weight is bold
  934. CSS.FontWeight weightValue = (CSS.FontWeight)
  935. from.getAttribute(CSS.Attribute.FONT_WEIGHT);
  936. if ((weightValue != null) && (weightValue.getValue() > 400)) {
  937. to.addAttribute(HTML.Tag.B, SimpleAttributeSet.EMPTY);
  938. }
  939. } else if (key == CSS.Attribute.FONT_STYLE) {
  940. String s = from.getAttribute(key).toString();
  941. if (s.indexOf("italic") >= 0) {
  942. to.addAttribute(HTML.Tag.I, SimpleAttributeSet.EMPTY);
  943. }
  944. } else if (key == CSS.Attribute.TEXT_DECORATION) {
  945. String decor = from.getAttribute(key).toString();
  946. if (decor.indexOf("underline") >= 0) {
  947. to.addAttribute(HTML.Tag.U, SimpleAttributeSet.EMPTY);
  948. }
  949. if (decor.indexOf("line-through") >= 0) {
  950. to.addAttribute(HTML.Tag.STRIKE, SimpleAttributeSet.EMPTY);
  951. }
  952. } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
  953. String vAlign = from.getAttribute(key).toString();
  954. if (vAlign.indexOf("sup") >= 0) {
  955. to.addAttribute(HTML.Tag.SUP, SimpleAttributeSet.EMPTY);
  956. }
  957. if (vAlign.indexOf("sub") >= 0) {
  958. to.addAttribute(HTML.Tag.SUB, SimpleAttributeSet.EMPTY);
  959. }
  960. } else if (key == CSS.Attribute.TEXT_ALIGN) {
  961. to.addAttribute(HTML.Attribute.ALIGN,
  962. from.getAttribute(key).toString());
  963. } else {
  964. // default is to store in a HTML style attribute
  965. if (value.length() > 0) {
  966. value = value + "; ";
  967. }
  968. value = value + key + ": " + from.getAttribute(key);
  969. }
  970. } else {
  971. to.addAttribute(key, from.getAttribute(key));
  972. }
  973. }
  974. if (value.length() > 0) {
  975. to.addAttribute(HTML.Attribute.STYLE, value);
  976. }
  977. }
  978. /**
  979. * Create/update an HTML <font> tag attribute. The
  980. * value of the attribute should be a MutableAttributeSet so
  981. * that the attributes can be updated as they are discovered.
  982. */
  983. private static void createFontAttribute(CSS.Attribute a, AttributeSet from,
  984. MutableAttributeSet to) {
  985. MutableAttributeSet fontAttr = (MutableAttributeSet)
  986. to.getAttribute(HTML.Tag.FONT);
  987. if (fontAttr == null) {
  988. fontAttr = new SimpleAttributeSet();
  989. to.addAttribute(HTML.Tag.FONT, fontAttr);
  990. }
  991. // edit the parameters to the font tag
  992. String htmlValue = from.getAttribute(a).toString();
  993. if (a == CSS.Attribute.FONT_FAMILY) {
  994. fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
  995. } else if (a == CSS.Attribute.FONT_SIZE) {
  996. fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
  997. } else if (a == CSS.Attribute.COLOR) {
  998. fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
  999. }
  1000. }
  1001. /**
  1002. * Copies the given AttributeSet to a new set, converting
  1003. * any CSS attributes found to arguments of an HTML style
  1004. * attribute.
  1005. */
  1006. private static void convertToHTML40(AttributeSet from, MutableAttributeSet to) {
  1007. Enumeration keys = from.getAttributeNames();
  1008. String value = "";
  1009. while (keys.hasMoreElements()) {
  1010. Object key = keys.nextElement();
  1011. if (key instanceof CSS.Attribute) {
  1012. value = value + " " + key + "=" + from.getAttribute(key) + ";";
  1013. } else {
  1014. to.addAttribute(key, from.getAttribute(key));
  1015. }
  1016. }
  1017. if (value.length() > 0) {
  1018. to.addAttribute(HTML.Attribute.STYLE, value);
  1019. }
  1020. }
  1021. //
  1022. // Overrides the writing methods to only break a string when
  1023. // canBreakString is true.
  1024. // In a future release it is likely AbstractWriter will get this
  1025. // functionality.
  1026. //
  1027. /**
  1028. * Writes the line separator. This is overriden to make sure we don't
  1029. * replace the newline content in case it is outside normal ascii.
  1030. */
  1031. protected void writeLineSeparator() throws IOException {
  1032. boolean oldReplace = replaceEntities;
  1033. replaceEntities = false;
  1034. super.writeLineSeparator();
  1035. replaceEntities = oldReplace;
  1036. }
  1037. /**
  1038. * This method is overriden to map any character entities, such as
  1039. * < to &lt;. <code>super.output</code> will be invoked to
  1040. * write the content.
  1041. */
  1042. protected void output(char[] chars, int start, int length)
  1043. throws IOException {
  1044. if (!replaceEntities) {
  1045. super.output(chars, start, length);
  1046. return;
  1047. }
  1048. int last = start;
  1049. length += start;
  1050. for (int counter = start; counter < length; counter++) {
  1051. // This will change, we need better support character level
  1052. // entities.
  1053. switch(chars[counter]) {
  1054. // Character level entities.
  1055. case '<':
  1056. if (counter > last) {
  1057. super.output(chars, last, counter - last);
  1058. }
  1059. last = counter + 1;
  1060. output("<");
  1061. break;
  1062. case '>':
  1063. if (counter > last) {
  1064. super.output(chars, last, counter - last);
  1065. }
  1066. last = counter + 1;
  1067. output(">");
  1068. break;
  1069. case '&':
  1070. if (counter > last) {
  1071. super.output(chars, last, counter - last);
  1072. }
  1073. last = counter + 1;
  1074. output("&");
  1075. break;
  1076. case '"':
  1077. if (counter > last) {
  1078. super.output(chars, last, counter - last);
  1079. }
  1080. last = counter + 1;
  1081. output(""");
  1082. break;
  1083. // Special characters
  1084. case '\n':
  1085. case '\t':
  1086. case '\r':
  1087. break;
  1088. default:
  1089. if (chars[counter] < ' ' || chars[counter] > 127) {
  1090. if (counter > last) {
  1091. super.output(chars, last, counter - last);
  1092. }
  1093. last = counter + 1;
  1094. // If the character is outside of ascii, write the
  1095. // numeric value.
  1096. output("&#");
  1097. output(String.valueOf((int)chars[counter]));
  1098. output(";");
  1099. }
  1100. break;
  1101. }
  1102. }
  1103. if (last < length) {
  1104. super.output(chars, last, length - last);
  1105. }
  1106. }
  1107. /**
  1108. * This directly invokes super's <code>output</code> after converting
  1109. * <code>string</code> to a char[].
  1110. */
  1111. private void output(String string) throws IOException {
  1112. int length = string.length();
  1113. if (tempChars == null || tempChars.length < length) {
  1114. tempChars = new char[length];
  1115. }
  1116. string.getChars(0, length, tempChars, 0);
  1117. super.output(tempChars, 0, length);
  1118. }
  1119. }