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