PageRenderTime 66ms CodeModel.GetById 14ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 0ms

/bundles/plugins-trunk/XML/xml/indent/IndentingTransformer.java

#
Java | 531 lines | 478 code | 22 blank | 31 comment | 8 complexity | 0a43f238bed585209540815ed0cd0aa4 MD5 | raw file
  1/*
  2*  AbstractIndentingTransformer.java - Indents XML elements, by adding whitespace where appropriate.
  3*
  4*  Copyright (c) 2002, 2003 Robert McKinnon
  5*
  6*  This program is free software; you can redistribute it and/or
  7*  modify it under the terms of the GNU General Public License
  8*  as published by the Free Software Foundation; either version 2
  9*  of the License, or any later version.
 10*
 11*  This program is distributed in the hope that it will be useful,
 12*  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14*  GNU General Public License for more details.
 15*
 16*  You should have received a copy of the GNU General Public License
 17*  along with this program; if not, write to the Free Software
 18*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 19*
 20*  email: robmckinnon@users.sourceforge.net
 21*/
 22
 23package xml.indent;
 24
 25import org.xml.sax.Attributes;
 26import org.xml.sax.SAXException;
 27import org.xml.sax.ext.DeclHandler;
 28import org.xml.sax.helpers.AttributesImpl;
 29
 30import javax.xml.transform.sax.TransformerHandler;
 31import java.io.IOException;
 32import java.io.Writer;
 33import java.util.regex.*;
 34
 35import org.gjt.sp.jedit.jEdit;
 36
 37
 38/**
 39 * Indents elements, by adding whitespace where appropriate.
 40 * Does not remove blank lines between nodes.
 41 * Does not remove new lines within text nodes.
 42 * Puts element tags immediately following mixed content text on the same line as the text.
 43 *
 44 * @author Robert McKinnon - robmckinnon@users.sourceforge.net
 45 */
 46public abstract class IndentingTransformer implements TransformerHandler, DeclHandler {
 47
 48    private static final String ON_NEW_LINE = "onNewLine";
 49
 50    /** buffer to hold character data */
 51    private Writer writer;
 52
 53    private String xml;
 54    private char[] chars;
 55    private boolean okToContinue = true;
 56    private boolean isClosingTag = false;
 57    private boolean isEmptyElement = false;
 58    private boolean isDocType = false;
 59
 60    public void characters( char ch[], int start, int length ) throws SAXException {
 61        try {
 62            if ( isDocType ) {
 63                isDocType = false;
 64            }
 65            else {
 66                writer.write( ch, start, length );
 67            }
 68        }
 69        catch ( IOException e ) {
 70            throw new SAXException( e );
 71        }
 72    }
 73
 74
 75    public void comment( char ch[], int start, int length ) throws SAXException {
 76        try {
 77            writer.write( "<!--" );
 78            writer.write( ch, start, length );
 79            writer.write( "-->" );
 80        }
 81        catch ( IOException e ) {
 82            throw new SAXException( e );
 83        }
 84    }
 85
 86
 87    public void processingInstruction( String target, String data ) throws SAXException {
 88        try {
 89            writer.write( "<?" );
 90            writer.write( target );
 91            writer.write( " " );
 92            writer.write( data );
 93            writer.write( "?>" );
 94        }
 95        catch ( IOException e ) {
 96            throw new SAXException( e );
 97        }
 98    }
 99
100
101    public void endElement( String namespaceURI, String localName, String qName ) throws SAXException {
102        try {
103            if ( isClosingTag ) {
104                writer.write( "</" );
105                writer.write( qName );
106                writer.write( ">" );
107                isClosingTag = false;
108            }
109        }
110        catch ( IOException e ) {
111            throw new SAXException( e );
112        }
113    }
114
115
116    public void startElement( String namespaceURI, String localName, String qName, Attributes atts )
117    throws SAXException {
118
119        try {
120            writer.write( "<" );
121
122            boolean spaceAtEnd = qName.charAt( qName.length() - 1 ) == ' ';
123            if ( spaceAtEnd ) {
124                writer.write( qName.substring( 0, qName.length() - 1 ) );
125            }
126            else {
127                writer.write( qName );
128            }
129
130            boolean onNewLine = jEdit.getBooleanProperty("xmlindenter.splitAttributes", false);
131            for ( int i = 0; i < atts.getLength(); i++ ) {
132                String attributeQName = atts.getQName( i );
133                String attributeValue = atts.getValue( i );
134                //boolean onNewLine = ( atts.getType( i ) == ON_NEW_LINE );
135
136                boolean containsDoubleQuote = ( attributeValue.indexOf( '"' ) != -1 );
137                char quote = containsDoubleQuote ? '\'' : '\"';
138
139                if ( onNewLine ) {
140                    indent( 1 );
141                }
142                else {
143                    writer.write( ' ' );
144                }
145
146                writer.write( attributeQName );
147                writer.write( '=' );
148                writer.write( quote );
149                writer.write( attributeValue );
150                writer.write( quote );
151            }
152
153            if ( isEmptyElement ) {
154                if ( spaceAtEnd ) {
155                    writer.write( " />" ); // to cater for <br /> elements
156                }
157                else {
158                    writer.write( "/>" );
159                }
160            }
161            else {
162                writer.write( ">" );
163            }
164        }
165        catch ( IOException e ) {
166            throw new SAXException( e );
167        }
168
169    }
170
171    protected abstract void indent( int levelAdjustment ) throws SAXException;
172
173    protected String indentXml( final String xmlString, final Writer outputWriter ) throws IOException, SAXException {
174        this.okToContinue = true;
175        this.isClosingTag = false;
176        this.isEmptyElement = false;
177        this.isDocType = false;
178        this.writer = outputWriter;
179        this.xml = xmlString;
180        this.chars = xml.toCharArray();
181
182        int start = 0;
183        int end = 0;
184
185        while ( okToContinue ) {
186            end = xml.indexOf( '<', start );
187            writeTextPrecedingLessThan( start, end );
188
189            if ( okToContinue ) {
190                start = end;
191
192                if ( xml.startsWith( "<!--", start ) ) {
193                    end = writeComment( start );
194
195                }
196                else if ( xml.startsWith( "<?", start ) ) {
197                    end = writeXmlDeclarationOrProcessingInstruction( start );
198
199                }
200                else if ( xml.startsWith( "<!", start ) ) {
201                    if ( xml.startsWith( "<![CDATA[", start ) ) {
202                        end = writeCData( start );
203                    }
204                    else {
205                        end = writeDocType( start );
206                    }
207                }
208                else if ( xml.startsWith( "</", start ) ) {
209                    end = writeClosingTag( start );
210
211                }
212                else if ( Character.isWhitespace( chars[ start + 1 ] ) ) {
213                    throw new SAXException( "The content of elements must consist of well-formed character data or markup." );
214
215                }
216                else {
217                    end = writeElement( start );
218                }
219
220                start = end;
221            }
222        }
223
224        return outputWriter.toString();
225    }
226
227
228    private int writeCData( int start ) throws IOException, SAXException {
229        int end = xml.indexOf( "]]>", start );
230        writeRemaining( start, end );
231        if ( okToContinue ) {
232            startCDATA();
233            end = end + 3;
234            writer.write( xml, start, end - start );
235            endCDATA();
236        }
237
238        return end;
239    }
240
241
242    private int writeElement( int start ) throws IOException, SAXException {
243        int end = getStartTagEnd( start );
244        writeRemaining( start, end );
245
246        if ( okToContinue ) {
247            int offset = 1;
248            while ( Character.isWhitespace( chars[ end - offset ] ) ) {
249                offset++;
250            }
251            isEmptyElement = ( chars[ end - offset ] == '/' );
252
253            if ( isEmptyElement ) {
254                end = end - offset;
255            }
256
257            AttributesImpl attributes = new AttributesImpl();
258            String elementName = getElementNameAndPopulateAttributes( start, end, attributes );
259
260            if ( isEmptyElement && chars[ end - 1 ] == ' ' ) {
261                elementName += ' '; // to cater for <br /> elements
262            }
263
264            startElement( "", "", elementName, attributes );
265
266            if ( isEmptyElement ) {
267                endElement( "", "", elementName );
268                end = end + offset + 1;
269                isEmptyElement = false;
270            }
271            else {
272                end = end + 1;
273            }
274        }
275
276        return end;
277    }
278
279
280    /**
281     * Ignores '>' characters that are inside of attribute values.
282     */
283    private int getStartTagEnd( int start ) {
284        int end = -1;
285        int index = start;
286
287        while ( index < chars.length && end == -1 ) {
288            char aChar = chars[ index ];
289            index++;
290
291            if ( aChar == '\"' ) {
292                while ( chars[ index ] != '\"' ) {
293                    index++;
294                }
295                index++;
296            }
297            else if ( aChar == '\'' ) {
298                while ( chars[ index ] != '\'' ) {
299                    index++;
300                }
301                index++;
302                //      } else if(aChar == '/') {
303                //        while(chars[index] != '>') {
304                //          index++;
305                //        }
306            }
307            else if ( aChar == '>' ) {
308                end = index - 1;
309                //        end = index;
310            }
311        }
312        return end;
313    }
314
315
316    Pattern p = Pattern.compile( "([<].*?)\\s", Pattern.DOTALL );
317    private String getElementNameAndPopulateAttributes( int start, int end, AttributesImpl attributes ) throws SAXException {
318        // element name ends at _first_ white space char
319        int nameEnd = -1;
320        Matcher m = p.matcher(xml.substring(start, end));
321        boolean found = m.find();
322        if (found) {
323            nameEnd = start + m.end();
324        }
325        else {
326            nameEnd = xml.indexOf( ' ', start );
327
328            if ( nameEnd == -1 || nameEnd > end ) {
329                nameEnd = xml.indexOf( '\n', start );
330            }
331
332            if ( nameEnd == -1 || nameEnd > end ) {
333                nameEnd = xml.indexOf( '\r', start );
334            }
335        }
336        if ( nameEnd == -1 || nameEnd > end ) {
337            nameEnd = end;
338        }
339        else if ( nameEnd + 1 != end ) {
340            while ( Character.isWhitespace( chars[ nameEnd - 1 ] ) ) {
341                nameEnd--; // want to check if char at nameEnd is a new line char
342            }
343            char[] elementChars = xml.substring( nameEnd, end ).toCharArray();
344            populateAttributes( elementChars, attributes );
345        }
346
347        StringBuffer elementName = new StringBuffer();
348        int index = start + 1;
349
350        while ( !Character.isWhitespace( chars[ index ] ) && chars[ index ] != '>' && chars[ index ] != '/' ) {
351            elementName.append( chars[ index++ ] );
352        }
353
354        return elementName.toString();
355    }
356
357
358    private void populateAttributes( char[] chars, AttributesImpl attributes ) throws SAXException {
359        StringBuffer qName = new StringBuffer();
360        StringBuffer value = new StringBuffer();
361        char quote = '\"';
362        int i = 0;
363        boolean attributeOnNewLine;
364
365        while ( i < chars.length ) {
366            attributeOnNewLine = false;
367            qName.setLength( 0 );
368            value.setLength( 0 );
369
370            // consume leading whitespace
371            while ( i < chars.length && Character.isWhitespace( chars[ i ] ) ) {
372                if ( chars[ i ] == '\r' || chars[ i ] == '\n' ) {
373                    attributeOnNewLine = true;
374                }
375                i++;
376            }
377
378            if ( i < chars.length ) {
379                // consume attribute name
380                while ( i < chars.length && chars[ i ] != '=' ) {
381                    qName.append( chars[ i ] );
382                    i++;
383                }
384                i++; // get past equals
385
386                // consume whitespace between equals sign and start of attribute value
387                while ( i < chars.length && Character.isWhitespace( chars[ i ] ) ) {
388                    i++; // get past whitespace before first quote
389                }
390
391                // consume attribute value start quote
392                if ( i < chars.length ) {
393                    if ( chars[ i ] != '\"' && chars[ i ] != '\'' ) {
394                        throw new SAXException( "value for attribute " + qName.toString().trim() + " must be in quotes" );
395                    }
396                    else {
397                        quote = chars[ i ];
398                        i++; // get past first quote
399                    }
400                }
401
402                // consume attribute value
403                while ( i < chars.length && chars[ i ] != quote ) {
404                    value.append( chars[ i ] );
405                    i++;
406                }
407
408                i++; // get past quote
409
410                String type = attributeOnNewLine ? ON_NEW_LINE : "";
411                attributes.addAttribute( "", "", qName.toString().trim(), type, value.toString() );
412            }
413        }
414    }
415
416
417    private int writeClosingTag( int start ) throws IOException, SAXException {
418        int end = xml.indexOf( '>', start );
419        writeRemaining( start, end );
420
421        if ( okToContinue ) {
422            isClosingTag = true;
423            endElement( "", "", xml.substring( start + 2, end ).trim() );
424            end = end + 1;
425        }
426        return end;
427    }
428
429
430    private int writeDocType( int start ) throws IOException {
431        int end = xml.indexOf( '>', start );
432        int bracketStart = xml.indexOf( '[', start );
433
434        if ( bracketStart != -1 && bracketStart < end ) {
435            int bracketEnd = xml.indexOf( ']', bracketStart );
436            end = xml.indexOf( '>', bracketEnd );
437        }
438
439        writeRemaining( start, end );
440
441        if ( okToContinue ) {
442            end = end + 1;
443            writer.write( "\n" );
444            int length = end - start;
445            writer.write( xml, start, length );
446            isDocType = true;
447        }
448
449        return end;
450    }
451
452
453    private int writeXmlDeclarationOrProcessingInstruction( int start ) throws IOException, SAXException {
454        int end;
455
456        if ( xml.startsWith( "<?xml ", start ) ) {
457            end = writeXmlDeclaration( start );
458        }
459        else {
460            end = writeProcessingInstruction( start );
461        }
462
463        return end;
464    }
465
466
467    private int writeProcessingInstruction( int start ) throws IOException, SAXException {
468        int end = xml.indexOf( "?>", start );
469        writeRemaining( start, end );
470
471        if ( okToContinue ) {
472            int targetEnd = xml.indexOf( ' ', start );
473            String target = xml.substring( start + "<?".length(), targetEnd );
474            String data = xml.substring( targetEnd + 1, end );
475            processingInstruction( target, data );
476            end = end + "?>".length();
477        }
478
479        return end;
480    }
481
482
483    private int writeXmlDeclaration( int start ) throws IOException {
484        int end = xml.indexOf( "?>", start );
485        writeRemaining( start, end );
486
487        if ( okToContinue ) {
488            end = end + "?>".length();
489            int length = end - start;
490            writer.write( xml, start, length );
491        }
492
493        return end;
494    }
495
496
497    private int writeComment( int start ) throws IOException, SAXException {
498        int end = xml.indexOf( "-->", start );
499        writeRemaining( start, end );
500
501        if ( okToContinue ) {
502            int commentTextStart = start + "<!--".length();
503            int commentTextLength = end - commentTextStart;
504            comment( chars, commentTextStart, commentTextLength );
505            end = end + "-->".length();
506        }
507
508        return end;
509    }
510
511
512    private void writeTextPrecedingLessThan( int start, int end ) throws IOException, SAXException {
513        writeRemaining( start, end );
514
515        if ( okToContinue && end > start ) {
516            int length = end - start;
517            characters( chars, start, length );
518        }
519    }
520
521
522    private void writeRemaining( int start, int end ) throws IOException {
523        if ( end == -1 ) {
524            int length = xml.length() - start;
525            writer.write( xml, start, length );
526            okToContinue = false;
527        }
528    }
529
530
531}