001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.trans.XPathException;
004: import net.sf.saxon.style.StandardNames;
005:
006: import javax.xml.transform.OutputKeys;
007: import java.util.Properties;
008:
009: /**
010: * XMLIndenter: This ProxyReceiver indents elements, by adding character data where appropriate.
011: * The character data is always added as "ignorable white space", that is, it is never added
012: * adjacent to existing character data.
013: *
014: * @author Michael Kay
015: */
016:
017: public class XMLIndenter extends ProxyReceiver {
018:
019: private int level = 0;
020: private int indentSpaces = 3;
021: private String indentChars = "\n ";
022: private boolean sameline = false;
023: private boolean afterStartTag = false;
024: private boolean afterEndTag = true;
025: private boolean allWhite = true;
026: private int line = 0; // line and column measure the number of lines and columns
027: private int column = 0; // .. in whitespace text nodes between tags
028: private int suppressedAtLevel = -1;
029: private int xmlspace;
030:
031: /**
032: * Set the properties for this indenter
033: */
034:
035: public void setOutputProperties(Properties props) {
036: String s = props.getProperty(SaxonOutputKeys.INDENT_SPACES);
037: if (s == null) {
038: indentSpaces = 3;
039: } else {
040: try {
041: indentSpaces = Integer.parseInt(s.trim());
042: } catch (NumberFormatException err) {
043: indentSpaces = 3;
044: }
045: }
046: String omit = props
047: .getProperty(OutputKeys.OMIT_XML_DECLARATION);
048: afterEndTag = omit == null || !omit.trim().equals("yes")
049: || props.getProperty(OutputKeys.DOCTYPE_SYSTEM) != null;
050: }
051:
052: /**
053: * Start of document
054: */
055:
056: public void open() throws XPathException {
057: super .open();
058: //xmlspace = getNamePool().allocate("xml", NamespaceConstant.XML, "space") & 0xfffff;
059: xmlspace = StandardNames.XML_SPACE;
060: }
061:
062: /**
063: * Output element start tag
064: */
065:
066: public void startElement(int nameCode, int typeCode,
067: int locationId, int properties) throws XPathException {
068: if (afterStartTag || afterEndTag) {
069: indent();
070: }
071: super .startElement(nameCode, typeCode, locationId, properties);
072: level++;
073: sameline = true;
074: afterStartTag = true;
075: afterEndTag = false;
076: allWhite = true;
077: line = 0;
078: }
079:
080: /**
081: * Output an attribute
082: */
083:
084: public void attribute(int nameCode, int typeCode,
085: CharSequence value, int locationId, int properties)
086: throws XPathException {
087: if ((nameCode & 0xfffff) == xmlspace
088: && value.equals("preserve") && suppressedAtLevel < 0) {
089: suppressedAtLevel = level;
090: }
091: super .attribute(nameCode, typeCode, value, locationId,
092: properties);
093: }
094:
095: /**
096: * Output element end tag
097: */
098:
099: public void endElement() throws XPathException {
100: level--;
101: if (afterEndTag && !sameline) {
102: indent();
103: }
104: super .endElement();
105: sameline = false;
106: afterEndTag = true;
107: afterStartTag = false;
108: allWhite = true;
109: line = 0;
110: if (level == (suppressedAtLevel - 1)) {
111: suppressedAtLevel = -1;
112: // remove the suppression of indentation
113: }
114: }
115:
116: /**
117: * Output a processing instruction
118: */
119:
120: public void processingInstruction(String target, CharSequence data,
121: int locationId, int properties) throws XPathException {
122: if (afterEndTag) {
123: indent();
124: }
125: super .processingInstruction(target, data, locationId,
126: properties);
127: afterStartTag = false;
128: afterEndTag = false;
129: }
130:
131: /**
132: * Output character data
133: */
134:
135: public void characters(CharSequence chars, int locationId,
136: int properties) throws XPathException {
137: for (int i = 0; i < chars.length(); i++) {
138: char c = chars.charAt(i);
139: if (c == '\n') {
140: sameline = false;
141: line++;
142: column = 0;
143: }
144: if (!Character.isWhitespace(c)) {
145: allWhite = false;
146: }
147: column++;
148: }
149: super .characters(chars, locationId, properties);
150: if (!allWhite) {
151: afterStartTag = false;
152: afterEndTag = false;
153: }
154: }
155:
156: /**
157: * Output a comment
158: */
159:
160: public void comment(CharSequence chars, int locationId,
161: int properties) throws XPathException {
162: if (afterEndTag) {
163: indent();
164: }
165: super .comment(chars, locationId, properties);
166: afterStartTag = false;
167: afterEndTag = false;
168: }
169:
170: /**
171: * Output white space to reflect the current indentation level
172: */
173:
174: private void indent() throws XPathException {
175: if (suppressedAtLevel >= 0) {
176: // indentation has been suppressed (e.g. by xmlspace="preserve")
177: return;
178: }
179: int spaces = level * indentSpaces;
180: if (line > 0) {
181: spaces -= column;
182: if (spaces <= 0) {
183: return; // there's already enough white space, don't add more
184: }
185: }
186: while (spaces >= indentChars.length()) {
187: indentChars += " ";
188: }
189: // output the initial newline character only if line==0
190: int start = (line == 0 ? 0 : 1);
191: super .characters(indentChars.subSequence(start, start + spaces
192: + 1), 0, ReceiverOptions.NO_SPECIAL_CHARS);
193: sameline = false;
194: }
195:
196: };
197:
198: //
199: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
200: // you may not use this file except in compliance with the License. You may obtain a copy of the
201: // License at http://www.mozilla.org/MPL/
202: //
203: // Software distributed under the License is distributed on an "AS IS" basis,
204: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
205: // See the License for the specific language governing rights and limitations under the License.
206: //
207: // The Original Code is: all this file.
208: //
209: // The Initial Developer of the Original Code is Michael H. Kay.
210: //
211: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
212: //
213: // Contributor(s): none.
214: //
|