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