001 /*
002 * Copyright 1998-1999 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing.text.html;
027
028 import java.io.Writer;
029 import java.io.IOException;
030 import java.util.*;
031 import java.awt.Color;
032 import javax.swing.text.*;
033
034 /**
035 * MinimalHTMLWriter is a fallback writer used by the
036 * HTMLEditorKit to write out HTML for a document that
037 * is a not produced by the EditorKit.
038 *
039 * The format for the document is:
040 * <pre>
041 * <html>
042 * <head>
043 * <style>
044 * <!-- list of named styles
045 * p.normal {
046 * font-family: SansSerif;
047 * margin-height: 0;
048 * font-size: 14
049 * }
050 * -->
051 * </style>
052 * </head>
053 * <body>
054 * <p style=normal>
055 * <b>Bold, italic, and underline attributes
056 * of the run are emitted as HTML tags.
057 * The remaining attributes are emitted as
058 * part of the style attribute of a <span> tag.
059 * The syntax is similar to inline styles.</b>
060 * </p>
061 * </body>
062 * </html>
063 * </pre>
064 *
065 * @author Sunita Mani
066 * @version 1.21, 05/05/07
067 */
068
069 public class MinimalHTMLWriter extends AbstractWriter {
070
071 /**
072 * These static finals are used to
073 * tweak and query the fontMask about which
074 * of these tags need to be generated or
075 * terminated.
076 */
077 private static final int BOLD = 0x01;
078 private static final int ITALIC = 0x02;
079 private static final int UNDERLINE = 0x04;
080
081 // Used to map StyleConstants to CSS.
082 private static final CSS css = new CSS();
083
084 private int fontMask = 0;
085
086 int startOffset = 0;
087 int endOffset = 0;
088
089 /**
090 * Stores the attributes of the previous run.
091 * Used to compare with the current run's
092 * attributeset. If identical, then a
093 * <span> tag is not emitted.
094 */
095 private AttributeSet fontAttributes;
096
097 /**
098 * Maps from style name as held by the Document, to the archived
099 * style name (style name written out). These may differ.
100 */
101 private Hashtable styleNameMapping;
102
103 /**
104 * Creates a new MinimalHTMLWriter.
105 *
106 * @param w Writer
107 * @param doc StyledDocument
108 *
109 */
110 public MinimalHTMLWriter(Writer w, StyledDocument doc) {
111 super (w, doc);
112 }
113
114 /**
115 * Creates a new MinimalHTMLWriter.
116 *
117 * @param w Writer
118 * @param doc StyledDocument
119 * @param pos The location in the document to fetch the
120 * content.
121 * @param len The amount to write out.
122 *
123 */
124 public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos,
125 int len) {
126 super (w, doc, pos, len);
127 }
128
129 /**
130 * Generates HTML output
131 * from a StyledDocument.
132 *
133 * @exception IOException on any I/O error
134 * @exception BadLocationException if pos represents an invalid
135 * location within the document.
136 *
137 */
138 public void write() throws IOException, BadLocationException {
139 styleNameMapping = new Hashtable();
140 writeStartTag("<html>");
141 writeHeader();
142 writeBody();
143 writeEndTag("</html>");
144 }
145
146 /**
147 * Writes out all the attributes for the
148 * following types:
149 * StyleConstants.ParagraphConstants,
150 * StyleConstants.CharacterConstants,
151 * StyleConstants.FontConstants,
152 * StyleConstants.ColorConstants.
153 * The attribute name and value are separated by a colon.
154 * Each pair is separated by a semicolon.
155 *
156 * @exception IOException on any I/O error
157 */
158 protected void writeAttributes(AttributeSet attr)
159 throws IOException {
160 Enumeration attributeNames = attr.getAttributeNames();
161 while (attributeNames.hasMoreElements()) {
162 Object name = attributeNames.nextElement();
163 if ((name instanceof StyleConstants.ParagraphConstants)
164 || (name instanceof StyleConstants.CharacterConstants)
165 || (name instanceof StyleConstants.FontConstants)
166 || (name instanceof StyleConstants.ColorConstants)) {
167 indent();
168 write(name.toString());
169 write(':');
170 write(css.styleConstantsValueToCSSValue(
171 (StyleConstants) name, attr.getAttribute(name))
172 .toString());
173 write(';');
174 write(NEWLINE);
175 }
176 }
177 }
178
179 /**
180 * Writes out text.
181 *
182 * @exception IOException on any I/O error
183 */
184 protected void text(Element elem) throws IOException,
185 BadLocationException {
186 String contentStr = getText(elem);
187 if ((contentStr.length() > 0)
188 && (contentStr.charAt(contentStr.length() - 1) == NEWLINE)) {
189 contentStr = contentStr.substring(0,
190 contentStr.length() - 1);
191 }
192 if (contentStr.length() > 0) {
193 write(contentStr);
194 }
195 }
196
197 /**
198 * Writes out a start tag appropriately
199 * indented. Also increments the indent level.
200 *
201 * @exception IOException on any I/O error
202 */
203 protected void writeStartTag(String tag) throws IOException {
204 indent();
205 write(tag);
206 write(NEWLINE);
207 incrIndent();
208 }
209
210 /**
211 * Writes out an end tag appropriately
212 * indented. Also decrements the indent level.
213 *
214 * @exception IOException on any I/O error
215 */
216 protected void writeEndTag(String endTag) throws IOException {
217 decrIndent();
218 indent();
219 write(endTag);
220 write(NEWLINE);
221 }
222
223 /**
224 * Writes out the <head> and <style>
225 * tags, and then invokes writeStyles() to write
226 * out all the named styles as the content of the
227 * <style> tag. The content is surrounded by
228 * valid HTML comment markers to ensure that the
229 * document is viewable in applications/browsers
230 * that do not support the tag.
231 *
232 * @exception IOException on any I/O error
233 */
234 protected void writeHeader() throws IOException {
235 writeStartTag("<head>");
236 writeStartTag("<style>");
237 writeStartTag("<!--");
238 writeStyles();
239 writeEndTag("-->");
240 writeEndTag("</style>");
241 writeEndTag("</head>");
242 }
243
244 /**
245 * Writes out all the named styles as the
246 * content of the <style> tag.
247 *
248 * @exception IOException on any I/O error
249 */
250 protected void writeStyles() throws IOException {
251 /*
252 * Access to DefaultStyledDocument done to workaround
253 * a missing API in styled document to access the
254 * stylenames.
255 */
256 DefaultStyledDocument styledDoc = ((DefaultStyledDocument) getDocument());
257 Enumeration styleNames = styledDoc.getStyleNames();
258
259 while (styleNames.hasMoreElements()) {
260 Style s = styledDoc.getStyle((String) styleNames
261 .nextElement());
262
263 /** PENDING: Once the name attribute is removed
264 from the list we check check for 0. **/
265 if (s.getAttributeCount() == 1
266 && s.isDefined(StyleConstants.NameAttribute)) {
267 continue;
268 }
269 indent();
270 write("p." + addStyleName(s.getName()));
271 write(" {\n");
272 incrIndent();
273 writeAttributes(s);
274 decrIndent();
275 indent();
276 write("}\n");
277 }
278 }
279
280 /**
281 * Iterates over the elements in the document
282 * and processes elements based on whether they are
283 * branch elements or leaf elements. This method specially handles
284 * leaf elements that are text.
285 *
286 * @exception IOException on any I/O error
287 */
288 protected void writeBody() throws IOException, BadLocationException {
289 ElementIterator it = getElementIterator();
290
291 /*
292 This will be a section element for a styled document.
293 We represent this element in HTML as the body tags.
294 Therefore we ignore it.
295 */
296 it.current();
297
298 Element next = null;
299
300 writeStartTag("<body>");
301
302 boolean inContent = false;
303
304 while ((next = it.next()) != null) {
305 if (!inRange(next)) {
306 continue;
307 }
308 if (next instanceof AbstractDocument.BranchElement) {
309 if (inContent) {
310 writeEndParagraph();
311 inContent = false;
312 fontMask = 0;
313 }
314 writeStartParagraph(next);
315 } else if (isText(next)) {
316 writeContent(next, !inContent);
317 inContent = true;
318 } else {
319 writeLeaf(next);
320 inContent = true;
321 }
322 }
323 if (inContent) {
324 writeEndParagraph();
325 }
326 writeEndTag("</body>");
327 }
328
329 /**
330 * Emits an end tag for a <p>
331 * tag. Before writing out the tag, this method ensures
332 * that all other tags that have been opened are
333 * appropriately closed off.
334 *
335 * @exception IOException on any I/O error
336 */
337 protected void writeEndParagraph() throws IOException {
338 writeEndMask(fontMask);
339 if (inFontTag()) {
340 endSpanTag();
341 } else {
342 write(NEWLINE);
343 }
344 writeEndTag("</p>");
345 }
346
347 /**
348 * Emits the start tag for a paragraph. If
349 * the paragraph has a named style associated with it,
350 * then this method also generates a class attribute for the
351 * <p> tag and sets its value to be the name of the
352 * style.
353 *
354 * @exception IOException on any I/O error
355 */
356 protected void writeStartParagraph(Element elem) throws IOException {
357 AttributeSet attr = elem.getAttributes();
358 Object resolveAttr = attr
359 .getAttribute(StyleConstants.ResolveAttribute);
360 if (resolveAttr instanceof StyleContext.NamedStyle) {
361 writeStartTag("<p class="
362 + mapStyleName(((StyleContext.NamedStyle) resolveAttr)
363 .getName()) + ">");
364 } else {
365 writeStartTag("<p>");
366 }
367 }
368
369 /**
370 * Responsible for writing out other non-text leaf
371 * elements.
372 *
373 * @exception IOException on any I/O error
374 */
375 protected void writeLeaf(Element elem) throws IOException {
376 indent();
377 if (elem.getName() == StyleConstants.IconElementName) {
378 writeImage(elem);
379 } else if (elem.getName() == StyleConstants.ComponentElementName) {
380 writeComponent(elem);
381 }
382 }
383
384 /**
385 * Responsible for handling Icon Elements;
386 * deliberately unimplemented. How to implement this method is
387 * an issue of policy. For example, if you're generating
388 * an <img> tag, how should you
389 * represent the src attribute (the location of the image)?
390 * In certain cases it could be a URL, in others it could
391 * be read from a stream.
392 *
393 * @param elem element of type StyleConstants.IconElementName
394 */
395 protected void writeImage(Element elem) throws IOException {
396 }
397
398 /**
399 * Responsible for handling Component Elements;
400 * deliberately unimplemented.
401 * How this method is implemented is a matter of policy.
402 */
403 protected void writeComponent(Element elem) throws IOException {
404 }
405
406 /**
407 * Returns true if the element is a text element.
408 *
409 */
410 protected boolean isText(Element elem) {
411 return (elem.getName() == AbstractDocument.ContentElementName);
412 }
413
414 /**
415 * Writes out the attribute set
416 * in an HTML-compliant manner.
417 *
418 * @exception IOException on any I/O error
419 * @exception BadLocationException if pos represents an invalid
420 * location within the document.
421 */
422 protected void writeContent(Element elem, boolean needsIndenting)
423 throws IOException, BadLocationException {
424
425 AttributeSet attr = elem.getAttributes();
426 writeNonHTMLAttributes(attr);
427 if (needsIndenting) {
428 indent();
429 }
430 writeHTMLTags(attr);
431 text(elem);
432 }
433
434 /**
435 * Generates
436 * bold <b>, italic <i>, and <u> tags for the
437 * text based on its attribute settings.
438 *
439 * @exception IOException on any I/O error
440 */
441
442 protected void writeHTMLTags(AttributeSet attr) throws IOException {
443
444 int oldMask = fontMask;
445 setFontMask(attr);
446
447 int endMask = 0;
448 int startMask = 0;
449 if ((oldMask & BOLD) != 0) {
450 if ((fontMask & BOLD) == 0) {
451 endMask |= BOLD;
452 }
453 } else if ((fontMask & BOLD) != 0) {
454 startMask |= BOLD;
455 }
456
457 if ((oldMask & ITALIC) != 0) {
458 if ((fontMask & ITALIC) == 0) {
459 endMask |= ITALIC;
460 }
461 } else if ((fontMask & ITALIC) != 0) {
462 startMask |= ITALIC;
463 }
464
465 if ((oldMask & UNDERLINE) != 0) {
466 if ((fontMask & UNDERLINE) == 0) {
467 endMask |= UNDERLINE;
468 }
469 } else if ((fontMask & UNDERLINE) != 0) {
470 startMask |= UNDERLINE;
471 }
472 writeEndMask(endMask);
473 writeStartMask(startMask);
474 }
475
476 /**
477 * Tweaks the appropriate bits of fontMask
478 * to reflect whether the text is to be displayed in
479 * bold, italic, and/or with an underline.
480 *
481 */
482 private void setFontMask(AttributeSet attr) {
483 if (StyleConstants.isBold(attr)) {
484 fontMask |= BOLD;
485 }
486
487 if (StyleConstants.isItalic(attr)) {
488 fontMask |= ITALIC;
489 }
490
491 if (StyleConstants.isUnderline(attr)) {
492 fontMask |= UNDERLINE;
493 }
494 }
495
496 /**
497 * Writes out start tags <u>, <i>, and <b> based on
498 * the mask settings.
499 *
500 * @exception IOException on any I/O error
501 */
502 private void writeStartMask(int mask) throws IOException {
503 if (mask != 0) {
504 if ((mask & UNDERLINE) != 0) {
505 write("<u>");
506 }
507 if ((mask & ITALIC) != 0) {
508 write("<i>");
509 }
510 if ((mask & BOLD) != 0) {
511 write("<b>");
512 }
513 }
514 }
515
516 /**
517 * Writes out end tags for <u>, <i>, and <b> based on
518 * the mask settings.
519 *
520 * @exception IOException on any I/O error
521 */
522 private void writeEndMask(int mask) throws IOException {
523 if (mask != 0) {
524 if ((mask & BOLD) != 0) {
525 write("</b>");
526 }
527 if ((mask & ITALIC) != 0) {
528 write("</i>");
529 }
530 if ((mask & UNDERLINE) != 0) {
531 write("</u>");
532 }
533 }
534 }
535
536 /**
537 * Writes out the remaining
538 * character-level attributes (attributes other than bold,
539 * italic, and underline) in an HTML-compliant way. Given that
540 * attributes such as font family and font size have no direct
541 * mapping to HTML tags, a <span> tag is generated and its
542 * style attribute is set to contain the list of remaining
543 * attributes just like inline styles.
544 *
545 * @exception IOException on any I/O error
546 */
547 protected void writeNonHTMLAttributes(AttributeSet attr)
548 throws IOException {
549
550 String style = "";
551 String separator = "; ";
552
553 if (inFontTag() && fontAttributes.isEqual(attr)) {
554 return;
555 }
556
557 boolean first = true;
558 Color color = (Color) attr
559 .getAttribute(StyleConstants.Foreground);
560 if (color != null) {
561 style += "color: "
562 + css.styleConstantsValueToCSSValue(
563 (StyleConstants) StyleConstants.Foreground,
564 color);
565 first = false;
566 }
567 Integer size = (Integer) attr
568 .getAttribute(StyleConstants.FontSize);
569 if (size != null) {
570 if (!first) {
571 style += separator;
572 }
573 style += "font-size: " + size.intValue() + "pt";
574 first = false;
575 }
576
577 String family = (String) attr
578 .getAttribute(StyleConstants.FontFamily);
579 if (family != null) {
580 if (!first) {
581 style += separator;
582 }
583 style += "font-family: " + family;
584 first = false;
585 }
586
587 if (style.length() > 0) {
588 if (fontMask != 0) {
589 writeEndMask(fontMask);
590 fontMask = 0;
591 }
592 startSpanTag(style);
593 fontAttributes = attr;
594 } else if (fontAttributes != null) {
595 writeEndMask(fontMask);
596 fontMask = 0;
597 endSpanTag();
598 }
599 }
600
601 /**
602 * Returns true if we are currently in a <font> tag.
603 */
604 protected boolean inFontTag() {
605 return (fontAttributes != null);
606 }
607
608 /**
609 * This is no longer used, instead <span> will be written out.
610 * <p>
611 * Writes out an end tag for the <font> tag.
612 *
613 * @exception IOException on any I/O error
614 */
615 protected void endFontTag() throws IOException {
616 write(NEWLINE);
617 writeEndTag("</font>");
618 fontAttributes = null;
619 }
620
621 /**
622 * This is no longer used, instead <span> will be written out.
623 * <p>
624 * Writes out a start tag for the <font> tag.
625 * Because font tags cannot be nested,
626 * this method closes out
627 * any enclosing font tag before writing out a
628 * new start tag.
629 *
630 * @exception IOException on any I/O error
631 */
632 protected void startFontTag(String style) throws IOException {
633 boolean callIndent = false;
634 if (inFontTag()) {
635 endFontTag();
636 callIndent = true;
637 }
638 writeStartTag("<font style=\"" + style + "\">");
639 if (callIndent) {
640 indent();
641 }
642 }
643
644 /**
645 * Writes out a start tag for the <font> tag.
646 * Because font tags cannot be nested,
647 * this method closes out
648 * any enclosing font tag before writing out a
649 * new start tag.
650 *
651 * @exception IOException on any I/O error
652 */
653 private void startSpanTag(String style) throws IOException {
654 boolean callIndent = false;
655 if (inFontTag()) {
656 endSpanTag();
657 callIndent = true;
658 }
659 writeStartTag("<span style=\"" + style + "\">");
660 if (callIndent) {
661 indent();
662 }
663 }
664
665 /**
666 * Writes out an end tag for the <span> tag.
667 *
668 * @exception IOException on any I/O error
669 */
670 private void endSpanTag() throws IOException {
671 write(NEWLINE);
672 writeEndTag("</span>");
673 fontAttributes = null;
674 }
675
676 /**
677 * Adds the style named <code>style</code> to the style mapping. This
678 * returns the name that should be used when outputting. CSS does not
679 * allow the full Unicode set to be used as a style name.
680 */
681 private String addStyleName(String style) {
682 if (styleNameMapping == null) {
683 return style;
684 }
685 StringBuffer sb = null;
686 for (int counter = style.length() - 1; counter >= 0; counter--) {
687 if (!isValidCharacter(style.charAt(counter))) {
688 if (sb == null) {
689 sb = new StringBuffer(style);
690 }
691 sb.setCharAt(counter, 'a');
692 }
693 }
694 String mappedName = (sb != null) ? sb.toString() : style;
695 while (styleNameMapping.get(mappedName) != null) {
696 mappedName = mappedName + 'x';
697 }
698 styleNameMapping.put(style, mappedName);
699 return mappedName;
700 }
701
702 /**
703 * Returns the mapped style name corresponding to <code>style</code>.
704 */
705 private String mapStyleName(String style) {
706 if (styleNameMapping == null) {
707 return style;
708 }
709 String retValue = (String) styleNameMapping.get(style);
710 return (retValue == null) ? style : retValue;
711 }
712
713 private boolean isValidCharacter(char character) {
714 return ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z'));
715 }
716 }
|