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><br></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><meta http-equiv="Content-type" |
392 |
* content="text/xml;charset=..." /></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: &, <, and ]]> 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><isindex></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 |
} |