/[classpath]/classpath/external/jaxp/source/gnu/xml/util/XMLWriter.java
ViewVC logotype

Contents of /classpath/external/jaxp/source/gnu/xml/util/XMLWriter.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1.1.1 - (show annotations) (download) (vendor branch)
Sat Feb 1 02:10:26 2003 UTC (21 years, 2 months ago) by cbj
Branch: Classpathx
CVS Tags: classpath-0_07-release, JAXP_CVS_20030814, classpath-0_05-release, classpath-0_08-release, classpath-0_06-release, JAXP_CVS_20030123
Changes since 1.1: +0 -0 lines
import classpathx jaxp 20030123

1 /*
2 * $Id: XMLWriter.java,v 1.8 2001/11/20 01:15:45 db Exp $
3 * Copyright (C) 1999-2001 David Brownell
4 *
5 * This file is part of GNU JAXP, a library.
6 *
7 * GNU JAXP is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * GNU JAXP is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 * As a special exception, if you link this library with other files to
22 * produce an executable, this library does not by itself cause the
23 * resulting executable to be covered by the GNU General Public License.
24 * This exception does not however invalidate any other reasons why the
25 * executable file might be covered by the GNU General Public License.
26 */
27
28 package gnu.xml.util;
29
30 import java.io.BufferedWriter;
31 import java.io.CharConversionException;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.io.OutputStreamWriter;
35 import java.io.Writer;
36 import java.util.Stack;
37
38 import org.xml.sax.*;
39 import org.xml.sax.ext.*;
40 import org.xml.sax.helpers.*;
41
42
43 /**
44 * This class is a SAX handler which writes all its input as a well formed
45 * XML or XHTML document. If driven using SAX2 events, this output may
46 * include a recreated document type declaration, subject to limitations
47 * of SAX (no internal subset exposed) or DOM (the important declarations,
48 * with their documentation, are discarded).
49 *
50 * <p> By default, text is generated "as-is", but some optional modes
51 * are supported. Pretty-printing is supported, to make life easier
52 * for people reading the output. XHTML (1.0) output has can be made
53 * particularly pretty; all the built-in character entities are known.
54 * Canonical XML can also be generated, assuming the input is properly
55 * formed.
56 *
57 * <hr>
58 *
59 * <p> Some of the methods on this class are intended for applications to
60 * use directly, rather than as pure SAX2 event callbacks. Some of those
61 * methods access the JavaBeans properties (used to tweak output formats,
62 * for example canonicalization and pretty printing). Subclasses
63 * are expected to add new behaviors, not to modify current behavior, so
64 * many such methods are final.</p>
65 *
66 * <p> The <em>write*()</em> methods may be slightly simpler for some
67 * applications to use than direct callbacks. For example, they support
68 * a simple policy for encoding data items as the content of a single element.
69 *
70 * <p> To reuse an XMLWriter you must provide it with a new Writer, since
71 * this handler closes the writer it was given as part of its endDocument()
72 * handling. (XML documents have an end of input, and the way to encode
73 * that on a stream is to close it.) </p>
74 *
75 * <hr>
76 *
77 * <p> Note that any relative URIs in the source document, as found in
78 * entity and notation declarations, ought to have been fully resolved by
79 * the parser providing events to this handler. This means that the
80 * output text should only have fully resolved URIs, which may not be
81 * the desired behavior in cases where later binding is desired. </p>
82 *
83 * <p> <em>Note that due to SAX2 defaults, you may need to manually
84 * ensure that the input events are XML-conformant with respect to namespace
85 * prefixes and declarations. {@link gnu.xml.pipeline.NSFilter} is
86 * one solution to this problem, in the context of processing pipelines.</em>
87 * Something as simple as connecting this handler to a parser might not
88 * generate the correct output. Another workaround is to ensure that the
89 * <em>namespace-prefixes</em> feature is always set to true, if you're
90 * hooking this directly up to some XMLReader implementation.
91 *
92 * @see gnu.xml.pipeline.TextConsumer
93 *
94 * @author David Brownell
95 * @version $Date: 2001/11/20 01:15:45 $
96 */
97 public class XMLWriter
98 implements ContentHandler, LexicalHandler, DTDHandler, DeclHandler
99 {
100 // text prints/escapes differently depending on context
101 // CTX_ENTITY ... entity literal value
102 // CTX_ATTRIBUTE ... attribute literal value
103 // CTX_CONTENT ... content of an element
104 // CTX_UNPARSED ... CDATA, comment, PI, names, etc
105 // CTX_NAME ... name or nmtoken, no escapes possible
106 private static final int CTX_ENTITY = 1;
107 private static final int CTX_ATTRIBUTE = 2;
108 private static final int CTX_CONTENT = 3;
109 private static final int CTX_UNPARSED = 4;
110 private static final int CTX_NAME = 5;
111
112 // FIXME: names (element, attribute, PI, notation, etc) are not
113 // currently written out with range checks (escapeChars).
114 // In non-XHTML, some names can't be directly written; panic!
115
116 private static String sysEOL;
117
118 static {
119 try {
120 sysEOL = System.getProperty ("line.separator", "\n");
121
122 // don't use the system's EOL if it's illegal XML.
123 if (!isLineEnd (sysEOL))
124 sysEOL = "\n";
125
126 } catch (SecurityException e) {
127 sysEOL = "\n";
128 }
129 }
130
131 private static boolean isLineEnd (String eol)
132 {
133 return "\n".equals (eol)
134 || "\r".equals (eol)
135 || "\r\n".equals (eol);
136 }
137
138 private Writer out;
139 private boolean inCDATA;
140 private int elementNestLevel;
141 private String eol = sysEOL;
142
143 private short dangerMask;
144 private StringBuffer stringBuf;
145 private Locator locator;
146 private ErrorHandler errHandler;
147
148 private boolean expandingEntities = false;
149 private int entityNestLevel;
150 private boolean xhtml;
151 private boolean startedDoctype;
152 private String encoding;
153
154 private boolean canonical;
155 private boolean inDoctype;
156 private boolean inEpilogue;
157
158 // pretty printing controls
159 private boolean prettyPrinting;
160 private int column;
161 private boolean noWrap;
162 private Stack space = new Stack ();
163
164 // this is not a hard'n'fast rule -- longer lines are OK,
165 // but are to be avoided. Here, prettyprinting is more to
166 // show structure "cleanly" than to be precise about it.
167 // better to have ragged layout than one line 24Kb long.
168 private static final int lineLength = 75;
169
170
171 /**
172 * Constructs this handler with System.out used to write SAX events
173 * using the UTF-8 encoding. Avoid using this except when you know
174 * it's safe to close System.out at the end of the document.
175 */
176 public XMLWriter () throws IOException
177 { this (System.out); }
178
179 /**
180 * Constructs a handler which writes all input to the output stream
181 * in the UTF-8 encoding, and closes it when endDocument is called.
182 * (Yes it's annoying that this throws an exception -- but there's
183 * really no way around it, since it's barely possible a JDK may
184 * exist somewhere that doesn't know how to emit UTF-8.)
185 */
186 public XMLWriter (OutputStream out) throws IOException
187 {
188 this (new OutputStreamWriter (out, "UTF8"));
189 }
190
191 /**
192 * Constructs a handler which writes all input to the writer, and then
193 * closes the writer when the document ends. If an XML declaration is
194 * written onto the output, and this class can determine the name of
195 * the character encoding for this writer, that encoding name will be
196 * included in the XML declaration.
197 *
198 * <P> See the description of the constructor which takes an encoding
199 * name for imporant information about selection of encodings.
200 *
201 * @param writer XML text is written to this writer.
202 */
203 public XMLWriter (Writer writer)
204 {
205 this (writer, null);
206 }
207
208 /**
209 * Constructs a handler which writes all input to the writer, and then
210 * closes the writer when the document ends. If an XML declaration is
211 * written onto the output, this class will use the specified encoding
212 * name in that declaration. If no encoding name is specified, no
213 * encoding name will be declared unless this class can otherwise
214 * determine the name of the character encoding for this writer.
215 *
216 * <P> At this time, only the UTF-8 ("UTF8") and UTF-16 ("Unicode")
217 * output encodings are fully lossless with respect to XML data. If you
218 * use any other encoding you risk having your data be silently mangled
219 * on output, as the standard Java character encoding subsystem silently
220 * maps non-encodable characters to a question mark ("?") and will not
221 * report such errors to applications.
222 *
223 * <p> For a few other encodings the risk can be reduced. If the writer is
224 * a java.io.OutputStreamWriter, and uses either the ISO-8859-1 ("8859_1",
225 * "ISO8859_1", etc) or US-ASCII ("ASCII") encodings, content which
226 * can't be encoded in those encodings will be written safely. Where
227 * relevant, the XHTML entity names will be used; otherwise, numeric
228 * character references will be emitted.
229 *
230 * <P> However, there remain a number of cases where substituting such
231 * entity or character references is not an option. Such references are
232 * not usable within a DTD, comment, PI, or CDATA section. Neither may
233 * they be used when element, attribute, entity, or notation names have
234 * the problematic characters.
235 *
236 * @param writer XML text is written to this writer.
237 * @param encoding if non-null, and an XML declaration is written,
238 * this is the name that will be used for the character encoding.
239 */
240 public XMLWriter (Writer writer, String encoding)
241 {
242 setWriter (writer, encoding);
243 }
244
245 private void setEncoding (String encoding)
246 {
247 if (encoding == null && out instanceof OutputStreamWriter)
248 encoding = ((OutputStreamWriter)out).getEncoding ();
249
250 if (encoding != null) {
251 encoding = encoding.toUpperCase ();
252
253 // Use official encoding names where we know them,
254 // avoiding the Java-only names. When using common
255 // encodings where we can easily tell if characters
256 // are out of range, we'll escape out-of-range
257 // characters using character refs for safety.
258
259 // I _think_ these are all the main synonyms for these!
260 if ("UTF8".equals (encoding)) {
261 encoding = "UTF-8";
262 } else if ("US-ASCII".equals (encoding)
263 || "ASCII".equals (encoding)) {
264 dangerMask = (short) 0xff80;
265 encoding = "US-ASCII";
266 } else if ("ISO-8859-1".equals (encoding)
267 || "8859_1".equals (encoding)
268 || "ISO8859_1".equals (encoding)) {
269 dangerMask = (short) 0xff00;
270 encoding = "ISO-8859-1";
271 } else if ("UNICODE".equals (encoding)
272 || "UNICODE-BIG".equals (encoding)
273 || "UNICODE-LITTLE".equals (encoding)) {
274 encoding = "UTF-16";
275
276 // TODO: UTF-16BE, UTF-16LE ... no BOM; what
277 // release of JDK supports those Unicode names?
278 }
279
280 if (dangerMask != 0)
281 stringBuf = new StringBuffer ();
282 }
283
284 this.encoding = encoding;
285 }
286
287
288 /**
289 * Resets the handler to write a new text document.
290 *
291 * @param writer XML text is written to this writer.
292 * @param encoding if non-null, and an XML declaration is written,
293 * this is the name that will be used for the character encoding.
294 *
295 * @exception IllegalStateException if the current
296 * document hasn't yet ended (with {@link #endDocument})
297 */
298 final public void setWriter (Writer writer, String encoding)
299 {
300 if (out != null)
301 throw new IllegalStateException (
302 "can't change stream in mid course");
303 out = writer;
304 if (out != null)
305 setEncoding (encoding);
306 if (!(out instanceof BufferedWriter))
307 out = new BufferedWriter (out);
308 space.push ("default");
309 }
310
311 /**
312 * Assigns the line ending style to be used on output.
313 * @param eolString null to use the system default; else
314 * "\n", "\r", or "\r\n".
315 */
316 final public void setEOL (String eolString)
317 {
318 if (eolString == null)
319 eol = sysEOL;
320 else if (!isLineEnd (eolString))
321 eol = eolString;
322 else
323 throw new IllegalArgumentException (eolString);
324 }
325
326 /**
327 * Assigns the error handler to be used to present most fatal
328 * errors.
329 */
330 public void setErrorHandler (ErrorHandler handler)
331 {
332 errHandler = handler;
333 }
334
335 /**
336 * Used internally and by subclasses, this encapsulates the logic
337 * involved in reporting fatal errors. It uses locator information
338 * for good diagnostics, if available, and gives the application's
339 * ErrorHandler the opportunity to handle the error before throwing
340 * an exception.
341 */
342 protected void fatal (String message, Exception e)
343 throws SAXException
344 {
345 SAXParseException x;
346
347 if (locator == null)
348 x = new SAXParseException (message, null, null, -1, -1, e);
349 else
350 x = new SAXParseException (message, locator, e);
351 if (errHandler != null)
352 errHandler.fatalError (x);
353 throw x;
354 }
355
356
357 // JavaBeans properties
358
359 /**
360 * Controls whether the output should attempt to follow the "transitional"
361 * XHTML rules so that it meets the "HTML Compatibility Guidelines"
362 * appendix in the XHTML specification. A "transitional" Document Type
363 * Declaration (DTD) is placed near the beginning of the output document,
364 * instead of whatever DTD would otherwise have been placed there, and
365 * XHTML empty elements are printed specially. When writing text in
366 * US-ASCII or ISO-8859-1 encodings, the predefined XHTML internal
367 * entity names are used (in preference to character references) when
368 * writing content characters which can't be expressed in those encodings.
369 *
370 * <p> When this option is enabled, it is the caller's responsibility
371 * to ensure that the input is otherwise valid as XHTML. Things to
372 * be careful of in all cases, as described in the appendix referenced
373 * above, include: <ul>
374 *
375 * <li> Element and attribute names must be in lower case, both
376 * in the document and in any CSS style sheet.
377 * <li> All XML constructs must be valid as defined by the XHTML
378 * "transitional" DTD (including all familiar constructs,
379 * even deprecated ones).
380 * <li> The root element must be "html".
381 * <li> Elements that must be empty (such as <em>&lt;br&gt;</em>
382 * must have no content.
383 * <li> Use both <em>lang</em> and <em>xml:lang</em> attributes
384 * when specifying language.
385 * <li> Similarly, use both <em>id</em> and <em>name</em> attributes
386 * when defining elements that may be referred to through
387 * URI fragment identifiers ... and make sure that the
388 * value is a legal NMTOKEN, since not all such HTML 4.0
389 * identifiers are valid in XML.
390 * <li> Be careful with character encodings; make sure you provide
391 * a <em>&lt;meta http-equiv="Content-type"
392 * content="text/xml;charset=..." /&gt;</em> element in
393 * the HTML "head" element, naming the same encoding
394 * used to create this handler. Also, if that encoding
395 * is anything other than US-ASCII, make sure that if
396 * the document is given a MIME content type, it has
397 * a <em>charset=...</em> attribute with that encoding.
398 * </ul>
399 *
400 * <p> Additionally, some of the oldest browsers have additional
401 * quirks, to address with guidelines such as: <ul>
402 *
403 * <li> Processing instructions may be rendered, so avoid them.
404 * (Similarly for an XML declaration.)
405 * <li> Embedded style sheets and scripts should not contain XML
406 * markup delimiters: &amp;, &lt;, and ]]&gt; are trouble.
407 * <li> Attribute values should not have line breaks or multiple
408 * consecutive white space characters.
409 * <li> Use no more than one of the deprecated (transitional)
410 * <em>&lt;isindex&gt;</em> elements.
411 * <li> Some boolean attributes (such as <em>compact, checked,
412 * disabled, readonly, selected,</em> and more) confuse
413 * some browsers, since they only understand minimized
414 * versions which are illegal in XML.
415 * </ul>
416 *
417 * <p> Also, some characteristics of the resulting output may be
418 * a function of whether the document is later given a MIME
419 * content type of <em>text/html</em> rather than one indicating
420 * XML (<em>application/xml</em> or <em>text/xml</em>). Worse,
421 * some browsers ignore MIME content types and prefer to rely URI
422 * name suffixes -- so an "index.xml" could always be XML, never
423 * XHTML, no matter its MIME type.
424 */
425 final public void setXhtml (boolean value)
426 {
427 if (locator != null)
428 throw new IllegalStateException ("started parsing");
429 xhtml = value;
430 if (xhtml)
431 canonical = false;
432 }
433
434 /**
435 * Returns true if the output attempts to echo the input following
436 * "transitional" XHTML rules and matching the "HTML Compatibility
437 * Guidelines" so that an HTML version 3 browser can read the output
438 * as HTML; returns false (the default) othewise.
439 */
440 final public boolean isXhtml ()
441 {
442 return xhtml;
443 }
444
445 /**
446 * Controls whether the output text contains references to
447 * entities (the default), or instead contains the expanded
448 * values of those entities.
449 */
450 final public void setExpandingEntities (boolean value)
451 {
452 if (locator != null)
453 throw new IllegalStateException ("started parsing");
454 expandingEntities = value;
455 if (!expandingEntities)
456 canonical = false;
457 }
458
459 /**
460 * Returns true if the output will have no entity references;
461 * returns false (the default) otherwise.
462 */
463 final public boolean isExpandingEntities ()
464 {
465 return expandingEntities;
466 }
467
468 /**
469 * Controls pretty-printing, which by default is not enabled
470 * (and currently is most useful for XHTML output).
471 * Pretty printing enables structural indentation, sorting of attributes
472 * by name, line wrapping, and potentially other mechanisms for making
473 * output more or less readable.
474 *
475 * <p> At this writing, structural indentation and line wrapping are
476 * enabled when pretty printing is enabled and the <em>xml:space</em>
477 * attribute has the value <em>default</em> (its other legal value is
478 * <em>preserve</em>, as defined in the XML specification). The three
479 * XHTML element types which use another value are recognized by their
480 * names (namespaces are ignored).
481 *
482 * <p> Also, for the record, the "pretty" aspect of printing here
483 * is more to provide basic structure on outputs that would otherwise
484 * risk being a single long line of text. For now, expect the
485 * structure to be ragged ... unless you'd like to submit a patch
486 * to make this be more strictly formatted!
487 *
488 * @exception IllegalStateException thrown if this method is invoked
489 * after output has begun.
490 */
491 final public void setPrettyPrinting (boolean value)
492 {
493 if (locator != null)
494 throw new IllegalStateException ("started parsing");
495 prettyPrinting = value;
496 if (prettyPrinting)
497 canonical = false;
498 }
499
500 /**
501 * Returns value of flag controlling pretty printing.
502 */
503 final public boolean isPrettyPrinting ()
504 {
505 return prettyPrinting;
506 }
507
508
509 /**
510 * Sets the output style to be canonicalized. Input events must
511 * meet requirements that are slightly more stringent than the
512 * basic well-formedness ones, and include: <ul>
513 *
514 * <li> Namespace prefixes must not have been changed from those
515 * in the original document. (This may only be ensured by setting
516 * the SAX2 XMLReader <em>namespace-prefixes</em> feature flag;
517 * by default, it is cleared.)
518 *
519 * <li> Redundant namespace declaration attributes have been
520 * removed. (If an ancestor element defines a namespace prefix
521 * and that declaration hasn't been overriden, an element must
522 * not redeclare it.)
523 *
524 * <li> If comments are not to be included in the canonical output,
525 * they must first be removed from the input event stream; this
526 * <em>Canonical XML with comments</em> by default.
527 *
528 * <li> If the input character encoding was not UCS-based, the
529 * character data must have been normalized using Unicode
530 * Normalization Form C. (UTF-8 and UTF-16 are UCS-based.)
531 *
532 * <li> Attribute values must have been normalized, as is done
533 * by any conformant XML processor which processes all external
534 * parameter entities.
535 *
536 * <li> Similarly, attribute value defaulting has been performed.
537 *
538 * </ul>
539 *
540 * <p> Note that fragments of XML documents, as specified by an XPath
541 * node set, may be canonicalized. In such cases, elements may need
542 * some fixup (for <em>xml:*</em> attributes and application-specific
543 * context).
544 *
545 * @exception IllegalArgumentException if the output encoding
546 * is anything other than UTF-8.
547 */
548 final public void setCanonical (boolean value)
549 {
550 if (value && !"UTF-8".equals (encoding))
551 throw new IllegalArgumentException ("encoding != UTF-8");
552 canonical = value;
553 if (canonical) {
554 prettyPrinting = xhtml = false;
555 expandingEntities = true;
556 eol = "\n";
557 }
558 }
559
560
561 /**
562 * Returns value of flag controlling canonical output.
563 */
564 final public boolean isCanonical ()
565 {
566 return canonical;
567 }
568
569
570 /**
571 * Flushes the output stream. When this handler is used in long lived
572 * pipelines, it can be important to flush buffered state, for example
573 * so that it can reach the disk as part of a state checkpoint.
574 */
575 final public void flush ()
576 throws IOException
577 {
578 if (out != null)
579 out.flush ();
580 }
581
582
583 // convenience routines
584
585 // FIXME: probably want a subclass that holds a lot of these...
586 // and maybe more!
587
588 /**
589 * Writes the string as if characters() had been called on the contents
590 * of the string. This is particularly useful when applications act as
591 * producers and write data directly to event consumers.
592 */
593 final public void write (String data)
594 throws SAXException
595 {
596 char buf [] = data.toCharArray ();
597 characters (buf, 0, buf.length);
598 }
599
600
601 /**
602 * Writes an element that has content consisting of a single string.
603 * @see #writeEmptyElement
604 * @see #startElement
605 */
606 public void writeElement (
607 String uri,
608 String localName,
609 String qName,
610 Attributes atts,
611 String content
612 ) throws SAXException
613 {
614 if (content == null || content.length () == 0) {
615 writeEmptyElement (uri, localName, qName, atts);
616 return;
617 }
618 startElement (uri, localName, qName, atts);
619 char chars [] = content.toCharArray ();
620 characters (chars, 0, chars.length);
621 endElement (uri, localName, qName);
622 }
623
624
625 /**
626 * Writes an element that has content consisting of a single integer,
627 * encoded as a decimal string.
628 * @see #writeEmptyElement
629 * @see #startElement
630 */
631 public void writeElement (
632 String uri,
633 String localName,
634 String qName,
635 Attributes atts,
636 int content
637 ) throws SAXException
638 {
639 writeElement (uri, localName, qName, atts, Integer.toString (content));
640 }
641
642
643 // SAX1 ContentHandler
644 /** <b>SAX1</b>: provides parser status information */
645 final public void setDocumentLocator (Locator l)
646 {
647 locator = l;
648 }
649
650
651 // URL for dtd that validates against all normal HTML constructs
652 private static final String xhtmlFullDTD =
653 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";
654
655
656 /**
657 * <b>SAX1</b>: indicates the beginning of a document parse.
658 * If you're writing (well formed) fragments of XML, neither
659 * this nor endDocument should be called.
660 */
661 // NOT final
662 public void startDocument ()
663 throws SAXException
664 {
665 try {
666 if (out == null)
667 throw new IllegalStateException (
668 "null Writer given to XMLWriter");
669
670 // Not all parsers provide the locator we want; this also
671 // flags whether events are being sent to this object yet.
672 // We could only have this one call if we only printed whole
673 // documents ... but we also print fragments, so most of the
674 // callbacks here replicate this test.
675
676 if (locator == null)
677 locator = new LocatorImpl ();
678
679 // Unless the data is in US-ASCII or we're canonicalizing, write
680 // the XML declaration if we know the encoding. US-ASCII won't
681 // normally get mangled by web server confusion about the
682 // character encodings used. Plus, it's an easy way to
683 // ensure we can write ASCII that's unlikely to confuse
684 // elderly HTML parsers.
685
686 if (!canonical
687 && dangerMask != (short) 0xff80
688 && encoding != null) {
689 rawWrite ("<?xml version='1.0'");
690 rawWrite (" encoding='" + encoding + "'");
691 rawWrite ("?>");
692 newline ();
693 }
694
695 if (xhtml) {
696
697 rawWrite ("<!DOCTYPE html PUBLIC");
698 newline ();
699 rawWrite (" '-//W3C//DTD XHTML 1.0 Transitional//EN'");
700 newline ();
701 rawWrite (" '");
702 // NOTE: URL (above) matches the REC
703 rawWrite (xhtmlFullDTD);
704 rawWrite ("'>");
705 newline ();
706 newline ();
707
708 // fake the rest of the handler into ignoring
709 // everything until the root element, so any
710 // XHTML DTD comments, PIs, etc are ignored
711 startedDoctype = true;
712 }
713
714 entityNestLevel = 0;
715
716 } catch (IOException e) {
717 fatal ("can't write", e);
718 }
719 }
720
721 /**
722 * <b>SAX1</b>: indicates the completion of a parse.
723 * Note that all complete SAX event streams make this call, even
724 * if an error is reported during a parse.
725 */
726 // NOT final
727 public void endDocument ()
728 throws SAXException
729 {
730 try {
731 if (!canonical) {
732 newline ();
733 newline ();
734 }
735 out.close ();
736 out = null;
737 locator = null;
738 } catch (IOException e) {
739 fatal ("can't write", e);
740 }
741 }
742
743 // XHTML elements declared as EMPTY print differently
744 final private static boolean isEmptyElementTag (String tag)
745 {
746 switch (tag.charAt (0)) {
747 case 'a': return "area".equals (tag);
748 case 'b': return "base".equals (tag)
749 || "basefont".equals (tag)
750 || "br".equals (tag);
751 case 'c': return "col".equals (tag);
752 case 'f': return "frame".equals (tag);
753 case 'h': return "hr".equals (tag);
754 case 'i': return "img".equals (tag)
755 || "input".equals (tag)
756 || "isindex".equals (tag);
757 case 'l': return "link".equals (tag);
758 case 'm': return "meta".equals (tag);
759 case 'p': return "param".equals (tag);
760 }
761 return false;
762 }
763
764 private static boolean indentBefore (String tag)
765 {
766 // basically indent before block content
767 // and within structure like tables, lists
768 switch (tag.charAt (0)) {
769 case 'a': return "applet".equals (tag);
770 case 'b': return "body".equals (tag)
771 || "blockquote".equals (tag);
772 case 'c': return "center".equals (tag);
773 case 'f': return "frame".equals (tag)
774 || "frameset".equals (tag);
775 case 'h': return "head".equals (tag);
776 case 'm': return "meta".equals (tag);
777 case 'o': return "object".equals (tag);
778 case 'p': return "param".equals (tag)
779 || "pre".equals (tag);
780 case 's': return "style".equals (tag);
781 case 't': return "title".equals (tag)
782 || "td".equals (tag)
783 || "th".equals (tag);
784 }
785 // ... but not inline elements like "em", "b", "font"
786 return false;
787 }
788
789 private static boolean spaceBefore (String tag)
790 {
791 // blank line AND INDENT before certain structural content
792 switch (tag.charAt (0)) {
793 case 'h': return "h1".equals (tag)
794 || "h2".equals (tag)
795 || "h3".equals (tag)
796 || "h4".equals (tag)
797 || "h5".equals (tag)
798 || "h6".equals (tag)
799 || "hr".equals (tag);
800 case 'l': return "li".equals (tag);
801 case 'o': return "ol".equals (tag);
802 case 'p': return "p".equals (tag);
803 case 't': return "table".equals (tag)
804 || "tr".equals (tag);
805 case 'u': return "ul".equals (tag);
806 }
807 return false;
808 }
809
810 // XHTML DTDs say these three have xml:space="preserve"
811 private static boolean spacePreserve (String tag)
812 {
813 return "pre".equals (tag)
814 || "style".equals (tag)
815 || "script".equals (tag);
816 }
817
818 /**
819 * <b>SAX2</b>: ignored.
820 */
821 final public void startPrefixMapping (String prefix, String uri)
822 {}
823
824 /**
825 * <b>SAX2</b>: ignored.
826 */
827 final public void endPrefixMapping (String prefix)
828 {}
829
830 private void writeStartTag (
831 String name,
832 Attributes atts,
833 boolean isEmpty
834 ) throws SAXException, IOException
835 {
836 rawWrite ('<');
837 rawWrite (name);
838
839 // write out attributes ... sorting is particularly useful
840 // with output that's been heavily defaulted.
841 if (atts != null && atts.getLength () != 0) {
842
843 // Set up to write, with optional sorting
844 int indices [] = new int [atts.getLength ()];
845
846 for (int i= 0; i < indices.length; i++)
847 indices [i] = i;
848
849 // optionally sort
850
851 // FIXME: canon xml demands xmlns nodes go first,
852 // and sorting by URI first (empty first) then localname
853 // it should maybe use a different sort
854
855 if (canonical || prettyPrinting) {
856
857 // insertion sort by attribute name
858 for (int i = 1; i < indices.length; i++) {
859 int n = indices [i], j;
860 String s = atts.getQName (n);
861
862 for (j = i - 1; j >= 0; j--) {
863 if (s.compareTo (atts.getQName (indices [j]))
864 >= 0)
865 break;
866 indices [j + 1] = indices [j];
867 }
868 indices [j + 1] = n;
869 }
870 }
871
872 // write, sorted or no
873 for (int i= 0; i < indices.length; i++) {
874 String s = atts.getQName (indices [i]);
875
876 if (s == null || "".equals (s))
877 throw new IllegalArgumentException ("no XML name");
878 rawWrite (" ");
879 rawWrite (s);
880 rawWrite ("=");
881 writeQuotedValue (atts.getValue (indices [i]),
882 CTX_ATTRIBUTE);
883 }
884 }
885 if (isEmpty)
886 rawWrite (" /");
887 rawWrite ('>');
888 }
889
890 /**
891 * <b>SAX2</b>: indicates the start of an element.
892 * When XHTML is in use, avoid attribute values with
893 * line breaks or multiple whitespace characters, since
894 * not all user agents handle them correctly.
895 */
896 final public void startElement (
897 String uri,
898 String localName,
899 String qName,
900 Attributes atts
901 ) throws SAXException
902 {
903 startedDoctype = false;
904
905 if (locator == null)
906 locator = new LocatorImpl ();
907
908 if (qName == null || "".equals (qName))
909 throw new IllegalArgumentException ("no XML name");
910
911 try {
912 if (entityNestLevel != 0)
913 return;
914 if (prettyPrinting) {
915 String whitespace = null;
916
917 if (xhtml && spacePreserve (qName))
918 whitespace = "preserve";
919 else if (atts != null)
920 whitespace = atts.getValue ("xml:space");
921 if (whitespace == null)
922 whitespace = (String) space.peek ();
923 space.push (whitespace);
924
925 if ("default".equals (whitespace)) {
926 if (xhtml) {
927 if (spaceBefore (qName)) {
928 newline ();
929 doIndent ();
930 } else if (indentBefore (qName))
931 doIndent ();
932 // else it's inlined, modulo line length
933 // FIXME: incrementing element nest level
934 // for inlined elements causes ugliness
935 } else
936 doIndent ();
937 }
938 }
939 elementNestLevel++;
940 writeStartTag (qName, atts, xhtml && isEmptyElementTag (qName));
941
942 if (xhtml) {
943 // FIXME: if this is an XHTML "pre" element, turn
944 // off automatic wrapping.
945 }
946
947 } catch (IOException e) {
948 fatal ("can't write", e);
949 }
950 }
951
952 /**
953 * Writes an empty element.
954 * @see #startElement
955 */
956 public void writeEmptyElement (
957 String uri,
958 String localName,
959 String qName,
960 Attributes atts
961 ) throws SAXException
962 {
963 if (canonical) {
964 startElement (uri, localName, qName, atts);
965 endElement (uri, localName, qName);
966 } else {
967 try {
968 writeStartTag (qName, atts, true);
969 } catch (IOException e) {
970 fatal ("can't write", e);
971 }
972 }
973 }
974
975
976 /** <b>SAX2</b>: indicates the end of an element */
977 final public void endElement (String uri, String localName, String qName)
978 throws SAXException
979 {
980 if (qName == null || "".equals (qName))
981 throw new IllegalArgumentException ("no XML name");
982
983 try {
984 elementNestLevel--;
985 if (entityNestLevel != 0)
986 return;
987 if (xhtml && isEmptyElementTag (qName))
988 return;
989 rawWrite ("</");
990 rawWrite (qName);
991 rawWrite ('>');
992
993 if (prettyPrinting) {
994 if (!space.empty ())
995 space.pop ();
996 else
997 fatal ("stack discipline", null);
998 }
999 if (elementNestLevel == 0)
1000 inEpilogue = true;
1001
1002 } catch (IOException e) {
1003 fatal ("can't write", e);
1004 }
1005 }
1006
1007 /** <b>SAX1</b>: reports content characters */
1008 final public void characters (char ch [], int start, int length)
1009 throws SAXException
1010 {
1011 if (locator == null)
1012 locator = new LocatorImpl ();
1013
1014 try {
1015 if (entityNestLevel != 0)
1016 return;
1017 if (inCDATA) {
1018 escapeChars (ch, start, length, CTX_UNPARSED);
1019 } else {
1020 escapeChars (ch, start, length, CTX_CONTENT);
1021 }
1022 } catch (IOException e) {
1023 fatal ("can't write", e);
1024 }
1025 }
1026
1027 /** <b>SAX1</b>: reports ignorable whitespace */
1028 final public void ignorableWhitespace (char ch [], int start, int length)
1029 throws SAXException
1030 {
1031 if (locator == null)
1032 locator = new LocatorImpl ();
1033
1034 try {
1035 if (entityNestLevel != 0)
1036 return;
1037 // don't forget to map NL to CRLF, CR, etc
1038 escapeChars (ch, start, length, CTX_CONTENT);
1039 } catch (IOException e) {
1040 fatal ("can't write", e);
1041 }
1042 }
1043
1044 /**
1045 * <b>SAX1</b>: reports a PI.
1046 * This doesn't check for illegal target names, such as "xml" or "XML",
1047 * or namespace-incompatible ones like "big:dog"; the caller is
1048 * responsible for ensuring those names are legal.
1049 */
1050 final public void processingInstruction (String target, String data)
1051 throws SAXException
1052 {
1053 if (locator == null)
1054 locator = new LocatorImpl ();
1055
1056 // don't print internal subset for XHTML
1057 if (xhtml && startedDoctype)
1058 return;
1059
1060 // ancient HTML browsers might render these ... their loss.
1061 // to prevent: "if (xhtml) return;".
1062
1063 try {
1064 if (entityNestLevel != 0)
1065 return;
1066 if (canonical && inEpilogue)
1067 newline ();
1068 rawWrite ("<?");
1069 rawWrite (target);
1070 rawWrite (' ');
1071 escapeChars (data.toCharArray (), -1, -1, CTX_UNPARSED);
1072 rawWrite ("?>");
1073 if (elementNestLevel == 0 && !(canonical && inEpilogue))
1074 newline ();
1075 } catch (IOException e) {
1076 fatal ("can't write", e);
1077 }
1078 }
1079
1080 /** <b>SAX1</b>: indicates a non-expanded entity reference */
1081 public void skippedEntity (String name)
1082 throws SAXException
1083 {
1084 try {
1085 rawWrite ("&");
1086 rawWrite (name);
1087 rawWrite (";");
1088 } catch (IOException e) {
1089 fatal ("can't write", e);
1090 }
1091 }
1092
1093 // SAX2 LexicalHandler
1094
1095 /** <b>SAX2</b>: called before parsing CDATA characters */
1096 final public void startCDATA ()
1097 throws SAXException
1098 {
1099 if (locator == null)
1100 locator = new LocatorImpl ();
1101
1102 if (canonical)
1103 return;
1104
1105 try {
1106 inCDATA = true;
1107 if (entityNestLevel == 0)
1108 rawWrite ("<![CDATA[");
1109 } catch (IOException e) {
1110 fatal ("can't write", e);
1111 }
1112 }
1113
1114 /** <b>SAX2</b>: called after parsing CDATA characters */
1115 final public void endCDATA ()
1116 throws SAXException
1117 {
1118 if (canonical)
1119 return;
1120
1121 try {
1122 inCDATA = false;
1123 if (entityNestLevel == 0)
1124 rawWrite ("]]>");
1125 } catch (IOException e) {
1126 fatal ("can't write", e);
1127 }
1128 }
1129
1130 /**
1131 * <b>SAX2</b>: called when the doctype is partially parsed
1132 * Note that this, like other doctype related calls, is ignored
1133 * when XHTML is in use.
1134 */
1135 final public void startDTD (String name, String publicId, String systemId)
1136 throws SAXException
1137 {
1138 if (locator == null)
1139 locator = new LocatorImpl ();
1140 if (xhtml)
1141 return;
1142 try {
1143 inDoctype = startedDoctype = true;
1144 if (canonical)
1145 return;
1146 rawWrite ("<!DOCTYPE ");
1147 rawWrite (name);
1148 rawWrite (' ');
1149
1150 if (!expandingEntities) {
1151 if (publicId != null)
1152 rawWrite ("PUBLIC '" + publicId + "' '" + systemId + "' ");
1153 else if (systemId != null)
1154 rawWrite ("SYSTEM '" + systemId + "' ");
1155 }
1156
1157 rawWrite ('[');
1158 newline ();
1159 } catch (IOException e) {
1160 fatal ("can't write", e);
1161 }
1162 }
1163
1164 /** <b>SAX2</b>: called after the doctype is parsed */
1165 final public void endDTD ()
1166 throws SAXException
1167 {
1168 inDoctype = false;
1169 if (canonical || xhtml)
1170 return;
1171 try {
1172 rawWrite ("]>");
1173 newline ();
1174 } catch (IOException e) {
1175 fatal ("can't write", e);
1176 }
1177 }
1178
1179 /**
1180 * <b>SAX2</b>: called before parsing a general entity in content
1181 */
1182 final public void startEntity (String name)
1183 throws SAXException
1184 {
1185 try {
1186 boolean writeEOL = true;
1187
1188 // Predefined XHTML entities (for characters) will get
1189 // mapped back later.
1190 if (xhtml || expandingEntities)
1191 return;
1192
1193 entityNestLevel++;
1194 if (name.equals ("[dtd]"))
1195 return;
1196 if (entityNestLevel != 1)
1197 return;
1198 if (!name.startsWith ("%")) {
1199 writeEOL = false;
1200 rawWrite ('&');
1201 }
1202 rawWrite (name);
1203 rawWrite (';');
1204 if (writeEOL)
1205 newline ();
1206 } catch (IOException e) {
1207 fatal ("can't write", e);
1208 }
1209 }
1210
1211 /**
1212 * <b>SAX2</b>: called after parsing a general entity in content
1213 */
1214 final public void endEntity (String name)
1215 throws SAXException
1216 {
1217 if (xhtml || expandingEntities)
1218 return;
1219 entityNestLevel--;
1220 }
1221
1222 /**
1223 * <b>SAX2</b>: called when comments are parsed.
1224 * When XHTML is used, the old HTML tradition of using comments
1225 * to for inline CSS, or for JavaScript code is discouraged.
1226 * This is because XML processors are encouraged to discard, on
1227 * the grounds that comments are for users (and perhaps text
1228 * editors) not programs. Instead, use external scripts
1229 */
1230 final public void comment (char ch [], int start, int length)
1231 throws SAXException
1232 {
1233 if (locator == null)
1234 locator = new LocatorImpl ();
1235
1236 // don't print internal subset for XHTML
1237 if (xhtml && startedDoctype)
1238 return;
1239 // don't print comment in doctype for canon xml
1240 if (canonical && inDoctype)
1241 return;
1242
1243 try {
1244 boolean indent;
1245
1246 if (prettyPrinting && space.empty ())
1247 fatal ("stack discipline", null);
1248 indent = prettyPrinting && "default".equals (space.peek ());
1249 if (entityNestLevel != 0)
1250 return;
1251 if (indent)
1252 doIndent ();
1253 if (canonical && inEpilogue)
1254 newline ();
1255 rawWrite ("<!--");
1256 escapeChars (ch, start, length, CTX_UNPARSED);
1257 rawWrite ("-->");
1258 if (indent)
1259 doIndent ();
1260 if (elementNestLevel == 0 && !(canonical && inEpilogue))
1261 newline ();
1262 } catch (IOException e) {
1263 fatal ("can't write", e);
1264 }
1265 }
1266
1267 // SAX1 DTDHandler
1268
1269 /** <b>SAX1</b>: called on notation declarations */
1270 final public void notationDecl (String name,
1271 String publicId, String systemId)
1272 throws SAXException
1273 {
1274 if (xhtml)
1275 return;
1276 try {
1277 // At this time, only SAX2 callbacks start these.
1278 if (!startedDoctype)
1279 return;
1280
1281 if (entityNestLevel != 0)
1282 return;
1283 rawWrite ("<!NOTATION " + name + " ");
1284 if (publicId != null)
1285 rawWrite ("PUBLIC \"" + publicId + '"');
1286 else
1287 rawWrite ("SYSTEM ");
1288 if (systemId != null)
1289 rawWrite ('"' + systemId + '"');
1290 rawWrite (">");
1291 newline ();
1292 } catch (IOException e) {
1293 fatal ("can't write", e);
1294 }
1295 }
1296
1297 /** <b>SAX1</b>: called on unparsed entity declarations */
1298 final public void unparsedEntityDecl (String name,
1299 String publicId, String systemId,
1300 String notationName)
1301 throws SAXException
1302 {
1303 if (xhtml)
1304 return;
1305 try {
1306 // At this time, only SAX2 callbacks start these.
1307 if (!startedDoctype) {
1308 // FIXME: write to temporary buffer, and make the start
1309 // of the root element write these declarations.
1310 return;
1311 }
1312
1313 if (entityNestLevel != 0)
1314 return;
1315 rawWrite ("<!ENTITY " + name + " ");
1316 if (publicId != null)
1317 rawWrite ("PUBLIC \"" + publicId + '"');
1318 else
1319 rawWrite ("SYSTEM ");
1320 rawWrite ('"' + systemId + '"');
1321 rawWrite (" NDATA " + notationName + ">");
1322 newline ();
1323 } catch (IOException e) {
1324 fatal ("can't write", e);
1325 }
1326 }
1327
1328 // SAX2 DeclHandler
1329
1330 /** <b>SAX2</b>: called on attribute declarations */
1331 final public void attributeDecl (String eName, String aName,
1332 String type, String mode, String value)
1333 throws SAXException
1334 {
1335 if (xhtml)
1336 return;
1337 try {
1338 // At this time, only SAX2 callbacks start these.
1339 if (!startedDoctype)
1340 return;
1341 if (entityNestLevel != 0)
1342 return;
1343 rawWrite ("<!ATTLIST " + eName + ' ' + aName + ' ');
1344 rawWrite (type);
1345 rawWrite (' ');
1346 if (mode != null)
1347 rawWrite (mode + ' ');
1348 if (value != null)
1349 writeQuotedValue (value, CTX_ATTRIBUTE);
1350 rawWrite ('>');
1351 newline ();
1352 } catch (IOException e) {
1353 fatal ("can't write", e);
1354 }
1355 }
1356
1357 /** <b>SAX2</b>: called on element declarations */
1358 final public void elementDecl (String name, String model)
1359 throws SAXException
1360 {
1361 if (xhtml)
1362 return;
1363 try {
1364 // At this time, only SAX2 callbacks start these.
1365 if (!startedDoctype)
1366 return;
1367 if (entityNestLevel != 0)
1368 return;
1369 rawWrite ("<!ELEMENT " + name + ' ' + model + '>');
1370 newline ();
1371 } catch (IOException e) {
1372 fatal ("can't write", e);
1373 }
1374 }
1375
1376 /** <b>SAX2</b>: called on external entity declarations */
1377 final public void externalEntityDecl (
1378 String name,
1379 String publicId,
1380 String systemId)
1381 throws SAXException
1382 {
1383 if (xhtml)
1384 return;
1385 try {
1386 // At this time, only SAX2 callbacks start these.
1387 if (!startedDoctype)
1388 return;
1389 if (entityNestLevel != 0)
1390 return;
1391 rawWrite ("<!ENTITY ");
1392 if (name.startsWith ("%")) {
1393 rawWrite ("% ");
1394 rawWrite (name.substring (1));
1395 } else
1396 rawWrite (name);
1397 if (publicId != null)
1398 rawWrite (" PUBLIC \"" + publicId + '"');
1399 else
1400 rawWrite (" SYSTEM ");
1401 rawWrite ('"' + systemId + "\">");
1402 newline ();
1403 } catch (IOException e) {
1404 fatal ("can't write", e);
1405 }
1406 }
1407
1408 /** <b>SAX2</b>: called on internal entity declarations */
1409 final public void internalEntityDecl (String name, String value)
1410 throws SAXException
1411 {
1412 if (xhtml)
1413 return;
1414 try {
1415 // At this time, only SAX2 callbacks start these.
1416 if (!startedDoctype)
1417 return;
1418 if (entityNestLevel != 0)
1419 return;
1420 rawWrite ("<!ENTITY ");
1421 if (name.startsWith ("%")) {
1422 rawWrite ("% ");
1423 rawWrite (name.substring (1));
1424 } else
1425 rawWrite (name);
1426 rawWrite (' ');
1427 writeQuotedValue (value, CTX_ENTITY);
1428 rawWrite ('>');
1429 newline ();
1430 } catch (IOException e) {
1431 fatal ("can't write", e);
1432 }
1433 }
1434
1435 private void writeQuotedValue (String value, int code)
1436 throws SAXException, IOException
1437 {
1438 char buf [] = value.toCharArray ();
1439 int off = 0, len = buf.length;
1440
1441 // we can't add line breaks to attribute/entity/... values
1442 noWrap = true;
1443 rawWrite ('"');
1444 escapeChars (buf, off, len, code);
1445 rawWrite ('"');
1446 noWrap = false;
1447 }
1448
1449 // From "HTMLlat1x.ent" ... names of entities for ISO-8859-1
1450 // (Latin/1) characters, all codes: 160-255 (0xA0-0xFF).
1451 // Codes 128-159 have no assigned values.
1452 private static final String HTMLlat1x [] = {
1453 // 160
1454 "nbsp", "iexcl", "cent", "pound", "curren",
1455 "yen", "brvbar", "sect", "uml", "copy",
1456
1457 // 170
1458 "ordf", "laquo", "not", "shy", "reg",
1459 "macr", "deg", "plusmn", "sup2", "sup3",
1460
1461 // 180
1462 "acute", "micro", "para", "middot", "cedil",
1463 "sup1", "ordm", "raquo", "frac14", "frac12",
1464
1465 // 190
1466 "frac34", "iquest", "Agrave", "Aacute", "Acirc",
1467 "Atilde", "Auml", "Aring", "AElig", "Ccedil",
1468
1469 // 200
1470 "Egrave", "Eacute", "Ecirc", "Euml", "Igrave",
1471 "Iacute", "Icirc", "Iuml", "ETH", "Ntilde",
1472
1473 // 210
1474 "Ograve", "Oacute", "Ocirc", "Otilde", "Ouml",
1475 "times", "Oslash", "Ugrave", "Uacute", "Ucirc",
1476
1477 // 220
1478 "Uuml", "Yacute", "THORN", "szlig", "agrave",
1479 "aacute", "acirc", "atilde", "auml", "aring",
1480
1481 // 230
1482 "aelig", "ccedil", "egrave", "eacute", "ecirc",
1483 "euml", "igrave", "iacute", "icirc", "iuml",
1484
1485 // 240
1486 "eth", "ntilde", "ograve", "oacute", "ocirc",
1487 "otilde", "ouml", "divide", "oslash", "ugrave",
1488
1489 // 250
1490 "uacute", "ucirc", "uuml", "yacute", "thorn",
1491 "yuml"
1492 };
1493
1494 // From "HTMLsymbolx.ent" ... some of the symbols that
1495 // we can conveniently handle. Entities for the Greek.
1496 // alphabet (upper and lower cases) are compact.
1497 private static final String HTMLsymbolx_GR [] = {
1498 // 913
1499 "Alpha", "Beta", "Gamma", "Delta", "Epsilon",
1500 "Zeta", "Eta", "Theta", "Iota", "Kappa",
1501
1502 // 923
1503 "Lambda", "Mu", "Nu", "Xi", "Omicron",
1504 "Pi", "Rho", null, "Sigma", "Tau",
1505
1506 // 933
1507 "Upsilon", "Phi", "Chi", "Psi", "Omega"
1508 };
1509
1510 private static final String HTMLsymbolx_gr [] = {
1511 // 945
1512 "alpha", "beta", "gamma", "delta", "epsilon",
1513 "zeta", "eta", "theta", "iota", "kappa",
1514
1515 // 955
1516 "lambda", "mu", "nu", "xi", "omicron",
1517 "pi", "rho", "sigmaf", "sigma", "tau",
1518
1519 // 965
1520 "upsilon", "phi", "chi", "psi", "omega"
1521 };
1522
1523
1524 // General routine to write text and substitute predefined
1525 // entities (XML, and a special case for XHTML) as needed.
1526 private void escapeChars (char buf [], int off, int len, int code)
1527 throws SAXException, IOException
1528 {
1529 int first = 0;
1530
1531 if (off < 0) {
1532 off = 0;
1533 len = buf.length;
1534 }
1535 for (int i = 0; i < len; i++) {
1536 String esc;
1537 char c = buf [off + i];
1538
1539 switch (c) {
1540 // Note that CTX_ATTRIBUTE isn't explicitly tested here;
1541 // all syntax delimiters are escaped in CTX_ATTRIBUTE,
1542 // otherwise it's similar to CTX_CONTENT
1543
1544 // ampersand flags entity references; entity replacement
1545 // text has unexpanded references, other text doesn't.
1546 case '&':
1547 if (code == CTX_ENTITY || code == CTX_UNPARSED)
1548 continue;
1549 esc = "amp";
1550 break;
1551
1552 // attributes and text may NOT have literal '<', but
1553 // entities may have markup constructs
1554 case '<':
1555 if (code == CTX_ENTITY || code == CTX_UNPARSED)
1556 continue;
1557 esc = "lt";
1558 break;
1559
1560 // as above re markup constructs; but otherwise
1561 // except when canonicalizing, this is for consistency
1562 case '>':
1563 if (code == CTX_ENTITY || code == CTX_UNPARSED)
1564 continue;
1565 esc = "gt";
1566 break;
1567 case '\'':
1568 if (code == CTX_CONTENT || code == CTX_UNPARSED)
1569 continue;
1570 if (canonical)
1571 continue;
1572 esc = "apos";
1573 break;
1574
1575 // needed when printing quoted attribute/entity values
1576 case '"':
1577 if (code == CTX_CONTENT || code == CTX_UNPARSED)
1578 continue;
1579 esc = "quot";
1580 break;
1581
1582 // make line ends work per host OS convention
1583 case '\n':
1584 esc = eol;
1585 break;
1586
1587 //
1588 // No other characters NEED special treatment ... except
1589 // for encoding-specific issues, like whether the character
1590 // can really be represented in that encoding.
1591 //
1592 default:
1593 //
1594 // There are characters we can never write safely; getting
1595 // them is an error.
1596 //
1597 // (a) They're never legal in XML ... detected by range
1598 // checks, and (eventually) by remerging surrogate
1599 // pairs on output. (Easy error for apps to prevent.)
1600 //
1601 // (b) This encoding can't represent them, and we
1602 // can't make reference substitution (e.g. inside
1603 // CDATA sections, names, PI data, etc). (Hard for
1604 // apps to prevent, except by using UTF-8 or UTF-16
1605 // as their output encoding.)
1606 //
1607 // We know a very little bit about what characters
1608 // the US-ASCII and ISO-8859-1 encodings support. For
1609 // other encodings we can't detect the second type of
1610 // error at all. (Never an issue for UTF-8 or UTF-16.)
1611 //
1612
1613 // FIXME: CR in CDATA is an error; in text, turn to a char ref
1614
1615 // FIXME: CR/LF/TAB in attributes should become char refs
1616
1617 if ((c > 0xfffd)
1618 || ((c < 0x0020) && !((c == 0x0009)
1619 || (c == 0x000A) || (c == 0x000D)))
1620 || (((c & dangerMask) != 0)
1621 && (code == CTX_UNPARSED))) {
1622
1623 // if case (b) in CDATA, we might end the section,
1624 // write a reference, then restart ... possible
1625 // in one DOM L3 draft.
1626
1627 throw new CharConversionException (
1628 "Illegal or non-writable character: U+"
1629 + Integer.toHexString (c));
1630 }
1631
1632 //
1633 // If the output encoding represents the character
1634 // directly, let it do so! Else we'll escape it.
1635 //
1636 if ((c & dangerMask) == 0)
1637 continue;
1638 esc = null;
1639
1640 // Avoid numeric refs where symbolic ones exist, as
1641 // symbolic ones make more sense to humans reading!
1642 if (xhtml) {
1643 // all the HTMLlat1x.ent entities
1644 // (all the "ISO-8859-1" characters)
1645 if (c >= 160 && c <= 255)
1646 esc = HTMLlat1x [c - 160];
1647
1648 // not quite half the HTMLsymbolx.ent entities
1649 else if (c >= 913 && c <= 937)
1650 esc = HTMLsymbolx_GR [c - 913];
1651 else if (c >= 945 && c <= 969)
1652 esc = HTMLsymbolx_gr [c - 945];
1653
1654 else switch (c) {
1655 // all of the HTMLspecialx.ent entities
1656 case 338: esc = "OElig"; break;
1657 case 339: esc = "oelig"; break;
1658 case 352: esc = "Scaron"; break;
1659 case 353: esc = "scaron"; break;
1660 case 376: esc = "Yuml"; break;
1661 case 710: esc = "circ"; break;
1662 case 732: esc = "tilde"; break;
1663 case 8194: esc = "ensp"; break;
1664 case 8195: esc = "emsp"; break;
1665 case 8201: esc = "thinsp"; break;
1666 case 8204: esc = "zwnj"; break;
1667 case 8205: esc = "zwj"; break;
1668 case 8206: esc = "lrm"; break;
1669 case 8207: esc = "rlm"; break;
1670 case 8211: esc = "ndash"; break;
1671 case 8212: esc = "mdash"; break;
1672 case 8216: esc = "lsquo"; break;
1673 case 8217: esc = "rsquo"; break;
1674 case 8218: esc = "sbquo"; break;
1675 case 8220: esc = "ldquo"; break;
1676 case 8221: esc = "rdquo"; break;
1677 case 8222: esc = "bdquo"; break;
1678 case 8224: esc = "dagger"; break;
1679 case 8225: esc = "Dagger"; break;
1680 case 8240: esc = "permil"; break;
1681 case 8249: esc = "lsaquo"; break;
1682 case 8250: esc = "rsaquo"; break;
1683 case 8364: esc = "euro"; break;
1684
1685 // the other HTMLsymbox.ent entities
1686 case 402: esc = "fnof"; break;
1687 case 977: esc = "thetasym"; break;
1688 case 978: esc = "upsih"; break;
1689 case 982: esc = "piv"; break;
1690 case 8226: esc = "bull"; break;
1691 case 8230: esc = "hellip"; break;
1692 case 8242: esc = "prime"; break;
1693 case 8243: esc = "Prime"; break;
1694 case 8254: esc = "oline"; break;
1695 case 8260: esc = "frasl"; break;
1696 case 8472: esc = "weierp"; break;
1697 case 8465: esc = "image"; break;
1698 case 8476: esc = "real"; break;
1699 case 8482: esc = "trade"; break;
1700 case 8501: esc = "alefsym"; break;
1701 case 8592: esc = "larr"; break;
1702 case 8593: esc = "uarr"; break;
1703 case 8594: esc = "rarr"; break;
1704 case 8595: esc = "darr"; break;
1705 case 8596: esc = "harr"; break;
1706 case 8629: esc = "crarr"; break;
1707 case 8656: esc = "lArr"; break;
1708 case 8657: esc = "uArr"; break;
1709 case 8658: esc = "rArr"; break;
1710 case 8659: esc = "dArr"; break;
1711 case 8660: esc = "hArr"; break;
1712 case 8704: esc = "forall"; break;
1713 case 8706: esc = "part"; break;
1714 case 8707: esc = "exist"; break;
1715 case 8709: esc = "empty"; break;
1716 case 8711: esc = "nabla"; break;
1717 case 8712: esc = "isin"; break;
1718 case 8713: esc = "notin"; break;
1719 case 8715: esc = "ni"; break;
1720 case 8719: esc = "prod"; break;
1721 case 8721: esc = "sum"; break;
1722 case 8722: esc = "minus"; break;
1723 case 8727: esc = "lowast"; break;
1724 case 8730: esc = "radic"; break;
1725 case 8733: esc = "prop"; break;
1726 case 8734: esc = "infin"; break;
1727 case 8736: esc = "ang"; break;
1728 case 8743: esc = "and"; break;
1729 case 8744: esc = "or"; break;
1730 case 8745: esc = "cap"; break;
1731 case 8746: esc = "cup"; break;
1732 case 8747: esc = "int"; break;
1733 case 8756: esc = "there4"; break;
1734 case 8764: esc = "sim"; break;
1735 case 8773: esc = "cong"; break;
1736 case 8776: esc = "asymp"; break;
1737 case 8800: esc = "ne"; break;
1738 case 8801: esc = "equiv"; break;
1739 case 8804: esc = "le"; break;
1740 case 8805: esc = "ge"; break;
1741 case 8834: esc = "sub"; break;
1742 case 8835: esc = "sup"; break;
1743 case 8836: esc = "nsub"; break;
1744 case 8838: esc = "sube"; break;
1745 case 8839: esc = "supe"; break;
1746 case 8853: esc = "oplus"; break;
1747 case 8855: esc = "otimes"; break;
1748 case 8869: esc = "perp"; break;
1749 case 8901: esc = "sdot"; break;
1750 case 8968: esc = "lceil"; break;
1751 case 8969: esc = "rceil"; break;
1752 case 8970: esc = "lfloor"; break;
1753 case 8971: esc = "rfloor"; break;
1754 case 9001: esc = "lang"; break;
1755 case 9002: esc = "rang"; break;
1756 case 9674: esc = "loz"; break;
1757 case 9824: esc = "spades"; break;
1758 case 9827: esc = "clubs"; break;
1759 case 9829: esc = "hearts"; break;
1760 case 9830: esc = "diams"; break;
1761 }
1762 }
1763
1764 // else escape with numeric char refs
1765 if (esc == null) {
1766 stringBuf.setLength (0);
1767 stringBuf.append ("#x");
1768 stringBuf.append (Integer.toHexString (c).toUpperCase ());
1769 esc = stringBuf.toString ();
1770
1771 // FIXME: We don't write surrogate pairs correctly.
1772 // They should work as one ref per character, since
1773 // each pair is one character. For reading back into
1774 // Unicode, it matters beginning in Unicode 3.1 ...
1775 }
1776 break;
1777 }
1778 if (i != first)
1779 rawWrite (buf, off + first, i - first);
1780 first = i + 1;
1781 if (esc == eol)
1782 newline ();
1783 else {
1784 rawWrite ('&');
1785 rawWrite (esc);
1786 rawWrite (';');
1787 }
1788 }
1789 if (first < len)
1790 rawWrite (buf, off + first, len - first);
1791 }
1792
1793
1794
1795 private void newline ()
1796 throws SAXException, IOException
1797 {
1798 out.write (eol);
1799 column = 0;
1800 }
1801
1802 private void doIndent ()
1803 throws SAXException, IOException
1804 {
1805 int space = elementNestLevel * 2;
1806
1807 newline ();
1808 column = space;
1809 // track tabs only at line starts
1810 while (space > 8) {
1811 out.write ("\t");
1812 space -= 8;
1813 }
1814 while (space > 0) {
1815 out.write (" ");
1816 space -= 2;
1817 }
1818 }
1819
1820 private void rawWrite (char c)
1821 throws IOException
1822 {
1823 out.write (c);
1824 column++;
1825 }
1826
1827 private void rawWrite (String s)
1828 throws SAXException, IOException
1829 {
1830 if (prettyPrinting && "default".equals (space.peek ())) {
1831 char data [] = s.toCharArray ();
1832 rawWrite (data, 0, data.length);
1833 } else {
1834 out.write (s);
1835 column += s.length ();
1836 }
1837 }
1838
1839 // NOTE: if xhtml, the REC gives some rules about whitespace
1840 // which we could follow ... notably, many places where conformant
1841 // agents "must" consolidate/normalize whitespace. Line ends can
1842 // be removed there, etc. This may not be the right place to do
1843 // such mappings though.
1844
1845 // Line buffering may help clarify algorithms and improve results.
1846
1847 // It's likely xml:space needs more attention.
1848
1849 private void rawWrite (char buf [], int offset, int length)
1850 throws SAXException, IOException
1851 {
1852 boolean wrap;
1853
1854 if (prettyPrinting && space.empty ())
1855 fatal ("stack discipline", null);
1856
1857 wrap = prettyPrinting && "default".equals (space.peek ());
1858 if (!wrap) {
1859 out.write (buf, offset, length);
1860 column += length;
1861 return;
1862 }
1863
1864 // we're pretty printing and want to fill lines out only
1865 // to the desired line length.
1866 while (length > 0) {
1867 int target = lineLength - column;
1868 boolean wrote = false;
1869
1870 // Do we even have a problem?
1871 if (target > length || noWrap) {
1872 out.write (buf, offset, length);
1873 column += length;
1874 return;
1875 }
1876
1877 // break the line at a space character, trying to fill
1878 // as much of the line as possible.
1879 char c;
1880
1881 for (int i = target - 1; i >= 0; i--) {
1882 if ((c = buf [offset + i]) == ' ' || c == '\t') {
1883 i++;
1884 out.write (buf, offset, i);
1885 doIndent ();
1886 offset += i;
1887 length -= i;
1888 wrote = true;
1889 break;
1890 }
1891 }
1892 if (wrote)
1893 continue;
1894
1895 // no space character permitting break before target
1896 // line length is filled. So, take the next one.
1897 if (target < 0)
1898 target = 0;
1899 for (int i = target; i < length; i++)
1900 if ((c = buf [offset + i]) == ' ' || c == '\t') {
1901 i++;
1902 out.write (buf, offset, i);
1903 doIndent ();
1904 offset += i;
1905 length -= i;
1906 wrote = true;
1907 break;
1908 }
1909 if (wrote)
1910 continue;
1911
1912 // no such luck.
1913 out.write (buf, offset, length);
1914 column += length;
1915 break;
1916 }
1917 }
1918 }

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26