001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Vadim L. Bogdanov
020: * @version $Revision$
021: */package javax.swing.text.html;
022:
023: import java.io.DataInputStream;
024: import java.io.IOException;
025: import java.io.Writer;
026: import java.net.URL;
027: import java.util.Enumeration;
028: import java.util.Iterator;
029: import java.util.Stack;
030: import java.util.Vector;
031:
032: import javax.swing.text.AbstractWriter;
033: import javax.swing.text.AttributeSet;
034: import javax.swing.text.BadLocationException;
035: import javax.swing.text.Document;
036: import javax.swing.text.Element;
037: import javax.swing.text.ElementIterator;
038: import javax.swing.text.Style;
039: import javax.swing.text.StyleConstants;
040: import javax.swing.text.html.parser.DTD;
041: import javax.swing.text.html.parser.Entity;
042:
043: import org.apache.harmony.x.swing.text.html.form.FormSelectModel;
044: import org.apache.harmony.x.swing.text.html.form.FormTextModel;
045: import org.apache.harmony.x.swing.text.html.form.FormOptionGroup;
046:
047: public class HTMLWriter extends AbstractWriter {
048: private static final int DEFAULT_LINE_LENGTH = 80;
049: private static DTD dtd;
050:
051: private boolean writingContent;
052: private boolean indentEmptyTag = true;
053: private boolean preformatted;
054: private boolean toWriteHead = true;
055: private Stack elemsStack;
056: private Vector openEmbeddedTags = new Vector();
057: private boolean isOptionGroupOpen;
058:
059: public HTMLWriter(final Writer w, final HTMLDocument doc) {
060: super (w, doc);
061: setLineLength(DEFAULT_LINE_LENGTH);
062: }
063:
064: public HTMLWriter(final Writer w, final HTMLDocument doc,
065: final int pos, final int len) {
066: super (w, doc, pos, len);
067: setLineLength(DEFAULT_LINE_LENGTH);
068: }
069:
070: public void write() throws IOException, BadLocationException {
071: if (elemsStack == null) {
072: elemsStack = new Stack();
073: }
074: ElementIterator it = getElementIterator();
075:
076: Element e;
077: while ((e = it.next()) != null) {
078: if (!elemsStack.isEmpty()) {
079: while (!elemsStack.isEmpty()
080: && elemsStack.peek() != e.getParentElement()) {
081: if (!synthesizedElement((Element) elemsStack.peek())) {
082: decrIndent();
083: }
084: if (!preformatted && getCurrentLineLength() != 0) {
085: writeLineSeparator();
086: }
087: endTag((Element) elemsStack.pop());
088: }
089: }
090:
091: if (isEmptyTag(getHTMLTag(e.getAttributes()))) {
092: emptyTag(e);
093: } else if (matchNameAttribute(e.getAttributes(),
094: HTML.Tag.TITLE)) {
095: continue;
096: } else {
097: if (getCurrentLineLength() != 0 && getCanWrapLines()) {
098: writeLineSeparator();
099: }
100: startTag(e);
101: elemsStack.push(e);
102: if (!synthesizedElement(e)) {
103: incrIndent();
104: }
105: indentEmptyTag = !preformatted;
106: if (matchNameAttribute(e.getAttributes(), HTML.Tag.HEAD)) {
107: writeDocumentProperties();
108: }
109: }
110: }
111:
112: while (!elemsStack.isEmpty()) {
113: if (!synthesizedElement((Element) elemsStack.peek())) {
114: decrIndent();
115: }
116: if (!preformatted && getCurrentLineLength() != 0) {
117: writeLineSeparator();
118: }
119: endTag((Element) elemsStack.pop());
120: }
121:
122: writeAdditionalComment();
123: }
124:
125: protected void comment(final Element elem)
126: throws BadLocationException, IOException {
127:
128: if (!matchNameAttribute(elem.getAttributes(), HTML.Tag.COMMENT)) {
129: return;
130: }
131:
132: Object comment = elem.getAttributes().getAttribute(
133: HTML.Attribute.COMMENT);
134: write("<!--");
135: if (comment != null) {
136: write(comment.toString());
137: }
138: write("-->");
139: writeLineSeparator();
140: }
141:
142: protected void emptyTag(final Element elem)
143: throws BadLocationException, IOException {
144:
145: if (indentEmptyTag) {
146: indent();
147: indentEmptyTag = false;
148: }
149: closeOutUnwantedEmbeddedTags(elem.getAttributes());
150: writeEmbeddedTags(elem.getAttributes());
151: if (matchNameAttribute(elem.getAttributes(), HTML.Tag.CONTENT)) {
152: text(elem);
153: } else if (matchNameAttribute(elem.getAttributes(),
154: HTML.Tag.COMMENT)) {
155: comment(elem);
156: } else {
157: write("<" + getHTMLTag(elem.getAttributes()).toString());
158: writeAttributes(elem.getAttributes());
159: write('>');
160: if (!elem.isLeaf()) {
161: writeLineSeparator();
162: }
163: }
164: }
165:
166: protected boolean isBlockTag(final AttributeSet attr) {
167: HTML.Tag tag = getHTMLTag(attr);
168: return tag == null ? false : tag.isBlock();
169: }
170:
171: protected boolean matchNameAttribute(final AttributeSet attr,
172: final HTML.Tag tag) {
173: return tag == null ? false : tag.equals(getHTMLTag(attr));
174: }
175:
176: protected boolean synthesizedElement(final Element elem) {
177: return matchNameAttribute(elem.getAttributes(),
178: HTML.Tag.IMPLIED);
179: }
180:
181: protected void output(final char[] chars, final int start,
182: final int length) throws IOException {
183:
184: if (!writingContent) {
185: super .output(chars, start, length);
186: return;
187: }
188:
189: StringBuffer buffer = new StringBuffer();
190: int writtenLength = 0;
191: for (int i = 0; i < length; i++) {
192: String entity = getEntity(chars[start + i]);
193: if (entity != null) {
194: buffer.append(chars, start + writtenLength, i
195: - writtenLength);
196: buffer.append(entity);
197: writtenLength = i + 1;
198: }
199: }
200: if (writtenLength == 0) {
201: super .output(chars, start, length);
202: } else {
203: buffer.append(chars, start + writtenLength, length
204: - writtenLength);
205: char[] content = buffer.toString().toCharArray();
206: super .output(content, 0, content.length);
207: }
208: }
209:
210: protected void startTag(final Element elem) throws IOException,
211: BadLocationException {
212:
213: if (synthesizedElement(elem)) {
214: return;
215: }
216:
217: closeOutUnwantedEmbeddedTags(elem.getAttributes());
218: indent();
219:
220: if (matchNameAttribute(elem.getAttributes(), HTML.Tag.HEAD)) {
221: toWriteHead = false;
222: } else if (toWriteHead
223: && matchNameAttribute(elem.getAttributes(),
224: HTML.Tag.BODY)) {
225: writeSynthesizedHead();
226: }
227:
228: HTML.Tag tag = getHTMLTag(elem.getAttributes());
229: writeTag(tag, elem.getAttributes());
230:
231: preformatted = tag.isPreformatted();
232: if (!preformatted) {
233: writeLineSeparator();
234: }
235:
236: if (matchNameAttribute(elem.getAttributes(), HTML.Tag.TEXTAREA)) {
237: textAreaContent(elem.getAttributes());
238: } else if (matchNameAttribute(elem.getAttributes(),
239: HTML.Tag.SELECT)) {
240: selectContent(elem.getAttributes());
241: }
242: }
243:
244: protected void endTag(final Element elem) throws IOException {
245: if (synthesizedElement(elem)) {
246: return;
247: }
248:
249: closeOutUnwantedEmbeddedTags(elem.getAttributes());
250: if (!preformatted) {
251: indent();
252: }
253: HTML.Tag tag = getHTMLTag(elem.getAttributes());
254: write("</" + tag + ">");
255: writeLineSeparator();
256: }
257:
258: protected void text(final Element elem)
259: throws BadLocationException, IOException {
260:
261: writingContent = true;
262: super .text(elem);
263: writingContent = false;
264: }
265:
266: protected void selectContent(final AttributeSet attr)
267: throws IOException {
268: FormSelectModel model = (FormSelectModel) attr
269: .getAttribute(StyleConstants.ModelAttribute);
270: incrIndent();
271: for (int i = 0; i < model.getOptionCount(); i++) {
272: if (model.getOption(i) instanceof FormOptionGroup) {
273: startOptionGroup(model.getOption(i));
274: } else {
275: writeOption(model.getOption(i));
276: }
277: }
278: endOptionGroup();
279: decrIndent();
280: }
281:
282: protected void textAreaContent(final AttributeSet attr)
283: throws BadLocationException, IOException {
284:
285: FormTextModel model = (FormTextModel) attr
286: .getAttribute(StyleConstants.ModelAttribute);
287: incrIndent();
288: writingContent = true;
289: indent();
290: write(model.getInitialContent());
291: writingContent = false;
292: decrIndent();
293: writeLineSeparator();
294: }
295:
296: protected void writeAttributes(final AttributeSet attr)
297: throws IOException {
298: writeCSSAttributes(attr, " style=\"", "\"");
299:
300: for (Enumeration attrs = attr.getAttributeNames(); attrs
301: .hasMoreElements();) {
302:
303: Object a = attrs.nextElement();
304: if (a instanceof HTML.Tag || a instanceof StyleConstants
305: || a instanceof CSS.Attribute
306: || HTML.Attribute.ENDTAG.equals(a)) {
307: continue;
308: }
309: write(" " + a + "=\"" + attr.getAttribute(a) + "\"");
310: }
311: }
312:
313: protected void writeEmbeddedTags(final AttributeSet attr)
314: throws IOException {
315: for (Enumeration attrs = attr.getAttributeNames(); attrs
316: .hasMoreElements();) {
317:
318: Object a = attrs.nextElement();
319: if (a instanceof HTML.Tag && !openEmbeddedTags.contains(a)) {
320: Object value = attr.getAttribute(a);
321: writeTag(
322: (HTML.Tag) a,
323: value instanceof AttributeSet ? (AttributeSet) value
324: : null);
325: openEmbeddedTags.add(a);
326: }
327: }
328: }
329:
330: protected void closeOutUnwantedEmbeddedTags(final AttributeSet attr)
331: throws IOException {
332:
333: int start = 0;
334: while (start < openEmbeddedTags.size()
335: && attr.isDefined(openEmbeddedTags.get(start))) {
336: start++;
337: }
338:
339: for (int i = openEmbeddedTags.size() - 1; i >= start; i--) {
340: Object a = openEmbeddedTags.get(i);
341: write("</" + a + ">");
342: openEmbeddedTags.remove(a);
343: }
344: }
345:
346: protected void writeLineSeparator() throws IOException {
347: super .writeLineSeparator();
348: }
349:
350: protected void writeOption(final Option option) throws IOException {
351: indent();
352: StringBuffer buffer = new StringBuffer(50);
353: buffer.append("<option");
354: String value = (String) option.getAttributes().getAttribute(
355: HTML.Attribute.VALUE);
356: if (value != null) {
357: buffer.append(" value=");
358: buffer.append(value);
359: }
360: if (option.isSelected()) {
361: buffer.append(" selected");
362: }
363: buffer.append(">");
364: if (option.getLabel() != null) {
365: buffer.append(option.getLabel());
366: }
367:
368: write(buffer.toString());
369: writeLineSeparator();
370: }
371:
372: private void startOptionGroup(final Option option)
373: throws IOException {
374: if (isOptionGroupOpen) {
375: endOptionGroup();
376: }
377:
378: isOptionGroupOpen = true;
379: indent();
380: StringBuffer buffer = new StringBuffer(50);
381: buffer.append("<optgroup");
382: if (option.getLabel() != null) {
383: buffer.append(" label=\"");
384: buffer.append(option.getLabel());
385: buffer.append("\"");
386: }
387: buffer.append(">");
388:
389: write(buffer.toString());
390: writeLineSeparator();
391: incrIndent();
392: }
393:
394: private void endOptionGroup() throws IOException {
395: if (!isOptionGroupOpen) {
396: return;
397: }
398:
399: decrIndent();
400: indent();
401: write("</optgroup>");
402: writeLineSeparator();
403: isOptionGroupOpen = false;
404: }
405:
406: protected boolean getCanWrapLines() {
407: return super .getCanWrapLines()
408: && (!preformatted && writingContent);
409: }
410:
411: private static HTML.Tag getHTMLTag(final AttributeSet attrs) {
412: return (HTML.Tag) attrs
413: .getAttribute(StyleConstants.NameAttribute);
414: }
415:
416: private String getEntity(final char ch) {
417: boolean useName = ch == '<' || ch == '>' || ch == '&'
418: || ch == '"';
419: Entity entity = (Entity) getDTD().entityHash
420: .get(new Integer(ch));
421: if (entity == null) {
422: return null;
423: } else if (useName) {
424: return "&" + entity.getName() + ";";
425: } else {
426: return "&#" + Integer.toString(ch) + ";";
427: }
428: }
429:
430: private static DTD getDTD() {
431: if (dtd == null) {
432: try {
433: dtd = DTD.getDTD("writer");
434: dtd.read(new DataInputStream(dtd.getClass()
435: .getResourceAsStream("transitional401.bdtd")));
436: } catch (IOException e) {
437: e.printStackTrace();
438: }
439: }
440:
441: return dtd;
442: }
443:
444: private boolean isEmptyTag(final HTML.Tag tag) {
445: if (HTML.Tag.CONTENT.equals(tag)
446: || HTML.Tag.COMMENT.equals(tag)) {
447: return true;
448: }
449: return getDTD().getElement(tag.toString()).isEmpty();
450: }
451:
452: private void writeTag(final HTML.Tag tag, final AttributeSet attrs)
453: throws IOException {
454:
455: write("<" + tag);
456: if (attrs != null) {
457: writeAttributes(attrs);
458: }
459: write('>');
460: }
461:
462: private void writeSynthesizedHead() throws IOException {
463: writeTag(HTML.Tag.HEAD, null);
464: writeLineSeparator();
465: writeLineSeparator();
466: indent();
467: write("</" + HTML.Tag.HEAD + ">");
468: writeLineSeparator();
469: indent();
470: }
471:
472: private void writeDocumentProperties() throws IOException {
473: writeEmbeddedStyleSheet();
474: writeDocumentTitle();
475: writeDocumentBase();
476: }
477:
478: private void writeEmbeddedStyleSheet() throws IOException {
479: StyleSheet ss = ((HTMLDocument) getDocument()).getStyleSheet();
480: Enumeration styles = ss.getStyleNames();
481: boolean firstRule = true;
482: while (styles.hasMoreElements()) {
483: String styleName = (String) styles.nextElement();
484: if (StyleSheet.DEFAULT_STYLE.equals(styleName)) {
485: continue;
486: }
487: if (firstRule) {
488: indent();
489: write("<style type=\"text/css\">");
490: writeLineSeparator();
491: incrIndent();
492: indent();
493: write("<!--");
494: writeLineSeparator();
495: incrIndent();
496: firstRule = false;
497: }
498: Style styleRule = ss.getStyle(styleName);
499: indent();
500: write(styleName);
501: writeCSSAttributes(styleRule, " { ", " }");
502: writeLineSeparator();
503: }
504: if (!firstRule) {
505: decrIndent();
506: indent();
507: write("-->");
508: writeLineSeparator();
509: decrIndent();
510: indent();
511: write("</style>");
512: writeLineSeparator();
513: }
514: }
515:
516: private void writeCSSAttributes(final AttributeSet attr,
517: final String begin, final String end) throws IOException {
518: boolean firstCSSAttr = true;
519: for (Enumeration attrs = attr.getAttributeNames(); attrs
520: .hasMoreElements();) {
521:
522: Object a = attrs.nextElement();
523: if (a instanceof CSS.Attribute) {
524: if (firstCSSAttr) {
525: firstCSSAttr = false;
526: write(begin);
527: } else {
528: write("; ");
529: }
530: write(a + ": " + attr.getAttribute(a));
531: }
532: }
533: if (!firstCSSAttr) {
534: write(end);
535: }
536: }
537:
538: private void writeDocumentTitle() throws IOException {
539: Object title = getDocument()
540: .getProperty(Document.TitleProperty);
541: if (title == null) {
542: return;
543: }
544:
545: indent();
546: write("<title>");
547: write(title.toString());
548: write("</title>");
549: writeLineSeparator();
550: }
551:
552: private void writeDocumentBase() throws IOException {
553: URL url = (URL) getDocument().getProperty(
554: HTMLDocument.INITIAL_BASE_PROPERTY);
555: if (url == null) {
556: return;
557: }
558:
559: indent();
560: write("<base href=\"");
561: write(url.toString());
562: write("\">");
563: }
564:
565: private void writeAdditionalComment() throws IOException {
566: Object comments = getDocument().getProperty(
567: HTMLDocument.AdditionalComments);
568: if (comments == null) {
569: return;
570: }
571:
572: Vector strings = (Vector) comments;
573: for (Iterator it = strings.iterator(); it.hasNext();) {
574: write("<!--");
575: write(it.next().toString());
576: write("-->");
577: writeLineSeparator();
578: }
579: }
580: }
|