001 /*
002 // $Id: //open/util/resgen/src/org/eigenbase/xom/XMLOutput.java#5 $
003 // Package org.eigenbase.xom is an XML Object Mapper.
004 // Copyright (C) 2005-2008 The Eigenbase Project
005 // Copyright (C) 2005-2008 Disruptive Tech
006 // Copyright (C) 2005-2008 LucidEra, Inc.
007 // Portions Copyright (C) 2000-2005 Kana Software, Inc. and others.
008 //
009 // This library is free software; you can redistribute it and/or modify it
010 // under the terms of the GNU Lesser General Public License as published by the
011 // Free Software Foundation; either version 2 of the License, or (at your
012 // option) any later version approved by The Eigenbase Project.
013 //
014 // This library is distributed in the hope that it will be useful,
015 // but WITHOUT ANY WARRANTY; without even the implied warranty of
016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017 // GNU Lesser General Public License for more details.
018 //
019 // You should have received a copy of the GNU Lesser General Public License
020 // along with this library; if not, write to the Free Software
021 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022 //
023 // dsommerfield, 12 December, 2000
024 */
025
026 package org.eigenbase.xom;
027
028 import java.io.*;
029 import java.util.Vector;
030
031 /**
032 * XMLOutput is a class which implements streaming XML output. Use this class
033 * to write XML to any streaming source. While the class itself is
034 * unstructured and doesn't enforce any DTD specification, use of the class
035 * does ensure that the output is syntactically valid XML.
036 */
037 public class XMLOutput {
038
039 // This Writer is the underlying output stream to which all XML is
040 // written.
041 private PrintWriter out;
042
043 // The tagStack is maintained to check that tags are balanced.
044 private Vector tagStack;
045
046 // The class maintains an indentation level to improve output quality.
047 private int indent;
048
049 // The class also maintains the total number of tags written. This
050 // is used to monitor changes to the output
051 private int tagsWritten;
052
053 // This flag is set to true if the output should be compacted.
054 // Compacted output is free of extraneous whitespace and is designed
055 // for easier transport.
056 private boolean compact;
057
058 /** @see setIndentString **/
059 private String indentString = "\t";
060
061 /** @see setGlob **/
062 private boolean glob;
063
064 /**
065 * Whether we have started but not finished a start tag. This only happens
066 * if <code>glob</code> is true. The start tag is automatically closed
067 * when we start a child node. If there are no child nodes, {@link #endTag}
068 * creates an empty tag.
069 **/
070 private boolean inTag;
071
072 /** @see #setAlwaysQuoteCData */
073 private boolean alwaysQuoteCData;
074
075 /** @see #setIgnorePcdata **/
076 private boolean ignorePcdata;
077
078 /**
079 * Private helper function to display a degree of indentation
080 * @param out the PrintWriter to which to display output.
081 * @param indent the degree of indentation.
082 */
083 private void displayIndent(PrintWriter out, int indent)
084 {
085 if(!compact) {
086 for (int i = 0; i < indent; i++) {
087 out.print(indentString);
088 }
089 }
090 }
091
092 /**
093 * Constructs a new XMLOutput based on any Writer.
094 * @param out the writer to which this XMLOutput generates results.
095 */
096 public XMLOutput(Writer out)
097 {
098 this.out = new PrintWriter(out, true);
099 indent = 0;
100 tagsWritten = 0;
101 tagStack = new Vector();
102 }
103
104 /**
105 * Sets or unsets the compact mode. Compact mode causes the generated
106 * XML to be free of extraneous whitespace and other unnecessary
107 * characters.
108 *
109 * @param compact true to turn on compact mode, or false to turn it off.
110 */
111 public void setCompact(boolean compact)
112 {
113 this.compact = compact;
114 }
115
116 public boolean getCompact()
117 {
118 return compact;
119 }
120
121 /**
122 * Sets the string to print for each level of indentation. The default is a
123 * tab. The value must not be <code>null</code>. Set this to the empty
124 * string to achieve no indentation (note that <code>{@link
125 * #setCompact}(true)</code> removes indentation <em>and</em> newlines).
126 **/
127 public void setIndentString(String indentString)
128 {
129 this.indentString = indentString;
130 }
131
132 /**
133 * Sets whether to detect that tags are empty.
134 **/
135 public void setGlob(boolean glob)
136 {
137 this.glob = glob;
138 }
139
140 /**
141 * Sets whether to always quote cdata segments (even if they don't contain
142 * special characters).
143 **/
144 public void setAlwaysQuoteCData(boolean alwaysQuoteCData)
145 {
146 this.alwaysQuoteCData = alwaysQuoteCData;
147 }
148
149 /**
150 * Sets whether to ignore unquoted text, such as whitespace.
151 **/
152 public void setIgnorePcdata(boolean ignorePcdata)
153 {
154 this.ignorePcdata = ignorePcdata;
155 }
156
157 public boolean getIgnorePcdata()
158 {
159 return ignorePcdata;
160 }
161
162 /**
163 * Sends a string directly to the output stream, without escaping any
164 * characters. Use with caution!
165 **/
166 public void print(String s)
167 {
168 out.print(s);
169 }
170
171 /**
172 * Start writing a new tag to the stream. The tag's name must be given and
173 * its attributes should be specified by a fully constructed AttrVector
174 * object.
175 * @param tagName the name of the tag to write.
176 * @param attributes an XMLAttrVector containing the attributes to include
177 * in the tag.
178 */
179 public void beginTag(String tagName, XMLAttrVector attributes)
180 {
181 beginBeginTag(tagName);
182 if (attributes != null) {
183 attributes.display(out, indent);
184 }
185 endBeginTag(tagName);
186 }
187
188 public void beginBeginTag(String tagName)
189 {
190 if (inTag) {
191 // complete the parent's start tag
192 if (compact) {
193 out.print(">");
194 } else {
195 out.println(">");
196 }
197 inTag = false;
198 }
199 displayIndent(out, indent);
200 out.print("<");
201 out.print(tagName);
202 }
203
204 public void endBeginTag(String tagName)
205 {
206 if (glob) {
207 inTag = true;
208 } else if (compact) {
209 out.print(">");
210 } else {
211 out.println(">");
212 }
213 out.flush();
214 tagStack.addElement(tagName);
215 indent++;
216 tagsWritten++;
217 }
218
219 /**
220 * Write an attribute.
221 **/
222 public void attribute(String name, String value)
223 {
224 XMLUtil.printAtt(out, name, value);
225 }
226
227 /**
228 * If we are currently inside the start tag, finish it off.
229 **/
230 public void beginNode()
231 {
232 if (inTag) {
233 // complete the parent's start tag
234 if (compact) {
235 out.print(">");
236 } else {
237 out.println(">");
238 }
239 inTag = false;
240 }
241 }
242
243 /**
244 * Complete a tag. This outputs the end tag corresponding to the
245 * last exposed beginTag. The tag name must match the name of the
246 * corresponding beginTag.
247 * @param tagName the name of the end tag to write.
248 */
249 public void endTag(String tagName)
250 {
251 // Check that the end tag matches the corresponding start tag
252 int stackSize = tagStack.size();
253 String matchTag = (String)(tagStack.elementAt(stackSize-1));
254 if(!tagName.equalsIgnoreCase(matchTag))
255 throw new AssertFailure(
256 "End tag <" + tagName + "> does not match " +
257 " start tag <" + matchTag + ">");
258 tagStack.removeElementAt(stackSize-1);
259
260 // Lower the indent and display the end tag
261 indent--;
262 if (inTag) {
263 // we're still in the start tag -- this element had no children
264 if (compact) {
265 out.print("/>");
266 } else {
267 out.println("/>");
268 }
269 inTag = false;
270 } else {
271 displayIndent(out, indent);
272 out.print("</");
273 out.print(tagName);
274 if (compact) {
275 out.print(">");
276 } else {
277 out.println(">");
278 }
279 }
280 out.flush();
281 }
282
283 /**
284 * Write an empty tag to the stream. An empty tag is one with no
285 * tags inside it, although it may still have attributes.
286 * @param tagName the name of the empty tag.
287 * @param attributes an XMLAttrVector containing the attributes to
288 * include in the tag.
289 */
290 public void emptyTag(String tagName, XMLAttrVector attributes)
291 {
292 if (inTag) {
293 // complete the parent's start tag
294 if (compact) {
295 out.print(">");
296 } else {
297 out.println(">");
298 }
299 inTag = false;
300 }
301 displayIndent(out, indent);
302 out.print("<");
303 out.print(tagName);
304 if(attributes != null) {
305 out.print(" ");
306 attributes.display(out, indent);
307 }
308
309 if(compact)
310 out.print("/>");
311 else
312 out.println("/>");
313 out.flush();
314 tagsWritten++;
315 }
316
317 /**
318 * Write a CDATA section. Such sections always appear on their own line.
319 * The nature in which the CDATA section is written depends on the actual
320 * string content with respect to these special characters/sequences:
321 * <ul>
322 * <li><code>&</code>
323 * <li><code>"</code>
324 * <li><code>'</code>
325 * <li><code><</code>
326 * <li><code>></code>
327 * </ul>
328 * Additionally, the sequence <code>]]></code> is special.
329 * <ul>
330 * <li>Content containing no special characters will be left as-is.
331 * <li>Content containing one or more special characters but not the
332 * sequence <code>]]></code> will be enclosed in a CDATA section.
333 * <li>Content containing special characters AND at least one
334 * <code>]]></code> sequence will be left as-is but have all of its
335 * special characters encoded as entities.
336 * </ul>
337 * These special treatment rules are required to allow cdata sections
338 * to contain XML strings which may themselves contain cdata sections.
339 * Traditional CDATA sections <b>do not nest</b>.
340 */
341 public void cdata(String data)
342 {
343 cdata(data, false);
344 }
345
346 /**
347 * Writes a CDATA section (as {@link #cdata(String)}).
348 *
349 * @param data string to write
350 * @param quote if true, quote in a <code><![CDATA[</code>
351 * ... <code>]]></code> regardless of the content of
352 * <code>data</code>; if false, quote only if the content needs it
353 **/
354 public void cdata(String data, boolean quote)
355 {
356 if (inTag) {
357 // complete the parent's start tag
358 if (compact) {
359 out.print(">");
360 } else {
361 out.println(">");
362 }
363 inTag = false;
364 }
365 if (data == null) {
366 data = "";
367 }
368 boolean specials = false;
369 boolean cdataEnd = false;
370
371 // Scan the string for special characters
372 // If special characters are found, scan the string for ']]>'
373 if(XOMUtil.stringHasXMLSpecials(data)) {
374 specials = true;
375 if(data.indexOf("]]>") > -1)
376 cdataEnd = true;
377 }
378
379 // Display the result
380 displayIndent(out, indent);
381 if (quote || alwaysQuoteCData) {
382 out.print("<![CDATA[");
383 out.print(data);
384 out.println("]]>");
385 } else if (!specials && !cdataEnd) {
386 out.print(data);
387 } else {
388 XMLUtil.stringEncodeXML(data, out);
389 }
390
391 out.flush();
392 tagsWritten++;
393 }
394
395 /**
396 * Write a String tag; a tag containing nothing but a CDATA section.
397 */
398 public void stringTag(String name, String data)
399 {
400 beginTag(name, null);
401 cdata(data);
402 endTag(name);
403 }
404
405 /**
406 * Write content.
407 */
408 public void content(String content)
409 {
410 if(content != null) {
411 indent++;
412 LineNumberReader in = new LineNumberReader(new StringReader(content));
413 try {
414 String line;
415 while((line = in.readLine()) != null) {
416 displayIndent(out, indent);
417 out.println(line);
418 }
419 } catch (IOException ex) {
420 throw new AssertFailure(ex);
421 }
422 indent--;
423 out.flush();
424 }
425 tagsWritten++;
426 }
427
428 /**
429 * Write header. Use default version 1.0.
430 */
431 public void header()
432 {
433 out.println("<?xml version=\"1.0\" ?>");
434 out.flush();
435 tagsWritten++;
436 }
437
438 /**
439 * Write header, take version as input.
440 */
441 public void header(String version)
442 {
443 out.print("<?xml version=\"");
444 out.print(version);
445 out.println("\" ?>");
446 out.flush();
447 tagsWritten++;
448 }
449
450 /**
451 * Get the total number of tags written
452 * @return the total number of tags written to the XML stream.
453 */
454 public int numTagsWritten()
455 {
456 return tagsWritten;
457 }
458
459 }
460
461
462 // End XMLOutput.java