001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.trans.XPathException;
004:
005: import java.util.Properties;
006:
007: /**
008: * HTMLIndenter: This ProxyEmitter indents HTML elements, by adding whitespace
009: * character data where appropriate.
010: * The character data is never added when within an inline element.
011: * The string used for indentation defaults to four spaces, but may be set using the
012: * indent-chars property
013: *
014: * @author Michael Kay
015: */
016:
017: public class HTMLIndenter extends ProxyReceiver {
018:
019: private int level = 0;
020: private int indentSpaces = 3;
021: private String indentChars = " ";
022: private boolean sameLine = false;
023: private boolean isInlineTag = false;
024: private boolean inFormattedTag = false;
025: private boolean afterInline = false;
026: private boolean afterFormatted = true; // to prevent a newline at the start
027: private int[] propertyStack = new int[20];
028:
029: // the list of inline tags is from the HTML 4.0 (loose) spec. The significance is that we
030: // mustn't add spaces immediately before or after one of these elements.
031:
032: protected static String[] inlineTags = { "tt", "i", "b", "u", "s",
033: "strike", "big", "small", "em", "strong", "dfn", "code",
034: "samp", "kbd", "var", "cite", "abbr", "acronym", "a",
035: "img", "applet", "object", "font", "basefont", "br",
036: "script", "map", "q", "sub", "sup", "span", "bdo",
037: "iframe", "input", "select", "textarea", "label", "button",
038: "ins", "del" };
039:
040: // INS and DEL are not actually inline elements, but the SGML DTD for HTML
041: // (apparently) permits them to be used as if they were.
042:
043: private static HTMLTagHashSet inlineTable = new HTMLTagHashSet(101);
044:
045: static {
046: for (int j = 0; j < inlineTags.length; j++) {
047: inlineTable.add(inlineTags[j]);
048: }
049: }
050:
051: protected static final int IS_INLINE = 1;
052: protected static final int IS_FORMATTED = 2;
053:
054: /**
055: * Classify an element name as inline, formatted, or both or neither.
056: * This method is overridden in the XHTML indenter
057: * @param nameCode the element name
058: * @return a bit-significant integer containing flags IS_INLINE and/or IS_FORMATTED
059: */
060: protected int classifyTag(int nameCode) {
061: int r = 0;
062: String tag = getNamePool().getDisplayName(nameCode);
063: if (inlineTable.contains(tag)) {
064: r |= IS_INLINE;
065: }
066: if (formattedTable.contains(tag)) {
067: r |= IS_FORMATTED;
068: }
069: return r;
070: }
071:
072: // Table of preformatted elements
073:
074: private static HTMLTagHashSet formattedTable = new HTMLTagHashSet(
075: 23);
076: protected static String[] formattedTags = { "pre", "script",
077: "style", "textarea", "xmp" };
078: // "xmp" is obsolete but still encountered!
079:
080: static {
081: for (int i = 0; i < formattedTags.length; i++) {
082: formattedTable.add(formattedTags[i]);
083: }
084: }
085:
086: public HTMLIndenter() {
087: }
088:
089: /**
090: * Set the properties for this indenter
091: */
092:
093: public void setOutputProperties(Properties props) {
094: String s = props.getProperty(SaxonOutputKeys.INDENT_SPACES);
095: if (s == null) {
096: indentSpaces = 3;
097: } else {
098: try {
099: indentSpaces = Integer.parseInt(s);
100: } catch (NumberFormatException err) {
101: indentSpaces = 3;
102: }
103: }
104: }
105:
106: /**
107: * Output element start tag
108: */
109:
110: public void startElement(int nameCode, int typeCode,
111: int locationId, int properties) throws XPathException {
112: int tagProps = classifyTag(nameCode);
113: if (level >= propertyStack.length) {
114: int[] p2 = new int[level * 2];
115: System.arraycopy(propertyStack, 0, p2, 0,
116: propertyStack.length);
117: propertyStack = p2;
118: }
119: propertyStack[level] = tagProps;
120: isInlineTag = (tagProps & IS_INLINE) != 0;
121: inFormattedTag = inFormattedTag
122: || ((tagProps & IS_FORMATTED) != 0);
123: if (!isInlineTag && !inFormattedTag && !afterInline
124: && !afterFormatted) {
125: indent();
126: }
127:
128: super .startElement(nameCode, typeCode, locationId, properties);
129: level++;
130: sameLine = true;
131: afterInline = false;
132: afterFormatted = false;
133: }
134:
135: /**
136: * Output element end tag
137: */
138:
139: public void endElement() throws XPathException {
140: level--;
141: boolean this Inline = (propertyStack[level] & IS_INLINE) != 0;
142: boolean this Formatted = (propertyStack[level] & IS_FORMATTED) != 0;
143: if (!this Inline && !this Formatted && !afterInline && !sameLine
144: && !afterFormatted && !inFormattedTag) {
145: indent();
146: afterInline = false;
147: afterFormatted = false;
148: } else {
149: afterInline = this Inline;
150: afterFormatted = this Formatted;
151: }
152: super .endElement();
153: inFormattedTag = inFormattedTag && !this Formatted;
154: sameLine = false;
155: }
156:
157: /**
158: * Output character data
159: */
160:
161: public void characters(CharSequence chars, int locationId,
162: int properties) throws XPathException {
163: if (inFormattedTag) {
164: super .characters(chars, locationId, properties);
165: } else {
166: int lastNL = 0;
167:
168: for (int i = 0; i < chars.length(); i++) {
169: if (chars.charAt(i) == '\n'
170: || (i - lastNL > 120 && chars.charAt(i) == ' ')) {
171: sameLine = false;
172: super .characters(chars.subSequence(lastNL, i),
173: locationId, properties);
174: indent();
175: lastNL = i + 1;
176: while (lastNL < chars.length()
177: && chars.charAt(lastNL) == ' ') {
178: lastNL++;
179: }
180: }
181: }
182: if (lastNL < chars.length()) {
183: super .characters(chars.subSequence(lastNL, chars
184: .length()), locationId, properties);
185: }
186: }
187: afterInline = false;
188: }
189:
190: /**
191: * Output a comment
192: */
193:
194: public void comment(CharSequence chars, int locationId,
195: int properties) throws XPathException {
196: indent();
197: super .comment(chars, locationId, properties);
198: }
199:
200: /**
201: * Output white space to reflect the current indentation level
202: */
203:
204: private void indent() throws XPathException {
205: int spaces = level * indentSpaces;
206: while (spaces > indentChars.length()) {
207: indentChars += indentChars;
208: }
209: super .characters("\n", 0, 0);
210: super .characters(indentChars.subSequence(0, spaces), 0, 0);
211: sameLine = false;
212: }
213:
214: };
215:
216: //
217: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
218: // you may not use this file except in compliance with the License. You may obtain a copy of the
219: // License at http://www.mozilla.org/MPL/
220: //
221: // Software distributed under the License is distributed on an "AS IS" basis,
222: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
223: // See the License for the specific language governing rights and limitations under the License.
224: //
225: // The Original Code is: all this file.
226: //
227: // The Initial Developer of the Original Code is Michael H. Kay.
228: //
229: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
230: //
231: // Contributor(s): none.
232: //
|