0001 /*
0002 * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text.html;
0026
0027 import javax.swing.text.*;
0028 import java.io.Writer;
0029 import java.util.Stack;
0030 import java.util.Enumeration;
0031 import java.util.Vector;
0032 import java.io.IOException;
0033 import java.util.StringTokenizer;
0034 import java.util.NoSuchElementException;
0035 import java.net.URL;
0036
0037 /**
0038 * This is a writer for HTMLDocuments.
0039 *
0040 * @author Sunita Mani
0041 * @version 1.26, 02/02/00
0042 */
0043
0044 public class HTMLWriter extends AbstractWriter {
0045 /*
0046 * Stores all elements for which end tags have to
0047 * be emitted.
0048 */
0049 private Stack blockElementStack = new Stack();
0050 private boolean inContent = false;
0051 private boolean inPre = false;
0052 /** When inPre is true, this will indicate the end offset of the pre
0053 * element. */
0054 private int preEndOffset;
0055 private boolean inTextArea = false;
0056 private boolean newlineOutputed = false;
0057 private boolean completeDoc;
0058
0059 /*
0060 * Stores all embedded tags. Embedded tags are tags that are
0061 * stored as attributes in other tags. Generally they're
0062 * character level attributes. Examples include
0063 * <b>, <i>, <font>, and <a>.
0064 */
0065 private Vector tags = new Vector(10);
0066
0067 /**
0068 * Values for the tags.
0069 */
0070 private Vector tagValues = new Vector(10);
0071
0072 /**
0073 * Used when writing out content.
0074 */
0075 private Segment segment;
0076
0077 /*
0078 * This is used in closeOutUnwantedEmbeddedTags.
0079 */
0080 private Vector tagsToRemove = new Vector(10);
0081
0082 /**
0083 * Set to true after the head has been output.
0084 */
0085 private boolean wroteHead;
0086
0087 /**
0088 * Set to true when entities (such as <) should be replaced.
0089 */
0090 private boolean replaceEntities;
0091
0092 /**
0093 * Temporary buffer.
0094 */
0095 private char[] tempChars;
0096
0097 /**
0098 * Creates a new HTMLWriter.
0099 *
0100 * @param w a Writer
0101 * @param doc an HTMLDocument
0102 *
0103 */
0104 public HTMLWriter(Writer w, HTMLDocument doc) {
0105 this (w, doc, 0, doc.getLength());
0106 }
0107
0108 /**
0109 * Creates a new HTMLWriter.
0110 *
0111 * @param w a Writer
0112 * @param doc an HTMLDocument
0113 * @param pos the document location from which to fetch the content
0114 * @param len the amount to write out
0115 */
0116 public HTMLWriter(Writer w, HTMLDocument doc, int pos, int len) {
0117 super (w, doc, pos, len);
0118 completeDoc = (pos == 0 && len == doc.getLength());
0119 setLineLength(80);
0120 }
0121
0122 /**
0123 * Iterates over the
0124 * Element tree and controls the writing out of
0125 * all the tags and its attributes.
0126 *
0127 * @exception IOException on any I/O error
0128 * @exception BadLocationException if pos represents an invalid
0129 * location within the document.
0130 *
0131 */
0132 public void write() throws IOException, BadLocationException {
0133 ElementIterator it = getElementIterator();
0134 Element current = null;
0135 Element next = null;
0136
0137 wroteHead = false;
0138 setCurrentLineLength(0);
0139 replaceEntities = false;
0140 setCanWrapLines(false);
0141 if (segment == null) {
0142 segment = new Segment();
0143 }
0144 inPre = false;
0145 boolean forcedBody = false;
0146 while ((next = it.next()) != null) {
0147 if (!inRange(next)) {
0148 if (completeDoc
0149 && next.getAttributes().getAttribute(
0150 StyleConstants.NameAttribute) == HTML.Tag.BODY) {
0151 forcedBody = true;
0152 } else {
0153 continue;
0154 }
0155 }
0156 if (current != null) {
0157
0158 /*
0159 if next is child of current increment indent
0160 */
0161
0162 if (indentNeedsIncrementing(current, next)) {
0163 incrIndent();
0164 } else if (current.getParentElement() != next
0165 .getParentElement()) {
0166 /*
0167 next and current are not siblings
0168 so emit end tags for items on the stack until the
0169 item on top of the stack, is the parent of the
0170 next.
0171 */
0172 Element top = (Element) blockElementStack.peek();
0173 while (top != next.getParentElement()) {
0174 /*
0175 pop() will return top.
0176 */
0177 blockElementStack.pop();
0178 if (!synthesizedElement(top)) {
0179 AttributeSet attrs = top.getAttributes();
0180 if (!matchNameAttribute(attrs, HTML.Tag.PRE)
0181 && !isFormElementWithContent(attrs)) {
0182 decrIndent();
0183 }
0184 endTag(top);
0185 }
0186 top = (Element) blockElementStack.peek();
0187 }
0188 } else if (current.getParentElement() == next
0189 .getParentElement()) {
0190 /*
0191 if next and current are siblings the indent level
0192 is correct. But, we need to make sure that if current is
0193 on the stack, we pop it off, and put out its end tag.
0194 */
0195 Element top = (Element) blockElementStack.peek();
0196 if (top == current) {
0197 blockElementStack.pop();
0198 endTag(top);
0199 }
0200 }
0201 }
0202 if (!next.isLeaf()
0203 || isFormElementWithContent(next.getAttributes())) {
0204 blockElementStack.push(next);
0205 startTag(next);
0206 } else {
0207 emptyTag(next);
0208 }
0209 current = next;
0210 }
0211 /* Emit all remaining end tags */
0212
0213 /* A null parameter ensures that all embedded tags
0214 currently in the tags vector have their
0215 corresponding end tags written out.
0216 */
0217 closeOutUnwantedEmbeddedTags(null);
0218
0219 if (forcedBody) {
0220 blockElementStack.pop();
0221 endTag(current);
0222 }
0223 while (!blockElementStack.empty()) {
0224 current = (Element) blockElementStack.pop();
0225 if (!synthesizedElement(current)) {
0226 AttributeSet attrs = current.getAttributes();
0227 if (!matchNameAttribute(attrs, HTML.Tag.PRE)
0228 && !isFormElementWithContent(attrs)) {
0229 decrIndent();
0230 }
0231 endTag(current);
0232 }
0233 }
0234
0235 if (completeDoc) {
0236 writeAdditionalComments();
0237 }
0238
0239 segment.array = null;
0240 }
0241
0242 /**
0243 * Writes out the attribute set. Ignores all
0244 * attributes with a key of type HTML.Tag,
0245 * attributes with a key of type StyleConstants,
0246 * and attributes with a key of type
0247 * HTML.Attribute.ENDTAG.
0248 *
0249 * @param attr an AttributeSet
0250 * @exception IOException on any I/O error
0251 *
0252 */
0253 protected void writeAttributes(AttributeSet attr)
0254 throws IOException {
0255 // translate css attributes to html
0256 convAttr.removeAttributes(convAttr);
0257 convertToHTML32(attr, convAttr);
0258
0259 Enumeration names = convAttr.getAttributeNames();
0260 while (names.hasMoreElements()) {
0261 Object name = names.nextElement();
0262 if (name instanceof HTML.Tag
0263 || name instanceof StyleConstants
0264 || name == HTML.Attribute.ENDTAG) {
0265 continue;
0266 }
0267 write(" " + name + "=\"" + convAttr.getAttribute(name)
0268 + "\"");
0269 }
0270 }
0271
0272 /**
0273 * Writes out all empty elements (all tags that have no
0274 * corresponding end tag).
0275 *
0276 * @param elem an Element
0277 * @exception IOException on any I/O error
0278 * @exception BadLocationException if pos represents an invalid
0279 * location within the document.
0280 */
0281 protected void emptyTag(Element elem) throws BadLocationException,
0282 IOException {
0283
0284 if (!inContent && !inPre) {
0285 indentSmart();
0286 }
0287
0288 AttributeSet attr = elem.getAttributes();
0289 closeOutUnwantedEmbeddedTags(attr);
0290 writeEmbeddedTags(attr);
0291
0292 if (matchNameAttribute(attr, HTML.Tag.CONTENT)) {
0293 inContent = true;
0294 text(elem);
0295 } else if (matchNameAttribute(attr, HTML.Tag.COMMENT)) {
0296 comment(elem);
0297 } else {
0298 boolean isBlock = isBlockTag(elem.getAttributes());
0299 if (inContent && isBlock) {
0300 writeLineSeparator();
0301 indentSmart();
0302 }
0303
0304 Object nameTag = (attr != null) ? attr
0305 .getAttribute(StyleConstants.NameAttribute) : null;
0306 Object endTag = (attr != null) ? attr
0307 .getAttribute(HTML.Attribute.ENDTAG) : null;
0308
0309 boolean outputEndTag = false;
0310 // If an instance of an UNKNOWN Tag, or an instance of a
0311 // tag that is only visible during editing
0312 //
0313 if (nameTag != null && endTag != null
0314 && (endTag instanceof String)
0315 && ((String) endTag).equals("true")) {
0316 outputEndTag = true;
0317 }
0318
0319 if (completeDoc && matchNameAttribute(attr, HTML.Tag.HEAD)) {
0320 if (outputEndTag) {
0321 // Write out any styles.
0322 writeStyles(((HTMLDocument) getDocument())
0323 .getStyleSheet());
0324 }
0325 wroteHead = true;
0326 }
0327
0328 write('<');
0329 if (outputEndTag) {
0330 write('/');
0331 }
0332 write(elem.getName());
0333 writeAttributes(attr);
0334 write('>');
0335 if (matchNameAttribute(attr, HTML.Tag.TITLE)
0336 && !outputEndTag) {
0337 Document doc = elem.getDocument();
0338 String title = (String) doc
0339 .getProperty(Document.TitleProperty);
0340 write(title);
0341 } else if (!inContent || isBlock) {
0342 writeLineSeparator();
0343 if (isBlock && inContent) {
0344 indentSmart();
0345 }
0346 }
0347 }
0348 }
0349
0350 /**
0351 * Determines if the HTML.Tag associated with the
0352 * element is a block tag.
0353 *
0354 * @param attr an AttributeSet
0355 * @return true if tag is block tag, false otherwise.
0356 */
0357 protected boolean isBlockTag(AttributeSet attr) {
0358 Object o = attr.getAttribute(StyleConstants.NameAttribute);
0359 if (o instanceof HTML.Tag) {
0360 HTML.Tag name = (HTML.Tag) o;
0361 return name.isBlock();
0362 }
0363 return false;
0364 }
0365
0366 /**
0367 * Writes out a start tag for the element.
0368 * Ignores all synthesized elements.
0369 *
0370 * @param elem an Element
0371 * @exception IOException on any I/O error
0372 */
0373 protected void startTag(Element elem) throws IOException,
0374 BadLocationException {
0375
0376 if (synthesizedElement(elem)) {
0377 return;
0378 }
0379
0380 // Determine the name, as an HTML.Tag.
0381 AttributeSet attr = elem.getAttributes();
0382 Object nameAttribute = attr
0383 .getAttribute(StyleConstants.NameAttribute);
0384 HTML.Tag name;
0385 if (nameAttribute instanceof HTML.Tag) {
0386 name = (HTML.Tag) nameAttribute;
0387 } else {
0388 name = null;
0389 }
0390
0391 if (name == HTML.Tag.PRE) {
0392 inPre = true;
0393 preEndOffset = elem.getEndOffset();
0394 }
0395
0396 // write out end tags for item on stack
0397 closeOutUnwantedEmbeddedTags(attr);
0398
0399 if (inContent) {
0400 writeLineSeparator();
0401 inContent = false;
0402 newlineOutputed = false;
0403 }
0404
0405 if (completeDoc && name == HTML.Tag.BODY && !wroteHead) {
0406 // If the head has not been output, output it and the styles.
0407 wroteHead = true;
0408 indentSmart();
0409 write("<head>");
0410 writeLineSeparator();
0411 incrIndent();
0412 writeStyles(((HTMLDocument) getDocument()).getStyleSheet());
0413 decrIndent();
0414 writeLineSeparator();
0415 indentSmart();
0416 write("</head>");
0417 writeLineSeparator();
0418 }
0419
0420 indentSmart();
0421 write('<');
0422 write(elem.getName());
0423 writeAttributes(attr);
0424 write('>');
0425 if (name != HTML.Tag.PRE) {
0426 writeLineSeparator();
0427 }
0428
0429 if (name == HTML.Tag.TEXTAREA) {
0430 textAreaContent(elem.getAttributes());
0431 } else if (name == HTML.Tag.SELECT) {
0432 selectContent(elem.getAttributes());
0433 } else if (completeDoc && name == HTML.Tag.BODY) {
0434 // Write out the maps, which is not stored as Elements in
0435 // the Document.
0436 writeMaps(((HTMLDocument) getDocument()).getMaps());
0437 } else if (name == HTML.Tag.HEAD) {
0438 HTMLDocument document = (HTMLDocument) getDocument();
0439 wroteHead = true;
0440 incrIndent();
0441 writeStyles(document.getStyleSheet());
0442 if (document.hasBaseTag()) {
0443 indentSmart();
0444 write("<base href=\"" + document.getBase() + "\">");
0445 writeLineSeparator();
0446 }
0447 decrIndent();
0448 }
0449
0450 }
0451
0452 /**
0453 * Writes out text that is contained in a TEXTAREA form
0454 * element.
0455 *
0456 * @param attr an AttributeSet
0457 * @exception IOException on any I/O error
0458 * @exception BadLocationException if pos represents an invalid
0459 * location within the document.
0460 */
0461 protected void textAreaContent(AttributeSet attr)
0462 throws BadLocationException, IOException {
0463 Document doc = (Document) attr
0464 .getAttribute(StyleConstants.ModelAttribute);
0465 if (doc != null && doc.getLength() > 0) {
0466 if (segment == null) {
0467 segment = new Segment();
0468 }
0469 doc.getText(0, doc.getLength(), segment);
0470 if (segment.count > 0) {
0471 inTextArea = true;
0472 incrIndent();
0473 indentSmart();
0474 setCanWrapLines(true);
0475 replaceEntities = true;
0476 write(segment.array, segment.offset, segment.count);
0477 replaceEntities = false;
0478 setCanWrapLines(false);
0479 writeLineSeparator();
0480 inTextArea = false;
0481 decrIndent();
0482 }
0483 }
0484 }
0485
0486 /**
0487 * Writes out text. If a range is specified when the constructor
0488 * is invoked, then only the appropriate range of text is written
0489 * out.
0490 *
0491 * @param elem an Element
0492 * @exception IOException on any I/O error
0493 * @exception BadLocationException if pos represents an invalid
0494 * location within the document.
0495 */
0496 protected void text(Element elem) throws BadLocationException,
0497 IOException {
0498 int start = Math.max(getStartOffset(), elem.getStartOffset());
0499 int end = Math.min(getEndOffset(), elem.getEndOffset());
0500 if (start < end) {
0501 if (segment == null) {
0502 segment = new Segment();
0503 }
0504 getDocument().getText(start, end - start, segment);
0505 newlineOutputed = false;
0506 if (segment.count > 0) {
0507 if (segment.array[segment.offset + segment.count - 1] == '\n') {
0508 newlineOutputed = true;
0509 }
0510 if (inPre && end == preEndOffset) {
0511 if (segment.count > 1) {
0512 segment.count--;
0513 } else {
0514 return;
0515 }
0516 }
0517 replaceEntities = true;
0518 setCanWrapLines(!inPre);
0519 write(segment.array, segment.offset, segment.count);
0520 setCanWrapLines(false);
0521 replaceEntities = false;
0522 }
0523 }
0524 }
0525
0526 /**
0527 * Writes out the content of the SELECT form element.
0528 *
0529 * @param attr the AttributeSet associated with the form element
0530 * @exception IOException on any I/O error
0531 */
0532 protected void selectContent(AttributeSet attr) throws IOException {
0533 Object model = attr.getAttribute(StyleConstants.ModelAttribute);
0534 incrIndent();
0535 if (model instanceof OptionListModel) {
0536 OptionListModel listModel = (OptionListModel) model;
0537 int size = listModel.getSize();
0538 for (int i = 0; i < size; i++) {
0539 Option option = (Option) listModel.getElementAt(i);
0540 writeOption(option);
0541 }
0542 } else if (model instanceof OptionComboBoxModel) {
0543 OptionComboBoxModel comboBoxModel = (OptionComboBoxModel) model;
0544 int size = comboBoxModel.getSize();
0545 for (int i = 0; i < size; i++) {
0546 Option option = (Option) comboBoxModel.getElementAt(i);
0547 writeOption(option);
0548 }
0549 }
0550 decrIndent();
0551 }
0552
0553 /**
0554 * Writes out the content of the Option form element.
0555 * @param option an Option
0556 * @exception IOException on any I/O error
0557 *
0558 */
0559 protected void writeOption(Option option) throws IOException {
0560
0561 indentSmart();
0562 write('<');
0563 write("option");
0564 // PENDING: should this be changed to check for null first?
0565 Object value = option.getAttributes().getAttribute(
0566 HTML.Attribute.VALUE);
0567 if (value != null) {
0568 write(" value=" + value);
0569 }
0570 if (option.isSelected()) {
0571 write(" selected");
0572 }
0573 write('>');
0574 if (option.getLabel() != null) {
0575 write(option.getLabel());
0576 }
0577 writeLineSeparator();
0578 }
0579
0580 /**
0581 * Writes out an end tag for the element.
0582 *
0583 * @param elem an Element
0584 * @exception IOException on any I/O error
0585 */
0586 protected void endTag(Element elem) throws IOException {
0587 if (synthesizedElement(elem)) {
0588 return;
0589 }
0590
0591 // write out end tags for item on stack
0592 closeOutUnwantedEmbeddedTags(elem.getAttributes());
0593 if (inContent) {
0594 if (!newlineOutputed && !inPre) {
0595 writeLineSeparator();
0596 }
0597 newlineOutputed = false;
0598 inContent = false;
0599 }
0600 if (!inPre) {
0601 indentSmart();
0602 }
0603 if (matchNameAttribute(elem.getAttributes(), HTML.Tag.PRE)) {
0604 inPre = false;
0605 }
0606 write('<');
0607 write('/');
0608 write(elem.getName());
0609 write('>');
0610 writeLineSeparator();
0611 }
0612
0613 /**
0614 * Writes out comments.
0615 *
0616 * @param elem an Element
0617 * @exception IOException on any I/O error
0618 * @exception BadLocationException if pos represents an invalid
0619 * location within the document.
0620 */
0621 protected void comment(Element elem) throws BadLocationException,
0622 IOException {
0623 AttributeSet as = elem.getAttributes();
0624 if (matchNameAttribute(as, HTML.Tag.COMMENT)) {
0625 Object comment = as.getAttribute(HTML.Attribute.COMMENT);
0626 if (comment instanceof String) {
0627 writeComment((String) comment);
0628 } else {
0629 writeComment(null);
0630 }
0631 }
0632 }
0633
0634 /**
0635 * Writes out comment string.
0636 *
0637 * @param string the comment
0638 * @exception IOException on any I/O error
0639 * @exception BadLocationException if pos represents an invalid
0640 * location within the document.
0641 */
0642 void writeComment(String string) throws IOException {
0643 write("<!--");
0644 if (string != null) {
0645 write(string);
0646 }
0647 write("-->");
0648 writeLineSeparator();
0649 indentSmart();
0650 }
0651
0652 /**
0653 * Writes out any additional comments (comments outside of the body)
0654 * stored under the property HTMLDocument.AdditionalComments.
0655 */
0656 void writeAdditionalComments() throws IOException {
0657 Object comments = getDocument().getProperty(
0658 HTMLDocument.AdditionalComments);
0659
0660 if (comments instanceof Vector) {
0661 Vector v = (Vector) comments;
0662 for (int counter = 0, maxCounter = v.size(); counter < maxCounter; counter++) {
0663 writeComment(v.elementAt(counter).toString());
0664 }
0665 }
0666 }
0667
0668 /**
0669 * Returns true if the element is a
0670 * synthesized element. Currently we are only testing
0671 * for the p-implied tag.
0672 */
0673 protected boolean synthesizedElement(Element elem) {
0674 if (matchNameAttribute(elem.getAttributes(), HTML.Tag.IMPLIED)) {
0675 return true;
0676 }
0677 return false;
0678 }
0679
0680 /**
0681 * Returns true if the StyleConstants.NameAttribute is
0682 * equal to the tag that is passed in as a parameter.
0683 */
0684 protected boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
0685 Object o = attr.getAttribute(StyleConstants.NameAttribute);
0686 if (o instanceof HTML.Tag) {
0687 HTML.Tag name = (HTML.Tag) o;
0688 if (name == tag) {
0689 return true;
0690 }
0691 }
0692 return false;
0693 }
0694
0695 /**
0696 * Searches for embedded tags in the AttributeSet
0697 * and writes them out. It also stores these tags in a vector
0698 * so that when appropriate the corresponding end tags can be
0699 * written out.
0700 *
0701 * @exception IOException on any I/O error
0702 */
0703 protected void writeEmbeddedTags(AttributeSet attr)
0704 throws IOException {
0705
0706 // translate css attributes to html
0707 attr = convertToHTML(attr, oConvAttr);
0708
0709 Enumeration names = attr.getAttributeNames();
0710 while (names.hasMoreElements()) {
0711 Object name = names.nextElement();
0712 if (name instanceof HTML.Tag) {
0713 HTML.Tag tag = (HTML.Tag) name;
0714 if (tag == HTML.Tag.FORM || tags.contains(tag)) {
0715 continue;
0716 }
0717 write('<');
0718 write(tag.toString());
0719 Object o = attr.getAttribute(tag);
0720 if (o != null && o instanceof AttributeSet) {
0721 writeAttributes((AttributeSet) o);
0722 }
0723 write('>');
0724 tags.addElement(tag);
0725 tagValues.addElement(o);
0726 }
0727 }
0728 }
0729
0730 /**
0731 * Searches the attribute set for a tag, both of which
0732 * are passed in as a parameter. Returns true if no match is found
0733 * and false otherwise.
0734 */
0735 private boolean noMatchForTagInAttributes(AttributeSet attr,
0736 HTML.Tag t, Object tagValue) {
0737 if (attr != null && attr.isDefined(t)) {
0738 Object newValue = attr.getAttribute(t);
0739
0740 if ((tagValue == null) ? (newValue == null)
0741 : (newValue != null && tagValue.equals(newValue))) {
0742 return false;
0743 }
0744 }
0745 return true;
0746 }
0747
0748 /**
0749 * Searches the attribute set and for each tag
0750 * that is stored in the tag vector. If the tag isnt found,
0751 * then the tag is removed from the vector and a corresponding
0752 * end tag is written out.
0753 *
0754 * @exception IOException on any I/O error
0755 */
0756 protected void closeOutUnwantedEmbeddedTags(AttributeSet attr)
0757 throws IOException {
0758
0759 tagsToRemove.removeAllElements();
0760
0761 // translate css attributes to html
0762 attr = convertToHTML(attr, null);
0763
0764 HTML.Tag t;
0765 Object tValue;
0766 int firstIndex = -1;
0767 int size = tags.size();
0768 // First, find all the tags that need to be removed.
0769 for (int i = size - 1; i >= 0; i--) {
0770 t = (HTML.Tag) tags.elementAt(i);
0771 tValue = tagValues.elementAt(i);
0772 if ((attr == null)
0773 || noMatchForTagInAttributes(attr, t, tValue)) {
0774 firstIndex = i;
0775 tagsToRemove.addElement(t);
0776 }
0777 }
0778 if (firstIndex != -1) {
0779 // Then close them out.
0780 boolean removeAll = ((size - firstIndex) == tagsToRemove
0781 .size());
0782 for (int i = size - 1; i >= firstIndex; i--) {
0783 t = (HTML.Tag) tags.elementAt(i);
0784 if (removeAll || tagsToRemove.contains(t)) {
0785 tags.removeElementAt(i);
0786 tagValues.removeElementAt(i);
0787 }
0788 write('<');
0789 write('/');
0790 write(t.toString());
0791 write('>');
0792 }
0793 // Have to output any tags after firstIndex that still remaing,
0794 // as we closed them out, but they should remain open.
0795 size = tags.size();
0796 for (int i = firstIndex; i < size; i++) {
0797 t = (HTML.Tag) tags.elementAt(i);
0798 write('<');
0799 write(t.toString());
0800 Object o = tagValues.elementAt(i);
0801 if (o != null && o instanceof AttributeSet) {
0802 writeAttributes((AttributeSet) o);
0803 }
0804 write('>');
0805 }
0806 }
0807 }
0808
0809 /**
0810 * Determines if the element associated with the attributeset
0811 * is a TEXTAREA or SELECT. If true, returns true else
0812 * false
0813 */
0814 private boolean isFormElementWithContent(AttributeSet attr) {
0815 if (matchNameAttribute(attr, HTML.Tag.TEXTAREA)
0816 || matchNameAttribute(attr, HTML.Tag.SELECT)) {
0817 return true;
0818 }
0819 return false;
0820 }
0821
0822 /**
0823 * Determines whether a the indentation needs to be
0824 * incremented. Basically, if next is a child of current, and
0825 * next is NOT a synthesized element, the indent level will be
0826 * incremented. If there is a parent-child relationship and "next"
0827 * is a synthesized element, then its children must be indented.
0828 * This state is maintained by the indentNext boolean.
0829 *
0830 * @return boolean that's true if indent level
0831 * needs incrementing.
0832 */
0833 private boolean indentNext = false;
0834
0835 private boolean indentNeedsIncrementing(Element current,
0836 Element next) {
0837 if ((next.getParentElement() == current) && !inPre) {
0838 if (indentNext) {
0839 indentNext = false;
0840 return true;
0841 } else if (synthesizedElement(next)) {
0842 indentNext = true;
0843 } else if (!synthesizedElement(current)) {
0844 return true;
0845 }
0846 }
0847 return false;
0848 }
0849
0850 /**
0851 * Outputs the maps as elements. Maps are not stored as elements in
0852 * the document, and as such this is used to output them.
0853 */
0854 void writeMaps(Enumeration maps) throws IOException {
0855 if (maps != null) {
0856 while (maps.hasMoreElements()) {
0857 Map map = (Map) maps.nextElement();
0858 String name = map.getName();
0859
0860 incrIndent();
0861 indentSmart();
0862 write("<map");
0863 if (name != null) {
0864 write(" name=\"");
0865 write(name);
0866 write("\">");
0867 } else {
0868 write('>');
0869 }
0870 writeLineSeparator();
0871 incrIndent();
0872
0873 // Output the areas
0874 AttributeSet[] areas = map.getAreas();
0875 if (areas != null) {
0876 for (int counter = 0, maxCounter = areas.length; counter < maxCounter; counter++) {
0877 indentSmart();
0878 write("<area");
0879 writeAttributes(areas[counter]);
0880 write("></area>");
0881 writeLineSeparator();
0882 }
0883 }
0884 decrIndent();
0885 indentSmart();
0886 write("</map>");
0887 writeLineSeparator();
0888 decrIndent();
0889 }
0890 }
0891 }
0892
0893 /**
0894 * Outputs the styles as a single element. Styles are not stored as
0895 * elements, but part of the document. For the time being styles are
0896 * written out as a comment, inside a style tag.
0897 */
0898 void writeStyles(StyleSheet sheet) throws IOException {
0899 if (sheet != null) {
0900 Enumeration styles = sheet.getStyleNames();
0901 if (styles != null) {
0902 boolean outputStyle = false;
0903 while (styles.hasMoreElements()) {
0904 String name = (String) styles.nextElement();
0905 // Don't write out the default style.
0906 if (!StyleContext.DEFAULT_STYLE.equals(name)
0907 && writeStyle(name, sheet.getStyle(name),
0908 outputStyle)) {
0909 outputStyle = true;
0910 }
0911 }
0912 if (outputStyle) {
0913 writeStyleEndTag();
0914 }
0915 }
0916 }
0917 }
0918
0919 /**
0920 * Outputs the named style. <code>outputStyle</code> indicates
0921 * whether or not a style has been output yet. This will return
0922 * true if a style is written.
0923 */
0924 boolean writeStyle(String name, Style style, boolean outputStyle)
0925 throws IOException {
0926 boolean didOutputStyle = false;
0927 Enumeration attributes = style.getAttributeNames();
0928 if (attributes != null) {
0929 while (attributes.hasMoreElements()) {
0930 Object attribute = attributes.nextElement();
0931 if (attribute instanceof CSS.Attribute) {
0932 String value = style.getAttribute(attribute)
0933 .toString();
0934 if (value != null) {
0935 if (!outputStyle) {
0936 writeStyleStartTag();
0937 outputStyle = true;
0938 }
0939 if (!didOutputStyle) {
0940 didOutputStyle = true;
0941 indentSmart();
0942 write(name);
0943 write(" {");
0944 } else {
0945 write(";");
0946 }
0947 write(' ');
0948 write(attribute.toString());
0949 write(": ");
0950 write(value);
0951 }
0952 }
0953 }
0954 }
0955 if (didOutputStyle) {
0956 write(" }");
0957 writeLineSeparator();
0958 }
0959 return didOutputStyle;
0960 }
0961
0962 void writeStyleStartTag() throws IOException {
0963 indentSmart();
0964 write("<style type=\"text/css\">");
0965 incrIndent();
0966 writeLineSeparator();
0967 indentSmart();
0968 write("<!--");
0969 incrIndent();
0970 writeLineSeparator();
0971 }
0972
0973 void writeStyleEndTag() throws IOException {
0974 decrIndent();
0975 indentSmart();
0976 write("-->");
0977 writeLineSeparator();
0978 decrIndent();
0979 indentSmart();
0980 write("</style>");
0981 writeLineSeparator();
0982 indentSmart();
0983 }
0984
0985 // --- conversion support ---------------------------
0986
0987 /**
0988 * Convert the give set of attributes to be html for
0989 * the purpose of writing them out. Any keys that
0990 * have been converted will not appear in the resultant
0991 * set. Any keys not converted will appear in the
0992 * resultant set the same as the received set.<p>
0993 * This will put the converted values into <code>to</code>, unless
0994 * it is null in which case a temporary AttributeSet will be returned.
0995 */
0996 AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
0997 if (to == null) {
0998 to = convAttr;
0999 }
1000 to.removeAttributes(to);
1001 if (writeCSS) {
1002 convertToHTML40(from, to);
1003 } else {
1004 convertToHTML32(from, to);
1005 }
1006 return to;
1007 }
1008
1009 /**
1010 * If true, the writer will emit CSS attributes in preference
1011 * to HTML tags/attributes (i.e. It will emit an HTML 4.0
1012 * style).
1013 */
1014 private boolean writeCSS = false;
1015
1016 /**
1017 * Buffer for the purpose of attribute conversion
1018 */
1019 private MutableAttributeSet convAttr = new SimpleAttributeSet();
1020
1021 /**
1022 * Buffer for the purpose of attribute conversion. This can be
1023 * used if convAttr is being used.
1024 */
1025 private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
1026
1027 /**
1028 * Create an older style of HTML attributes. This will
1029 * convert character level attributes that have a StyleConstants
1030 * mapping over to an HTML tag/attribute. Other CSS attributes
1031 * will be placed in an HTML style attribute.
1032 */
1033 private static void convertToHTML32(AttributeSet from,
1034 MutableAttributeSet to) {
1035 if (from == null) {
1036 return;
1037 }
1038 Enumeration keys = from.getAttributeNames();
1039 String value = "";
1040 while (keys.hasMoreElements()) {
1041 Object key = keys.nextElement();
1042 if (key instanceof CSS.Attribute) {
1043 if ((key == CSS.Attribute.FONT_FAMILY)
1044 || (key == CSS.Attribute.FONT_SIZE)
1045 || (key == CSS.Attribute.COLOR)) {
1046
1047 createFontAttribute((CSS.Attribute) key, from, to);
1048 } else if (key == CSS.Attribute.FONT_WEIGHT) {
1049 // add a bold tag is weight is bold
1050 CSS.FontWeight weightValue = (CSS.FontWeight) from
1051 .getAttribute(CSS.Attribute.FONT_WEIGHT);
1052 if ((weightValue != null)
1053 && (weightValue.getValue() > 400)) {
1054 addAttribute(to, HTML.Tag.B,
1055 SimpleAttributeSet.EMPTY);
1056 }
1057 } else if (key == CSS.Attribute.FONT_STYLE) {
1058 String s = from.getAttribute(key).toString();
1059 if (s.indexOf("italic") >= 0) {
1060 addAttribute(to, HTML.Tag.I,
1061 SimpleAttributeSet.EMPTY);
1062 }
1063 } else if (key == CSS.Attribute.TEXT_DECORATION) {
1064 String decor = from.getAttribute(key).toString();
1065 if (decor.indexOf("underline") >= 0) {
1066 addAttribute(to, HTML.Tag.U,
1067 SimpleAttributeSet.EMPTY);
1068 }
1069 if (decor.indexOf("line-through") >= 0) {
1070 addAttribute(to, HTML.Tag.STRIKE,
1071 SimpleAttributeSet.EMPTY);
1072 }
1073 } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
1074 String vAlign = from.getAttribute(key).toString();
1075 if (vAlign.indexOf("sup") >= 0) {
1076 addAttribute(to, HTML.Tag.SUP,
1077 SimpleAttributeSet.EMPTY);
1078 }
1079 if (vAlign.indexOf("sub") >= 0) {
1080 addAttribute(to, HTML.Tag.SUB,
1081 SimpleAttributeSet.EMPTY);
1082 }
1083 } else if (key == CSS.Attribute.TEXT_ALIGN) {
1084 addAttribute(to, HTML.Attribute.ALIGN, from
1085 .getAttribute(key).toString());
1086 } else {
1087 // default is to store in a HTML style attribute
1088 if (value.length() > 0) {
1089 value = value + "; ";
1090 }
1091 value = value + key + ": " + from.getAttribute(key);
1092 }
1093 } else {
1094 Object attr = from.getAttribute(key);
1095 if (attr instanceof AttributeSet) {
1096 attr = ((AttributeSet) attr).copyAttributes();
1097 }
1098 addAttribute(to, key, attr);
1099 }
1100 }
1101 if (value.length() > 0) {
1102 to.addAttribute(HTML.Attribute.STYLE, value);
1103 }
1104 }
1105
1106 /**
1107 * Add an attribute only if it doesn't exist so that we don't
1108 * loose information replacing it with SimpleAttributeSet.EMPTY
1109 */
1110 private static void addAttribute(MutableAttributeSet to,
1111 Object key, Object value) {
1112 Object attr = to.getAttribute(key);
1113 if (attr == null || attr == SimpleAttributeSet.EMPTY) {
1114 to.addAttribute(key, value);
1115 } else {
1116 if (attr instanceof MutableAttributeSet
1117 && value instanceof AttributeSet) {
1118 ((MutableAttributeSet) attr)
1119 .addAttributes((AttributeSet) value);
1120 }
1121 }
1122 }
1123
1124 /**
1125 * Create/update an HTML <font> tag attribute. The
1126 * value of the attribute should be a MutableAttributeSet so
1127 * that the attributes can be updated as they are discovered.
1128 */
1129 private static void createFontAttribute(CSS.Attribute a,
1130 AttributeSet from, MutableAttributeSet to) {
1131 MutableAttributeSet fontAttr = (MutableAttributeSet) to
1132 .getAttribute(HTML.Tag.FONT);
1133 if (fontAttr == null) {
1134 fontAttr = new SimpleAttributeSet();
1135 to.addAttribute(HTML.Tag.FONT, fontAttr);
1136 }
1137 // edit the parameters to the font tag
1138 String htmlValue = from.getAttribute(a).toString();
1139 if (a == CSS.Attribute.FONT_FAMILY) {
1140 fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
1141 } else if (a == CSS.Attribute.FONT_SIZE) {
1142 fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
1143 } else if (a == CSS.Attribute.COLOR) {
1144 fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
1145 }
1146 }
1147
1148 /**
1149 * Copies the given AttributeSet to a new set, converting
1150 * any CSS attributes found to arguments of an HTML style
1151 * attribute.
1152 */
1153 private static void convertToHTML40(AttributeSet from,
1154 MutableAttributeSet to) {
1155 Enumeration keys = from.getAttributeNames();
1156 String value = "";
1157 while (keys.hasMoreElements()) {
1158 Object key = keys.nextElement();
1159 if (key instanceof CSS.Attribute) {
1160 value = value + " " + key + "="
1161 + from.getAttribute(key) + ";";
1162 } else {
1163 to.addAttribute(key, from.getAttribute(key));
1164 }
1165 }
1166 if (value.length() > 0) {
1167 to.addAttribute(HTML.Attribute.STYLE, value);
1168 }
1169 }
1170
1171 //
1172 // Overrides the writing methods to only break a string when
1173 // canBreakString is true.
1174 // In a future release it is likely AbstractWriter will get this
1175 // functionality.
1176 //
1177
1178 /**
1179 * Writes the line separator. This is overriden to make sure we don't
1180 * replace the newline content in case it is outside normal ascii.
1181 * @since 1.3
1182 */
1183 protected void writeLineSeparator() throws IOException {
1184 boolean oldReplace = replaceEntities;
1185 replaceEntities = false;
1186 super .writeLineSeparator();
1187 replaceEntities = oldReplace;
1188 indented = false;
1189 }
1190
1191 /**
1192 * This method is overriden to map any character entities, such as
1193 * < to &lt;. <code>super.output</code> will be invoked to
1194 * write the content.
1195 * @since 1.3
1196 */
1197 protected void output(char[] chars, int start, int length)
1198 throws IOException {
1199 if (!replaceEntities) {
1200 super .output(chars, start, length);
1201 return;
1202 }
1203 int last = start;
1204 length += start;
1205 for (int counter = start; counter < length; counter++) {
1206 // This will change, we need better support character level
1207 // entities.
1208 switch (chars[counter]) {
1209 // Character level entities.
1210 case '<':
1211 if (counter > last) {
1212 super .output(chars, last, counter - last);
1213 }
1214 last = counter + 1;
1215 output("<");
1216 break;
1217 case '>':
1218 if (counter > last) {
1219 super .output(chars, last, counter - last);
1220 }
1221 last = counter + 1;
1222 output(">");
1223 break;
1224 case '&':
1225 if (counter > last) {
1226 super .output(chars, last, counter - last);
1227 }
1228 last = counter + 1;
1229 output("&");
1230 break;
1231 case '"':
1232 if (counter > last) {
1233 super .output(chars, last, counter - last);
1234 }
1235 last = counter + 1;
1236 output(""");
1237 break;
1238 // Special characters
1239 case '\n':
1240 case '\t':
1241 case '\r':
1242 break;
1243 default:
1244 if (chars[counter] < ' ' || chars[counter] > 127) {
1245 if (counter > last) {
1246 super .output(chars, last, counter - last);
1247 }
1248 last = counter + 1;
1249 // If the character is outside of ascii, write the
1250 // numeric value.
1251 output("&#");
1252 output(String.valueOf((int) chars[counter]));
1253 output(";");
1254 }
1255 break;
1256 }
1257 }
1258 if (last < length) {
1259 super .output(chars, last, length - last);
1260 }
1261 }
1262
1263 /**
1264 * This directly invokes super's <code>output</code> after converting
1265 * <code>string</code> to a char[].
1266 */
1267 private void output(String string) throws IOException {
1268 int length = string.length();
1269 if (tempChars == null || tempChars.length < length) {
1270 tempChars = new char[length];
1271 }
1272 string.getChars(0, length, tempChars, 0);
1273 super .output(tempChars, 0, length);
1274 }
1275
1276 private boolean indented = false;
1277
1278 /**
1279 * Writes indent only once per line.
1280 */
1281 private void indentSmart() throws IOException {
1282 if (!indented) {
1283 indent();
1284 indented = true;
1285 }
1286 }
1287 }
|