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