View Javadoc

1   /*
2    * $HeadURL: $
3    * $Date: $
4    * $Revision: $
5    * $Author: $
6    * 
7    * Copyright (c) 2005 MindTree Consulting Ltd. 
8    * 
9    * This file is part of Insight.
10   * 
11   * Insight is free software: you can redistribute it and/or modify it under the 
12   * terms of the GNU General Public License as published by the Free Software 
13   * Foundation, either version 3 of the License, or (at your option) any later 
14   * version.
15   * 
16   * Insight is distributed in the hope that it will be useful, but 
17   * WITHOUT ANY WARRANTY; without even the implied warranty of 
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
19   * Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public License along with 
22   * Insight.  If not, see <http://www.gnu.org/licenses/>.
23   */
24  
25  package com.mindtree.techworks.insight.preferences.xmlpersistence;
26  
27  import java.io.OutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.PrintWriter;
30  import java.io.UnsupportedEncodingException;
31  import java.lang.reflect.Method;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Stack;
36  
37  import org.xml.sax.Attributes;
38  import org.xml.sax.Locator;
39  import org.xml.sax.SAXException;
40  import org.xml.sax.SAXParseException;
41  import org.xml.sax.ext.LexicalHandler;
42  import org.xml.sax.helpers.DefaultHandler;
43  import org.xml.sax.helpers.NamespaceSupport;
44  
45  import com.mindtree.techworks.insight.preferences.model.Preference;
46  import com.mindtree.techworks.insight.preferences.model.PreferenceAttribute;
47  
48  
49  /**
50   * This class writes XML data for changed preferences
51   * 
52   * @author Bindul Bhowmik
53   * @version $Revision: 27 $ $Date: 2007-12-16 04:58:03 -0700 (Sun, 16 Dec 2007) $
54   */
55  public class XMLPreferenceDataWriter extends DefaultHandler implements
56  		LexicalHandler, PreferenceXMLNameConstants {
57  
58  	//
59  	// Private Constants
60  	//
61  	/**
62  	 * Seperator between preference and attribute keys
63  	 */
64  	private static final String KEY_SEPERATOR = "_";
65  
66  	/**
67  	 * Encoding of the output stream
68  	 */
69  	private static final String ENCODING = "UTF8";
70  
71  	/**
72  	 * Encoding to be written to the XML file
73  	 */
74  	private static final String ENCODING_XML_STRING = "UTF-8";
75  
76  	/**
77  	 * Marks the start of a Processing Instruction
78  	 */
79  	private static final String PI_START = "<?";
80  
81  	/**
82  	 * Marks the end of a Processing Instruction
83  	 */
84  	private static final String PI_END = "?>";
85  
86  	/**
87  	 * A single space
88  	 */
89  	private static final String SPACE = " ";
90  
91  	/**
92  	 * Start of an element
93  	 */
94  	private static final String ELEMENT_START = "<";
95  
96  	/**
97  	 * End of an element tag
98  	 */
99  	private static final String ELEMENT_END = ">";
100 
101 	/**
102 	 * Start of an element termination tag
103 	 */
104 	private static final String ELEMENT_TERM_START = "</";
105 
106 	/**
107 	 * Start of an attribute value
108 	 */
109 	private static final String ATTRIBUTE_START = "=\"";
110 
111 	/**
112 	 * End of an attribute value
113 	 */
114 	private static final String ATTRIBUTE_END = "\"";
115 
116 	/**
117 	 * Start of a CDATA Section
118 	 */
119 	private static final String CDATA_START = "<![CDATA[";
120 
121 	/**
122 	 * End of a CDATA Section
123 	 */
124 	private static final String CDATA_END = "]]>";
125 
126 	/**
127 	 * Start of a comment
128 	 */
129 	private static final String COMMENT_START = "<!--";
130 
131 	/**
132 	 * End of a comment
133 	 */
134 	private static final String COMMENT_END = "-->";
135 
136 	//
137 	// Instance Variables
138 	//
139 	/**
140 	 * Writer to which the XML is written
141 	 */
142 	private PrintWriter printWriter;
143 
144 	/**
145 	 * Document locator
146 	 */
147 	private Locator documentLocator;
148 
149 	/**
150 	 * Current depth of the element
151 	 */
152 	private int elementDepth;
153 
154 	/**
155 	 * Processing XML1.1 document
156 	 */
157 	private boolean inXML11;
158 
159 	/**
160 	 * In CDATA Section
161 	 */
162 	private boolean inCData;
163 
164 	/**
165 	 * Canonical output
166 	 */
167 	private boolean isCanonical;
168 
169 	/**
170 	 * Current Preference being processed
171 	 */
172 	private String currentCompletePreferenceId;
173 
174 	/**
175 	 * Supports nested preferences
176 	 */
177 	private Stack preferenceIdStack;
178 
179 	/**
180 	 * Current Preference attribute being processed
181 	 */
182 	private String currentPreferenceAttributeId;
183 
184 	/**
185 	 * Current element being read, if option
186 	 */
187 	private String currentElementToReadDataFor;
188 
189 	/**
190 	 * Preference Attributes which need to be saved
191 	 */
192 	private HashMap preferenceAttributesToSave;
193 
194 	/**
195 	 * Used for namespace processing
196 	 */
197 	private NamespaceSupport namespaceSupport;
198 
199 	/**
200 	 * Since character data in an XML may be read in multiple installments, the
201 	 * data should be written only once, for changed values
202 	 */
203 	private boolean isCurrentValueWritten = false;
204 
205 	//
206 	// Constructor
207 	//
208 
209 	/**
210 	 * 
211 	 */
212 	public XMLPreferenceDataWriter () {
213 
214 		isCanonical = false;
215 		preferenceAttributesToSave = new HashMap();
216 		namespaceSupport = new NamespaceSupport();
217 		preferenceIdStack = new Stack();
218 	}
219 
220 	//
221 	// Public Methods
222 	//
223 
224 	/**
225 	 * Sets the output stream for printing.
226 	 * 
227 	 * @param stream The output stream to write to
228 	 * @throws UnsupportedEncodingException If the encoding is not supported
229 	 */
230 	public void setOutput (OutputStream stream)
231 			throws UnsupportedEncodingException {
232 
233 		java.io.Writer writer = new OutputStreamWriter(stream, ENCODING);
234 		printWriter = new PrintWriter(writer);
235 
236 	}
237 
238 	/**
239 	 * Preferences which need be saved
240 	 * 
241 	 * @param preferences Preferences that need to be saved.
242 	 */
243 	public void setPreferencesToSave (List preferences) {
244 
245 		if (null != preferences) {
246 			for (Iterator preferencesItr = preferences.iterator(); preferencesItr
247 					.hasNext();) {
248 				Preference preference = (Preference) preferencesItr.next();
249 				setPreferenceToSave(preference);
250 			}
251 		}
252 	}
253 
254 	/**
255 	 * Handles a preference to save
256 	 * 
257 	 * @param preference
258 	 */
259 	private void setPreferenceToSave (Preference preference) {
260 
261 		String preferenceId = preference.getCompleteId();
262 		// Preference Attributes
263 		for (Iterator preferenceAttributeItr = preference
264 				.iteratePreferenceAttributes(); preferenceAttributeItr
265 				.hasNext();) {
266 			PreferenceAttribute preferenceAttribute = (PreferenceAttribute) preferenceAttributeItr
267 					.next();
268 			if (preferenceAttribute.isPersistant()) {
269 				preferenceAttributesToSave.put(preferenceId + KEY_SEPERATOR
270 						+ preferenceAttribute.getId(), preferenceAttribute
271 						.getValue());
272 			}
273 		}
274 
275 		// Child Preferences
276 		for (Iterator childPreferences = preference.iterateChildPreferences(); null != childPreferences
277 				&& childPreferences.hasNext();) {
278 			Preference childPreference = (Preference) childPreferences.next();
279 			setPreferenceToSave(childPreference);
280 		}
281 	}
282 
283 	//
284 	// Methods from Content Handler
285 	//
286 
287 	/**
288 	 * Sets the document locator
289 	 * 
290 	 * @param documentLocator The locator of the document
291 	 * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
292 	 */
293 	public void setDocumentLocator (Locator documentLocator) {
294 
295 		this.documentLocator = documentLocator;
296 	}
297 
298 	/**
299 	 * Start document.
300 	 * 
301 	 * @see org.xml.sax.ContentHandler#startDocument()
302 	 */
303 	public void startDocument () throws SAXException {
304 
305 		elementDepth = 0;
306 		inXML11 = false;
307 		inCData = false;
308 
309 	}
310 
311 	/**
312 	 * Handle Processing Instructions
313 	 * 
314 	 * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String,
315 	 *      java.lang.String)
316 	 */
317 	public void processingInstruction (String target, String data)
318 			throws SAXException {
319 
320 		if (elementDepth > 0) {
321 			printWriter.print(PI_START);
322 			printWriter.print(target);
323 			if (null != data && data.length() > 0) {
324 				printWriter.print(SPACE);
325 				printWriter.print(data);
326 			}
327 			printWriter.print(PI_END);
328 			printWriter.flush();
329 		}
330 	}
331 
332 	/**
333 	 * Start Element
334 	 * 
335 	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
336 	 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
337 	 */
338 	public void startElement (String uri, String localName, String qName,
339 			Attributes attributes) throws SAXException {
340 
341 		//TODO Process Namespace
342 		//System.out.println(uri + "\t" + localName + "\t" + qName);
343 		boolean writeCurrentNamespaceMapping = false;
344 		String namespacePrefix = null;
345 
346 
347 		// Special Processing for storing preference values
348 		if (XMLNAME_PREFERENCE.equals(localName)) {
349 			startPreference(attributes);
350 		} else if (XMLNAME_PREFERENCE_ATTRIBUTE.equals(localName)) {
351 			startPreferenceAttribute(attributes);
352 		} else if (XMLNAME_PREF_ATT_VALUE.equals(localName)) {
353 			currentElementToReadDataFor = localName;
354 			isCurrentValueWritten = false;
355 		}
356 
357 		// Namespace processing
358 		namespaceSupport.pushContext();
359 		if (null != uri && uri.equals(namespaceSupport.getURI(""))) {
360 			// Default namespace
361 		} else if (null != uri && uri.length() > 0) {
362 			namespacePrefix = namespaceSupport.getPrefix(uri);
363 			if (null == namespacePrefix) {
364 				// Need to add namespace mapping
365 				writeCurrentNamespaceMapping = true;
366 				if (elementDepth == 0) {
367 					// The namespace of the default element is assumed to be the
368 					// default namespace of the document.
369 					namespaceSupport.declarePrefix("", uri);
370 				} else {
371 					// Obtain the prefix
372 					namespacePrefix = uri.substring(uri.lastIndexOf('/') + 1);
373 					int suffixCount = 1;
374 					while (null != namespaceSupport.getURI(namespacePrefix)) {
375 						// To make namespace prefixes unique add a character at
376 						// the end
377 						if (namespacePrefix.endsWith(String
378 								.valueOf(suffixCount))) {
379 							namespacePrefix = namespacePrefix.substring(0,
380 									namespacePrefix.lastIndexOf(String
381 											.valueOf(suffixCount)));
382 							suffixCount++ ;
383 						}
384 						namespacePrefix = namespacePrefix
385 								+ String.valueOf(suffixCount);
386 					}
387 					namespaceSupport.declarePrefix(namespacePrefix, uri);
388 				}
389 			}
390 		}
391 
392 
393 		// Root Element
394 		if (elementDepth == 0) {
395 			if (documentLocator != null) {
396 				inXML11 = "1.1".equals(getVersion());
397 				documentLocator = null;
398 			}
399 
400 			// The XML declaration cannot be printed in startDocument because
401 			// the version reported by the Locator cannot be relied on until
402 			// after
403 			// the XML declaration in the instance document has been read.
404 			if (!isCanonical) {
405 				if (inXML11) {
406 					printWriter.println("<?xml version=\"1.1\" encoding=\""
407 							+ ENCODING_XML_STRING + "\"?>");
408 				} else {
409 					printWriter.println("<?xml version=\"1.0\" encoding=\""
410 							+ ENCODING_XML_STRING + "\"?>");
411 				}
412 				printWriter.flush();
413 			}
414 
415 
416 		}
417 
418 		elementDepth++ ;
419 		printWriter.print(ELEMENT_START);
420 		if (null != namespacePrefix) {
421 			printWriter.print(namespacePrefix);
422 			printWriter.print(":");
423 		}
424 		printWriter.print(localName);
425 
426 		if (writeCurrentNamespaceMapping) {
427 			writeNamespaceMapping(namespacePrefix, uri);
428 		}
429 		if (attributes != null) {
430 			// TODO Fix if required
431 			//attributes = sortAttributes(attributes);
432 			int len = attributes.getLength();
433 			boolean writeAttributeNamespaceMapping = false;
434 			for (int i = 0; i < len; i++ ) {
435 				writeAttributeNamespaceMapping = false;
436 
437 				// Namespace Processing
438 				String attributeURI = attributes.getURI(i);
439 				String attributeNamespacePrefix = null;
440 				if (null != attributeURI
441 						&& attributeURI.equals(namespaceSupport.getURI(""))) {
442 					// Default namespace
443 				} else if (null != attributeURI && attributeURI.length() > 0) {
444 					attributeNamespacePrefix = namespaceSupport
445 							.getPrefix(attributeURI);
446 					if (null == attributeNamespacePrefix) {
447 						// Need to add namespace mapping
448 						writeAttributeNamespaceMapping = true;
449 						// Obtain the prefix
450 						attributeNamespacePrefix = attributeURI
451 								.substring(attributeURI.lastIndexOf('/') + 1);
452 						int suffixCount = 1;
453 						while (null != namespaceSupport
454 								.getURI(attributeNamespacePrefix)) {
455 							// To make namespace prefixes unique add a character
456 							// at
457 							// the end
458 							if (attributeNamespacePrefix.endsWith(String
459 									.valueOf(suffixCount))) {
460 								attributeNamespacePrefix = attributeNamespacePrefix
461 										.substring(0, attributeNamespacePrefix
462 												.lastIndexOf(String
463 														.valueOf(suffixCount)));
464 								suffixCount++ ;
465 							}
466 							attributeNamespacePrefix = attributeNamespacePrefix
467 									+ String.valueOf(suffixCount);
468 						}
469 						namespaceSupport.declarePrefix(
470 								attributeNamespacePrefix, attributeURI);
471 					}
472 				}
473 
474 				if (writeAttributeNamespaceMapping) {
475 					writeNamespaceMapping(attributeNamespacePrefix,
476 							attributeURI);
477 				}
478 				printWriter.print(SPACE);
479 				if (null != attributeNamespacePrefix) {
480 					printWriter.print(attributeNamespacePrefix);
481 					printWriter.print(':');
482 				}
483 				printWriter.print(attributes.getLocalName(i));
484 				printWriter.print(ATTRIBUTE_START);
485 				normalizeAndPrint(attributes.getValue(i), true);
486 				printWriter.print(ATTRIBUTE_END);
487 			}
488 		}
489 		printWriter.print(ELEMENT_END);
490 		printWriter.flush();
491 
492 	}
493 
494 	/**
495 	 * Utility method to write a namespace alias
496 	 * 
497 	 * @param namespacePrefix The Prefix of the namespace
498 	 * @param uri The namespace
499 	 */
500 	private void writeNamespaceMapping (String namespacePrefix, String uri) {
501 
502 		printWriter.print(SPACE);
503 		printWriter.print("xmlns");
504 		if (null != namespacePrefix) {
505 			printWriter.print(":");
506 			printWriter.print(namespacePrefix);
507 		}
508 		printWriter.print(ATTRIBUTE_START);
509 		printWriter.print(uri);
510 		printWriter.print(ATTRIBUTE_END);
511 	}
512 
513 	/**
514 	 * Characters
515 	 * 
516 	 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
517 	 */
518 	public void characters (char [] ch, int start, int length)
519 			throws SAXException {
520 
521 		if (!inCData) {
522 			if (XMLNAME_PREF_ATT_VALUE.equals(currentElementToReadDataFor)) {
523 				String value = getCurrentAttributeChangedValue();
524 				if (null != value) {
525 					if (!isCurrentValueWritten) {
526 						normalizeAndPrint(value, false);
527 						isCurrentValueWritten = true;
528 					}
529 
530 				} else {
531 					normalizeAndPrint(ch, start, length, false);
532 				}
533 				//printWriter.print(currentCompletePreferenceId + "_" +
534 				// currentPreferenceAttributeId);
535 			} else {
536 				normalizeAndPrint(ch, start, length, false);
537 			}
538 		} else {
539 			if (XMLNAME_PREF_ATT_VALUE.equals(currentElementToReadDataFor)) {
540 				String value = getCurrentAttributeChangedValue();
541 				if (null != value) {
542 					if (!isCurrentValueWritten) {
543 						printWriter.print(value);
544 						isCurrentValueWritten = true;
545 					}
546 				} else {
547 					for (int i = 0; i < length; ++i) {
548 						printWriter.print(ch[start + i]);
549 					}
550 				}
551 				//printWriter.print(currentCompletePreferenceId + "_" +
552 				// currentPreferenceAttributeId);
553 			} else {
554 				for (int i = 0; i < length; ++i) {
555 					printWriter.print(ch[start + i]);
556 				}
557 			}
558 		}
559 		printWriter.flush();
560 	}
561 
562 	/**
563 	 * Ignorable Whitespace
564 	 * 
565 	 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
566 	 */
567 	public void ignorableWhitespace (char [] ch, int start, int length)
568 			throws SAXException {
569 
570 		characters(ch, start, length);
571 		printWriter.flush();
572 	}
573 
574 	/**
575 	 * End Element
576 	 * 
577 	 * @see org.xml.sax.ContentHandler#endElement(java.lang.String,
578 	 *      java.lang.String, java.lang.String)
579 	 */
580 	public void endElement (String uri, String localName, String qName)
581 			throws SAXException {
582 
583 		// Special Processing for preferences
584 		if (XMLNAME_PREFERENCE.equals(localName)) {
585 			endPreference();
586 		} else if (XMLNAME_PREFERENCE_ATTRIBUTE.equals(localName)) {
587 			endPreferenceAttribute();
588 		} else if (XMLNAME_PREF_ATT_VALUE.equals(localName)) {
589 			currentElementToReadDataFor = null;
590 		}
591 
592 		printWriter.print(ELEMENT_TERM_START);
593 		// Namespace Processing
594 		namespaceSupport.pushContext();
595 		if (null != uri && uri.equals(namespaceSupport.getURI(""))) {
596 			// Default namespace - no mapping required
597 		} else {
598 			String namespacePrefix = namespaceSupport.getPrefix(uri);
599 			if (null == namespacePrefix) {
600 				// No namespace present
601 			} else {
602 				printWriter.print(namespacePrefix);
603 				printWriter.print(":");
604 			}
605 		}
606 		printWriter.print(localName);
607 		printWriter.print(ELEMENT_END);
608 		printWriter.flush();
609 
610 		elementDepth-- ;
611 		namespaceSupport.popContext();
612 	}
613 
614 	//
615 	// Methods from Error handler
616 	//
617 
618 	/**
619 	 * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
620 	 */
621 	public void error (SAXParseException e) throws SAXException {
622 
623 		System.out.println("Error: " + e.getMessage());
624 	}
625 
626 	/**
627 	 * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
628 	 */
629 	public void fatalError (SAXParseException e) throws SAXException {
630 
631 		System.out.println("Fatal Error: " + e.getMessage());
632 		throw e;
633 	}
634 
635 	/**
636 	 * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
637 	 */
638 	public void warning (SAXParseException e) throws SAXException {
639 
640 		System.out.println("Warning: " + e.getMessage());
641 	}
642 
643 	//
644 	// Methods from Lexical handler
645 	//
646 
647 	/**
648 	 * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String,
649 	 *      java.lang.String, java.lang.String)
650 	 */
651 	public void startDTD (String name, String publicId, String systemId)
652 			throws SAXException {
653 
654 		// Need not implement
655 	}
656 
657 	/**
658 	 * @see org.xml.sax.ext.LexicalHandler#endDTD()
659 	 */
660 	public void endDTD () throws SAXException {
661 
662 		// Need not implement
663 	}
664 
665 	/**
666 	 * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
667 	 */
668 	public void startEntity (String name) throws SAXException {
669 
670 		// Need not implement
671 	}
672 
673 	/**
674 	 * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
675 	 */
676 	public void endEntity (String name) throws SAXException {
677 
678 		// Need not implement
679 	}
680 
681 	/**
682 	 * @see org.xml.sax.ext.LexicalHandler#startCDATA()
683 	 */
684 	public void startCDATA () throws SAXException {
685 
686 		if (!isCanonical) {
687 			printWriter.print(CDATA_START);
688 			inCData = true;
689 		}
690 	}
691 
692 	/**
693 	 * @see org.xml.sax.ext.LexicalHandler#endCDATA()
694 	 */
695 	public void endCDATA () throws SAXException {
696 
697 		if (!isCanonical) {
698 			inCData = false;
699 			printWriter.print(CDATA_END);
700 		}
701 	}
702 
703 	/**
704 	 * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
705 	 */
706 	public void comment (char [] ch, int start, int length) throws SAXException {
707 
708 		if (!isCanonical && elementDepth > 0) {
709 			printWriter.print(COMMENT_START);
710 			for (int i = 0; i < length; ++i) {
711 				printWriter.print(ch[start + i]);
712 			}
713 			printWriter.print(COMMENT_END);
714 			printWriter.flush();
715 		}
716 	}
717 
718 	//
719 	// Utility Methods
720 	//
721 
722 	//	/**
723 	//	 * Returns a sorted list of attributes.
724 	//	 * @param attributes Attribute list to sort
725 	//	 * @return Sorted Attribute List
726 	//	 */
727 	//	private Attributes sortAttributes(Attributes attributes) {
728 	//		AttributesImpl attributesImpl = new AttributesImpl();
729 	//
730 	//        int len = (attributesImpl != null) ? attributesImpl.getLength() : 0;
731 	//        for (int i = 0; i < len; i++) {
732 	//            String name = attributesImpl.getQName(i);
733 	//            int count = attributesImpl.getLength();
734 	//            int j = 0;
735 	//            while (j < count) {
736 	//                if (name.compareTo(attributesImpl.getQName(j)) < 0) {
737 	//                    break;
738 	//                }
739 	//                j++;
740 	//            }
741 	//            //attributesImpl.setAttribute(j, name, name, name, name,
742 	// attr)insertAttributeAt(j, name, attributesImpl.getType(i),
743 	//            // attributesImpl.getValue(i));
744 	//        }
745 	//
746 	//        return attributesImpl;
747 	//	}
748 
749 	/**
750 	 * Normalizes and prints the given string.
751 	 * 
752 	 * @param s The string to normalize
753 	 * @param isAttValue Is Attribute Value
754 	 */
755 	private void normalizeAndPrint (String s, boolean isAttValue) {
756 
757 		int len = (s != null) ? s.length() : 0;
758 		for (int i = 0; i < len; i++ ) {
759 			char c = s.charAt(i);
760 			normalizeAndPrint(c, isAttValue);
761 		}
762 
763 	}
764 
765 	/**
766 	 * Normalizes and prints the given array of characters.
767 	 * 
768 	 * @param ch Character array to normalize
769 	 * @param offset Offset to start from
770 	 * @param length Length of data
771 	 * @param isAttValue Is Attribute value
772 	 */
773 	private void normalizeAndPrint (char [] ch, int offset, int length,
774 			boolean isAttValue) {
775 
776 		for (int i = 0; i < length; i++ ) {
777 			normalizeAndPrint(ch[offset + i], isAttValue);
778 		}
779 	}
780 
781 	/**
782 	 * Normalizes and print the given character.
783 	 * 
784 	 * @param c Character to print
785 	 * @param isAttValue Attribute Value to print
786 	 */
787 	protected void normalizeAndPrint (char c, boolean isAttValue) {
788 
789 		switch (c) {
790 			case '<': {
791 				printWriter.print("&lt;");
792 				break;
793 			}
794 			case '>': {
795 				printWriter.print("&gt;");
796 				break;
797 			}
798 			case '&': {
799 				printWriter.print("&amp;");
800 				break;
801 			}
802 			case '"': {
803 				// A '"' that appears in character data
804 				// does not need to be escaped.
805 				if (isAttValue) {
806 					printWriter.print("&quot;");
807 				} else {
808 					printWriter.print("\"");
809 				}
810 				break;
811 			}
812 			case '\r': {
813 				// If CR is part of the document's content, it
814 				// must not be printed as a literal otherwise
815 				// it would be normalized to LF when the document
816 				// is reparsed.
817 				printWriter.print("&#xD;");
818 				break;
819 			}
820 			case '\n': {
821 				if (isCanonical) {
822 					printWriter.print("&#xA;");
823 					break;
824 				}
825 				// else, default print char
826 			}
827 			default: {
828 				// In XML 1.1, control chars in the ranges [#x1-#x1F, #x7F-#x9F]
829 				// must be escaped.
830 				//
831 				// Escape space characters that would be normalized to #x20 in
832 				// attribute values
833 				// when the document is reparsed.
834 				//
835 				// Escape NEL (0x85) and LSEP (0x2028) that appear in content
836 				// if the document is XML 1.1, since they would be normalized to
837 				// LF
838 				// when the document is reparsed.
839 				if (inXML11
840 						&& ((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A)
841 								|| (c >= 0x7F && c <= 0x9F) || c == 0x2028)
842 						|| isAttValue && (c == 0x09 || c == 0x0A)) {
843 					printWriter.print("&#x");
844 					printWriter.print(Integer.toHexString(c).toUpperCase());
845 					printWriter.print(";");
846 				} else {
847 					printWriter.print(c);
848 				}
849 			}
850 		}
851 	}
852 
853 	/**
854 	 * Returns the version from the locator
855 	 * 
856 	 * @return The XML Version
857 	 */
858 	private String getVersion () {
859 
860 		if (null == documentLocator) {
861 			return null;
862 		}
863 		String version = null;
864 		Method getXMLVersion = null;
865 		try {
866 			getXMLVersion = documentLocator.getClass().getMethod(
867 					"getXMLVersion", new Class [] {});
868 			// If Locator implements Locator2, this method will exist.
869 			if (getXMLVersion != null) {
870 				version = (String) getXMLVersion.invoke(documentLocator, null);
871 			}
872 		} catch (Exception e) {
873 			// Either this locator object doesn't have
874 			// this method, or we're on an old JDK.
875 		}
876 		return version;
877 	}
878 
879 	//
880 	// Handle Preferences
881 	//
882 	/**
883 	 * Sets the current Preference id
884 	 * 
885 	 * @param attributes attributes of the preference
886 	 */
887 	private void startPreference (Attributes attributes) {
888 
889 		if (null != currentCompletePreferenceId) {
890 			preferenceIdStack.push(currentCompletePreferenceId);
891 			currentCompletePreferenceId = currentCompletePreferenceId
892 					+ Preference.ID_SEPERATOR
893 					+ attributes.getValue(XMLNAME_PREFERENCE_ID);
894 		} else {
895 			currentCompletePreferenceId = attributes
896 					.getValue(XMLNAME_PREFERENCE_ID);
897 		}
898 	}
899 
900 	/**
901 	 * Sets the Preference id to null
902 	 */
903 	private void endPreference () {
904 
905 		if (!preferenceIdStack.empty()) {
906 			currentCompletePreferenceId = (String) preferenceIdStack.pop();
907 		} else {
908 			currentCompletePreferenceId = null;
909 		}
910 	}
911 
912 	/**
913 	 * Marks the start of an Preference Attribute element.
914 	 * 
915 	 * @param attributes The XML attributes of the Preference Attribute
916 	 */
917 	private void startPreferenceAttribute (Attributes attributes) {
918 
919 		currentPreferenceAttributeId = attributes
920 				.getValue(XMLNAME_PREFERENCE_ATT_ID);
921 	}
922 
923 	/**
924 	 * Sets the current preference attribute to null
925 	 */
926 	private void endPreferenceAttribute () {
927 
928 		currentPreferenceAttributeId = null;
929 	}
930 
931 	/**
932 	 * Returns the value of the current attribute if changed, or returns null.
933 	 * 
934 	 * @return Returns the value of the current attribute if changed, or returns
935 	 *         <code>null</code>.
936 	 */
937 	private String getCurrentAttributeChangedValue () {
938 
939 		String key = currentCompletePreferenceId + KEY_SEPERATOR
940 				+ currentPreferenceAttributeId;
941 		if (preferenceAttributesToSave.containsKey(key)) {
942 			return (String) preferenceAttributesToSave.get(key);
943 		}
944 		return null;
945 	}
946 
947 }