001package org.hl7.fhir.utilities.xml;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033
034import java.io.IOException;
035import java.io.OutputStream;
036import java.io.OutputStreamWriter;
037import java.io.UnsupportedEncodingException;
038
039import org.hl7.fhir.utilities.ElementDecoration;
040
041/**
042 * XML Writer class.
043 */
044public class XMLWriter extends OutputStreamWriter implements IXMLWriter {
045
046        private boolean xmlHeader = true;
047        private String charset;
048        private boolean prettyBase;
049        private boolean prettyHeader;
050        private boolean pendingClose;
051        private boolean pendingOpen;
052        private String pendingComment;
053        private int lineType = LINE_UNIX;
054        private OutputStream stream;
055        private boolean started = false;
056        private String[] specialAttributeNames = new String[] {"id", "name" };
057        private boolean sortAttributes;
058        private int attributeLineWrap;
059  private boolean xml1_1;
060        
061        public final static int LINE_UNIX = 0;
062        public final static int LINE_WINDOWS = 1;
063
064  public XMLWriter(OutputStream stream, String charset, boolean xml1_1) throws UnsupportedEncodingException {    
065    super(stream, charset);
066    this.stream = stream;
067    this.charset = charset;
068    this.xml1_1 = xml1_1;
069  }
070        public XMLWriter(OutputStream stream, String charset) throws UnsupportedEncodingException {
071                super(stream, charset);
072                this.stream = stream;
073                this.charset = charset;
074    this.xml1_1 = false;
075        }
076
077        protected boolean condition(boolean bTest, String message) throws IOException {
078                if (!bTest)
079                        throw new IOException(message);
080                return bTest;
081        }
082
083        // -- writing context ------------------------------------------------
084
085
086        
087        /**
088         * Returns the encoding.
089         * 
090         * @param charset
091         * @return encoding
092         * @throws IOException
093         */
094        public static String getXMLCharsetName(String charset) throws IOException {
095                if (charset == null || charset.equals(""))
096                        return "UTF-8";
097                else if (charset.equals("US-ASCII"))
098                        return "UTF-8";
099                else if (XMLUtil.charSetImpliesAscii(charset))
100                        return "ISO-8859-1";
101                else if (charset.equals("UTF-8"))
102                        return "UTF-8";
103                else if (charset.equals("UTF-16") || charset.equals("UTF-16BE") || charset.equals("UTF-16LE"))
104                        return "UTF-16";
105                else 
106                        throw new IOException("Unknown charset encoding "+charset);
107        }
108
109        /* (non-Javadoc)
110         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#start()
111         */
112        @Override
113        public void start() throws IOException {
114                condition(!started, "attempt to start after starting");
115                levels.clear();
116                attributes = null;
117                try {
118                        if (xmlHeader) {
119                                write("<?xml version=\""+(xml1_1 ? "1.1" : "1.0")+"\" encoding=\""+getXMLCharsetName(charset)+"\"?>");
120                                if (prettyBase || prettyHeader)
121                                        write(lineType == LINE_UNIX ? "\n" : "\r\n");
122                        }
123                } catch (UnsupportedEncodingException e) {
124                        // TODO Auto-generated catch block
125                        throw new IOException(e.getMessage());
126                }
127                started = true;
128        }
129
130        private void checkStarted () throws IOException {
131                condition(started, "not started");
132        }
133
134        private void checkInElement() throws IOException {
135                condition(levels.size() > 0, "not in an element");
136        }
137
138        // -- attributes ----------------------------------------------------
139
140        private String[][] attributes;
141        
142        private void addAttribute(String name, String value) throws IOException {
143                addAttribute(name, value, false);
144        }
145
146        private void addAttribute(String name, String value, boolean noLines) throws IOException {
147                if (!XMLUtil.isNMToken(name))
148                        throw new IOException("XML name "+name+" is not valid for value '"+value+"'");
149
150                newLevelIfRequired();
151                value = XMLUtil.escapeXML(value, charset, noLines);
152
153                if (attributes == null) 
154                        attributes = new String[][] {{name, value}};
155                else {
156                        String[][] newattr = new String[attributes.length+1][];
157                        for (int i = 0; i < attributes.length; i++) {
158                                condition(!attributes[i][0].equals(name), "attempt to define attribute with name "+name+" more than once for value '"+value+"'");
159                                newattr[i] = attributes[i];
160                        }
161                        attributes = newattr;
162                        attributes[attributes.length-1] = new String[] {name, value};
163                }
164        }
165
166        protected String getAttribute(String name) {
167                if (attributes != null) {
168                        for (int i = 0; i < attributes.length; i++) {
169                                if (attributes[i][0].equals(name)) {
170                                        return attributes[i][1];
171                                }
172                        }                       
173                }
174                return null;
175        }
176        
177        protected void setAttribute(String name, String value) throws IOException {
178                newLevelIfRequired();
179                if (attributes == null) 
180                        addAttribute(name, value, false);
181                else {
182                        for (int i = 0; i < attributes.length; i++) {
183                                if (attributes[i][0].equals(name)) {
184                                        attributes[i][1] = XMLUtil.escapeXML(value, charset, false);
185                                        return;
186                                }
187                        }
188                        addAttribute(name, value);
189                }
190        }
191
192        protected void commitAttributes() throws IOException {
193                
194        }
195
196        
197        private boolean nameIsSpecial(String name) {
198                for (int i = 0; i < specialAttributeNames.length; i++) {
199                        String n = specialAttributeNames[i];
200                        if (n.equalsIgnoreCase(name))
201                                return true;
202                }
203                return false;
204        }
205        
206        private void writeAttributes(int col) throws IOException {
207                commitAttributes();
208                if (attributes != null && sortAttributes)
209                        sortAttributes();       
210                int c = col;
211                c = writeAttributeSet(true, c, col);
212                writeAttributeSet(false, c, col);
213                attributes = null;
214        }
215
216
217        private void sortAttributes() {
218                // bubble sort - look, it's easy
219                for (int i = 0; i < attributes.length - 1; i++) {
220                        for (int j = 0; j < attributes.length - 1; j++) {
221                                if (String.CASE_INSENSITIVE_ORDER.compare(attributes[j][0], attributes[j+1][0]) < 0) {
222                                        String[] t = attributes[j];
223                                        attributes[j] = attributes[j+1];
224                                        attributes[j+1] = t;
225                                }
226                        }
227                }
228                
229        }
230
231
232        private int writeAttributeSet(boolean special, int col, int wrap) throws IOException {
233                // first pass: name, id
234                if (attributes != null) {
235                        for (int i=0; i < attributes.length; i++) {
236                                String[] element = attributes[i];
237                                if (nameIsSpecial(element[0]) == special) {
238                                        col = col + element[0].length()+element[1].length() + 4;
239                                        if (isPretty() && attributeLineWrap > 0 && col > attributeLineWrap && col > wrap) {
240                                                write(lineType == LINE_UNIX ? "\n" : "\r\n");
241                                                for (int j = 0; j < wrap; j++)
242                                                        write(" ");
243                                                col = wrap;
244                                        }
245                                        write(' ');
246                                        write(element[0]);
247                                        write("=\"");
248                                        if (element[1] != null)
249                                                write(xmlEscape(element[1]));
250                                        write("\"");
251                                }
252                        }
253                }
254                return col;
255        }
256
257         protected String xmlEscape(String s) {
258     StringBuilder b = new StringBuilder();
259     for (char c : s.toCharArray()) {
260       if (c < ' ') {
261         b.append("&#x");
262         b.append(Integer.toHexString(c).toUpperCase());
263         b.append(";");
264       } else
265         b.append(c);
266     }
267     return b.toString();
268   }
269
270         /* (non-Javadoc)
271         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String, java.lang.String, boolean)
272         */
273        @Override
274        public void attribute(String namespace, String name, String value, boolean onlyIfNotEmpty) throws IOException {
275                if (!onlyIfNotEmpty || value != null && !value.equals(""))
276                        attribute(namespace, name, value);
277        }
278        
279        /* (non-Javadoc)
280         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String, java.lang.String)
281         */
282        @Override
283        public void attribute(String namespace, String name, String value) throws IOException {
284
285                checkStarted();
286                if (namespace == null || namespace.equals("")) 
287                        addAttribute(name, value);
288                else
289                        addAttribute(getNSAbbreviation(namespace)+name, value);
290        }
291
292        /* (non-Javadoc)
293         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String, boolean)
294         */
295        @Override
296        public void attribute(String name, String value, boolean onlyIfNotEmpty) throws IOException {
297                if (!onlyIfNotEmpty || value != null && !value.equals(""))
298                        attribute(name, value);
299        }
300
301        /* (non-Javadoc)
302         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String)
303         */
304        @Override
305        public void attribute(String name, String value) throws IOException {
306                checkStarted();
307                addAttribute(name, value);
308        }
309
310        /* (non-Javadoc)
311         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attributeNoLines(java.lang.String, java.lang.String)
312         */
313        @Override
314        public void attributeNoLines(String name, String value) throws IOException {
315                checkStarted();
316                addAttribute(name, value, true);
317        }
318
319        // -- levels -------------------------------------------------
320
321        private XMLWriterStateStack levels = new XMLWriterStateStack();
322
323        private void newLevelIfRequired() throws IOException {
324                if (!pendingOpen) {
325                        if (!levels.empty())
326                                levels.current().seeChild();
327                        XMLWriterState level = new XMLWriterState();
328                        level.setPretty(isPretty());
329                        levels.push(level);
330                        pendingOpen = true;
331                }
332        }
333
334        // -- namespaces ---------------------------------------------
335
336
337        private void defineNamespace(String namespace, String abbrev) throws IOException {
338                checkStarted();
339                if (namespace != null && !namespace.equals("")) {
340                        if ("".equals(abbrev))
341                                abbrev = null;
342
343                        newLevelIfRequired();
344
345                        levels.current().addNamespaceDefn(namespace, abbrev);
346                        if (abbrev == null)
347                                addAttribute("xmlns", namespace);
348                        else
349                                addAttribute("xmlns:"+abbrev, namespace);
350                }
351        }
352
353        /* (non-Javadoc)
354         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#findByNamespace(java.lang.String)
355         */
356        public XMLNamespace findByNamespace(String namespace) {
357                for (int i = levels.size() - 1; i >= 0; i--) {
358                        XMLNamespace ns = levels.item(i).getDefnByNamespace(namespace);
359                        if (ns != null)
360                                return ns;
361                }
362                return null;
363        }
364
365        /* (non-Javadoc)
366         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#namespaceDefined(java.lang.String)
367         */
368        @Override
369        public boolean namespaceDefined(String namespace) {
370                return namespace == null || namespace.equals("") || findByNamespace(namespace) != null;
371        }
372
373        /* (non-Javadoc)
374         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#findByAbbreviation(java.lang.String)
375         */
376        public XMLNamespace findByAbbreviation(String abbreviation) {
377                for (int i = levels.size() - 1; i >= 0; i--) {
378                        XMLNamespace ns = levels.item(i).getDefnByAbbreviation(abbreviation);
379                        if (ns != null)
380                                return ns;
381                }
382                return null;
383        }
384
385        /* (non-Javadoc)
386         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#abbreviationDefined(java.lang.String)
387         */
388        @Override
389        public boolean abbreviationDefined(String abbreviation) {
390                return findByAbbreviation(abbreviation) != null;
391        }
392
393        protected XMLNamespace findDefaultNamespace() {
394                for (int i = levels.size() - 1; i >= 0; i--) {
395                        XMLNamespace ns = levels.item(i).getDefaultNamespace();
396                        if (ns != null)
397                                return ns;
398                }
399                return null;
400        }
401
402        /* (non-Javadoc)
403         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#getDefaultNamespace()
404         */
405        @Override
406        public String getDefaultNamespace() {
407                XMLNamespace ns = findDefaultNamespace();
408                if (ns == null)
409                        return null;
410                else
411                        return ns.getNamespace();
412        }
413
414        /* (non-Javadoc)
415         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#namespace(java.lang.String)
416         */
417        @Override
418        public void namespace(String namespace) throws IOException {
419                if (!namespaceDefined(namespace)) {
420                        int index = 0;
421                        while (abbreviationDefined("ns"+Integer.toString(index))) 
422                                index++;
423                        defineNamespace(namespace, "ns"+Integer.toString(index));
424                }
425        }
426
427        /* (non-Javadoc)
428         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#defaultNamespace(java.lang.String)
429         * 
430         * Replace defaultNamespace()
431         */
432        @Override
433        public void setDefaultNamespace(String namespace) throws IOException {
434                if ((namespace == null && getDefaultNamespace() != null) ||
435                                (namespace != null && !namespace.equals(getDefaultNamespace())))
436                        defineNamespace(namespace, "");                 
437        }
438
439        /* (non-Javadoc)
440         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#namespace(java.lang.String, java.lang.String)
441         */
442        @Override
443        public void namespace(String namespace, String abbreviation) throws IOException {
444                XMLNamespace ns = findByAbbreviation(abbreviation);
445                if (ns == null || !ns.getNamespace().equals(namespace))
446                        defineNamespace(namespace, abbreviation);
447        }
448
449
450        private String getNSAbbreviation(String namespace) throws IOException {
451                if ("http://www.w3.org/XML/1998/namespace".equals(namespace))
452                        return "xml:";
453                
454                if (namespace == null || "".equals(namespace) || "noNamespace".equals(namespace))
455                        return "";
456                
457                XMLNamespace ns = findByNamespace(namespace);
458                if (ns == null)
459                        throw new IOException("Namespace "+namespace+" is not defined");
460                else if (ns.getAbbreviation() == null)
461                        return "";
462                else
463                        return ns.getAbbreviation()+":";
464        }
465
466        // -- public API -----------------------------------------------------------
467
468        /* (non-Javadoc)
469         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#comment(java.lang.String, boolean)
470         */
471        @Override
472        public void comment(String comment, boolean doPretty) throws IOException {
473                checkStarted();
474                if (pendingClose) { 
475                        write('>');
476                        writePendingComment();
477                        pendingClose = false;
478                }
479                if (doPretty) {
480                        writePretty();
481                }
482                if (levels.inComment())
483                        write("  <!-- "+comment+" -- >");
484                else
485                        write("  <!-- "+comment+" -->");
486                if (doPretty && !isPretty())
487                        writePretty();
488        }
489
490
491        private void writePendingComment() throws IOException {
492                if (pendingComment != null) {
493                        if (isPretty())
494                                write("   ");
495                        if (levels.inComment())
496                                write("<!-- "+pendingComment+" -- >");
497                        else
498                                write("<!-- "+pendingComment+" -->");
499                }
500        }
501        
502        /* (non-Javadoc)
503         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#open(java.lang.String, java.lang.String)
504         */
505        @Override
506        public void enter(String namespace, String name) throws IOException {
507                enter(namespace, name, null);
508        }
509
510
511        /* (non-Javadoc)
512         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#open(java.lang.String, java.lang.String, java.lang.String)
513         */
514        @Override
515        public void enter(String namespace, String name, String comment) throws IOException {
516                if (name == null)
517                        throw new Error("name == null");
518                if (!XMLUtil.isNMToken(name))
519                        throw new IOException("XML name "+name+" is not valid");
520                checkStarted();
521                if (pendingClose) { 
522                        write('>');
523                        writePendingComment();
524                        pendingClose = false;
525                }
526
527                if (name == null) {
528                        throw new IOException("name is null");
529                }
530                newLevelIfRequired();
531                levels.current().setName(name);
532                levels.current().setNamespace(namespace);
533                int col = writePretty();
534                write('<');
535                if (namespace == null) {
536                        write(name);
537                        col = col + name.length()+1;
538                } else {
539                        String n = getNSAbbreviation(namespace)+name;
540                        write(n);
541                        col = col + n.length()+1;
542                }
543                writeAttributes(col);
544                pendingOpen = false;
545                pendingClose = true;
546                pendingComment = comment;
547        }
548
549
550        /* (non-Javadoc)
551         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#close(java.lang.String)
552         */
553        @Override
554        public void exit(String name) throws IOException {
555                checkStarted();
556                if (levels.empty())
557                        throw new IOException("Unable to close null|"+name+", nothing to close");
558                if (levels.current().getNamespace() != null || !levels.current().getName().equals(name))
559                        throw new IOException("Unable to close null|"+name+", found "+levels.current().getNamespace()+"|"+levels.current().getName());
560                exit();
561        }
562
563        /* (non-Javadoc)
564         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#close(java.lang.String, java.lang.String)
565         */
566        @Override
567        public void exit(String namespace, String name) throws IOException {
568                checkStarted();
569                if (levels.empty())
570                        throw new IOException("Unable to close "+namespace+"|"+name+", nothing to close");
571                if (levels == null)
572                        throw new Error("levels = null");
573                if (levels.current() == null)
574                        throw new Error("levels.current() = null");
575                if (levels.current().getName() == null)
576                        throw new Error("levels.current().getName() = null");
577                if (levels.current().getNamespace() == null)
578                        throw new Error("levels.current().getNamespace() = null");
579                if (name == null)
580                        throw new Error("name = null");
581                if (namespace == null)
582                        throw new Error("namespace = null");
583                if (!levels.current().getNamespace().equals(namespace) || !levels.current().getName().equals(name))
584                        throw new IOException("Unable to close "+namespace+"|"+name+", found "+levels.current().getNamespace()+"|"+levels.current().getName());
585                exit();
586        }
587
588        /* (non-Javadoc)
589         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#closeToLevel(int)
590         */
591        @Override
592        public void exitToLevel(int count) throws IOException {
593    checkStarted();
594                while (levels.size() > count)
595                        exit();         
596        }
597
598
599        /* (non-Javadoc)
600         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#close()
601         */
602  @Override
603  public void close() throws IOException {
604    checkStarted();
605    if (!levels.empty()) 
606      throw new IOException("Called close before exiting all opened elements");
607     super.close();
608  }
609    
610  @Override
611  public void end() throws IOException {
612    checkStarted();
613    if (!levels.empty()) 
614      throw new IOException("Called end() before exiting all opened elements");
615     flush();
616  }
617        @Override
618        public void exit() throws IOException {
619                checkStarted();
620                if (levels.empty()) {
621                        throw new IOException("Called exit one too many times");
622                } else {
623                        if (pendingClose) { 
624                                write("/>");
625                                writePendingComment();
626                                pendingClose = false;
627                        } else {
628                                if (levels.current().hasChildren())
629                                        writePretty();
630                                write("</");
631                                if (levels.current().getNamespace() == null)
632                                        write(levels.current().getName());
633                                else
634                                        write(getNSAbbreviation(levels.current().getNamespace())+levels.current().getName());
635                                write('>');
636                        }
637                        levels.pop();
638                }
639        }
640
641        /* (non-Javadoc)
642         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#open(java.lang.String)
643         */
644        @Override
645        public void enter(String name) throws IOException {
646                enter(null, name);
647        }
648
649
650        /* (non-Javadoc)
651         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, java.lang.String, boolean)
652         */
653        @Override
654        public void element(String namespace, String name, String content, boolean onlyIfNotEmpty) throws IOException {
655                if (!onlyIfNotEmpty || content != null && !content.equals(""))
656                        element(namespace, name, content);
657        }
658
659        /* (non-Javadoc)
660         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
661         */
662        @Override
663        public void element(String namespace, String name, String content, String comment) throws IOException {
664                if (!XMLUtil.isNMToken(name))
665                        throw new IOException("XML name "+name+" is not valid");
666                enter(namespace, name, comment);
667                text(content);
668                exit();
669        }
670        
671        /* (non-Javadoc)
672         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, java.lang.String)
673         */
674        @Override
675        public void element(String namespace, String name, String content) throws IOException {
676                if (!XMLUtil.isNMToken(name))
677                        throw new IOException("XML name "+name+" is not valid");
678                enter(namespace, name);
679                text(content);
680                exit();
681        }
682
683        /* (non-Javadoc)
684         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, boolean)
685         */
686        @Override
687        public void element(String name, String content, boolean onlyIfNotEmpty) throws IOException {
688                if (!onlyIfNotEmpty || content != null && !content.equals(""))
689                        element(null, name, content);
690        }
691
692        /* (non-Javadoc)
693         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String)
694         */
695        @Override
696        public void element(String name, String content) throws IOException {
697                element(null, name, content);
698        }
699
700  @Override
701  public void element(String name) throws IOException {
702    element(null, name, null);
703  }
704
705        /* (non-Javadoc)
706         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#text(java.lang.String)
707         */
708        @Override
709        public void text(String content) throws IOException {
710                text(content, false);
711        }
712
713        /* (non-Javadoc)
714         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#text(java.lang.String, boolean)
715         * 
716         * Replace escapeText()
717         */
718        @Override
719        public void text(String content, boolean dontEscape) throws IOException {
720                checkInElement();
721                if (content != null) {
722                        if (pendingClose) { 
723                                write(">");
724                                writePendingComment();
725                                pendingClose = false;
726                        }
727                        if (dontEscape)
728                                write(content);
729                        else
730                                write(XMLUtil.escapeXML(content, charset, false));
731                }
732        }
733
734        /* (non-Javadoc)
735         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#cData(java.lang.String)
736         */
737        @Override
738        public void cData(String text) throws IOException {
739                text("<![CDATA["+text+"]]>");           
740        }
741        
742        /* (non-Javadoc)
743         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#writeBytes(byte[])
744         */
745        @Override
746        public void writeBytes(byte[] bytes) throws IOException {
747                checkInElement();
748                if (pendingClose) { 
749                        write(">");
750                        writePendingComment();
751                        pendingClose = false;
752                }
753                flush();
754                stream.write(bytes);
755        }
756
757
758        /* (non-Javadoc)
759         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#isPretty()
760         */
761        @Override
762        public boolean isPretty() throws IOException {
763                return (levels == null || levels.empty()) ? prettyBase : levels.current().isPretty();
764        }
765
766        /* (non-Javadoc)
767         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#setPretty(boolean)
768         */
769        @Override
770        public void setPretty(boolean pretty) throws IOException {
771                if (levels == null || levels.empty())
772                        this.prettyBase = pretty;
773                else 
774                        levels.current().setPretty(pretty);
775        }
776
777        /* (non-Javadoc)
778         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#startCommentBlock()
779         */
780        @Override
781        public void startCommentBlock() throws IOException {
782                if (levels.inComment())
783                        throw new IOException("cannot nest comments");
784                levels.current().setInComment(true);
785                if (isPretty())
786                        writePretty();
787                write("<!--");
788                if (isPretty())
789                        writePretty();          
790        }
791
792        /* (non-Javadoc)
793         * @see org.eclipse.ohf.utilities.xml.IXMLWriter#endCommentBlock()
794         */
795        @Override
796        public void endCommentBlock() throws IOException {
797                if (!levels.inComment())
798                        throw new IOException("cannot close a comment block when it is open");
799                if (!levels.current().isInComment())
800                        throw new IOException("cannot close a comment block when it is open");
801                if (isPretty())
802                        writePretty();
803                write("-->");
804                if (isPretty())
805                        writePretty();          
806                levels.current().setInComment(false);
807        }
808
809        public boolean isSortAttributes() {
810                return sortAttributes;
811        }
812
813        public void setSortAttributes(boolean sortAttributes) {
814                this.sortAttributes = sortAttributes;
815        }
816
817
818        public boolean isPrettyHeader() {
819                return prettyHeader;
820        }
821
822        public void setPrettyHeader(boolean pretty) {
823                this.prettyHeader = pretty;
824        }
825
826        public int writePretty() throws IOException {
827                return writePretty(true);
828        }
829        
830        public int writePretty(boolean eoln) throws IOException {
831                if (isPretty()) {
832                        if (eoln)
833                                write(lineType == LINE_UNIX ? "\n" : "\r\n");
834                        for (int i = 0; i < levels.size() - 1; i++)
835                                write("  ");
836                        return (levels.size() - 1) * 2;
837                } else
838                        return 0;
839        }
840
841        public int getLineType() {
842                return lineType;
843        }
844
845        public void setLineType(int lineType) {
846                this.lineType = lineType;
847        }
848
849        public boolean isXmlHeader() {
850                return xmlHeader;
851        }
852
853        public void setXmlHeader(boolean xmlHeader) {
854                this.xmlHeader = xmlHeader;
855        }
856
857        public String[] getSpecialAttributeNames() {
858                return specialAttributeNames;
859        }
860
861        public void setSpecialAttributeNames(String[] specialAttributeNames) {
862                this.specialAttributeNames = specialAttributeNames;
863        }
864
865        public int getAttributeLineWrap() {
866                return attributeLineWrap;
867        }
868
869        public void setAttributeLineWrap(int attributeLineWrap) {
870                this.attributeLineWrap = attributeLineWrap;
871        }
872
873        @Override
874        public void escapedText(String content) throws IOException {
875                text("");
876                int i = content.length();
877                if (isPretty())
878                  while (i > 0 && (content.charAt(i-1) == '\r' || content.charAt(i-1) == '\n'))
879                         i--;
880                write(content.substring(0, i));
881        }
882
883  public void processingInstruction(String value) throws IOException {
884    write("<?"+value+"?>");
885    if (isPrettyHeader())
886      write("\r\n");
887  }
888
889  @Override
890  public void link(String href) {
891    // ignore this
892    
893  }
894
895  @Override
896  public void anchor(String name) {
897    // ignore this    
898  }
899
900  @Override
901  public void decorate(ElementDecoration element) throws IOException {
902    // nothing...
903  }
904  @Override
905  public void setSchemaLocation(String ns, String loc) throws IOException {
906    namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi");
907    attribute("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", ns+" "+loc);
908    
909  }
910        
911        
912}