| package org.apache.maven.shared.utils.xml; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import org.apache.maven.shared.utils.Os; |
| |
| /** |
| * XMLWriter with nice indentation |
| */ |
| /** |
| * @author kama |
| * |
| */ |
| public class PrettyPrintXMLWriter |
| implements XMLWriter |
| { |
| private static final char[] CLOSE_1 = "/>".toCharArray(); |
| |
| private static final char[] CLOSE_2 = "</".toCharArray(); |
| |
| private static final char[] DEFAULT_LINE_INDENT = new char[]{ ' ', ' ' }; |
| |
| private PrintWriter writer; |
| |
| private ArrayList<String> elementStack = new ArrayList<String>(); |
| |
| private boolean processingElement = false; |
| |
| private boolean documentStarted = false; |
| |
| private boolean endOnSameLine = false; |
| |
| private int depth = 0; |
| |
| private char[] lineIndent; |
| |
| private char[] lineSeparator; |
| |
| private String encoding; |
| |
| private String docType; |
| |
| /** |
| * @param writer not null |
| * @param lineIndent could be null, but the normal way is some spaces. |
| */ |
| public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent ) |
| { |
| this( writer, lineIndent, null, null ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param lineIndent could be null, but the normal way is some spaces. |
| */ |
| public PrettyPrintXMLWriter( Writer writer, String lineIndent ) |
| { |
| this( new PrintWriter( writer ), lineIndent ); |
| } |
| |
| /** |
| * @param writer not null |
| */ |
| public PrettyPrintXMLWriter( PrintWriter writer ) |
| { |
| this( writer, null, null ); |
| } |
| |
| /** |
| * @param writer not null |
| */ |
| public PrettyPrintXMLWriter( Writer writer ) |
| { |
| this( new PrintWriter( writer ) ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param lineIndent could be null, but the normal way is some spaces. |
| * @param encoding could be null or invalid. |
| * @param doctype could be null. |
| */ |
| public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String encoding, String doctype ) |
| { |
| this( writer, lineIndent.toCharArray(), Os.LINE_SEP.toCharArray(), encoding, doctype ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param lineIndent could be null, but the normal way is some spaces. |
| * @param encoding could be null or invalid. |
| * @param doctype could be null. |
| */ |
| public PrettyPrintXMLWriter( Writer writer, String lineIndent, String encoding, String doctype ) |
| { |
| this( new PrintWriter( writer ), lineIndent, encoding, doctype ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param encoding could be null or invalid. |
| * @param doctype could be null. |
| */ |
| public PrettyPrintXMLWriter( PrintWriter writer, String encoding, String doctype ) |
| { |
| this( writer, DEFAULT_LINE_INDENT, Os.LINE_SEP.toCharArray(), encoding, doctype ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param encoding could be null or invalid. |
| * @param doctype could be null. |
| */ |
| public PrettyPrintXMLWriter( Writer writer, String encoding, String doctype ) |
| { |
| this( new PrintWriter( writer ), encoding, doctype ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param lineIndent could be null, but the normal way is some spaces. |
| * @param lineSeparator could be null, but the normal way is valid line separator |
| * @param encoding could be null or the encoding to use. |
| * @param doctype could be null. |
| */ |
| public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String lineSeparator, String encoding, |
| String doctype ) |
| { |
| this( writer, lineIndent.toCharArray(), lineSeparator.toCharArray(), encoding, doctype ); |
| } |
| |
| /** |
| * @param writer not null |
| * @param lineIndent could be null, but the normal way is some spaces. |
| * @param lineSeparator could be null, but the normal way is valid line separator |
| * @param encoding could be null or the encoding to use. |
| * @param doctype could be null. |
| */ |
| private PrettyPrintXMLWriter( PrintWriter writer, char[] lineIndent, char[] lineSeparator, String encoding, |
| String doctype ) |
| { |
| super(); |
| this.writer = writer; |
| this.lineIndent = lineIndent; |
| this.lineSeparator = lineSeparator; |
| this.encoding = encoding; |
| this.docType = doctype; |
| |
| depth = 0; |
| |
| // Fail early with assertions enabled. Issue is in the calling code not having checked for any errors. |
| assert !writer.checkError() : "Unexpected error state PrintWriter passed to PrettyPrintXMLWriter."; |
| } |
| |
| @Override |
| public void addAttribute( String key, String value ) throws IOException |
| { |
| if ( !processingElement ) |
| { |
| throw new IllegalStateException( "currently processing no element" ); |
| } |
| |
| writer.write( ' ' ); |
| writer.write( key ); |
| writer.write( '=' ); |
| XMLEncode.xmlEncodeTextAsPCDATA( value, true, '"', writer ); |
| if ( writer.checkError() ) |
| { |
| throw new IOException( "Failure adding attribute '" + key + "' with value '" + value + "'" ); |
| } |
| } |
| |
| @Override |
| public void setEncoding( String encoding ) |
| { |
| if ( documentStarted ) |
| { |
| throw new IllegalStateException( "Document headers already written!" ); |
| } |
| |
| this.encoding = encoding; |
| } |
| |
| @Override |
| public void setDocType( String docType ) |
| { |
| if ( documentStarted ) |
| { |
| throw new IllegalStateException( "Document headers already written!" ); |
| } |
| |
| this.docType = docType; |
| } |
| |
| /** |
| * @param lineSeparator The line separator to be used. |
| */ |
| public void setLineSeparator( String lineSeparator ) |
| { |
| if ( documentStarted ) |
| { |
| throw new IllegalStateException( "Document headers already written!" ); |
| } |
| |
| this.lineSeparator = lineSeparator.toCharArray(); |
| } |
| |
| /** |
| * @param lineIndentParameter The line indent parameter. |
| */ |
| public void setLineIndenter( String lineIndentParameter ) |
| { |
| if ( documentStarted ) |
| { |
| throw new IllegalStateException( "Document headers already written!" ); |
| } |
| |
| this.lineIndent = lineIndentParameter.toCharArray(); |
| } |
| |
| @Override |
| public void startElement( String elementName ) throws IOException |
| { |
| boolean firstLine = ensureDocumentStarted(); |
| |
| completePreviouslyOpenedElement(); |
| |
| if ( !firstLine ) |
| { |
| newLine(); |
| } |
| |
| writer.write( '<' ); |
| writer.write( elementName ); |
| if ( writer.checkError() ) |
| { |
| throw new IOException( "Failure starting element '" + elementName + "'." ); |
| } |
| |
| processingElement = true; |
| |
| elementStack.add( depth++, elementName ); |
| } |
| |
| @Override |
| public void writeText( String text ) throws IOException |
| { |
| ensureDocumentStarted(); |
| |
| completePreviouslyOpenedElement(); |
| |
| XMLEncode.xmlEncodeText( text, writer ); |
| |
| endOnSameLine = true; |
| |
| if ( writer.checkError() ) |
| { |
| throw new IOException( "Failure writing text." ); |
| } |
| } |
| |
| @Override |
| public void writeMarkup( String markup ) throws IOException |
| { |
| ensureDocumentStarted(); |
| |
| completePreviouslyOpenedElement(); |
| |
| writer.write( markup ); |
| |
| if ( writer.checkError() ) |
| { |
| throw new IOException( "Failure writing markup." ); |
| } |
| } |
| |
| @Override |
| public void endElement() throws IOException |
| { |
| String chars = elementStack.get( --depth ); |
| if ( processingElement ) |
| { |
| // this means we don't have any content yet so we just add a /> |
| writer.write( CLOSE_1 ); |
| |
| processingElement = false; |
| } |
| else |
| { |
| if ( !endOnSameLine ) |
| { |
| newLine(); |
| } |
| |
| // otherwise we need a full closing tag for that element |
| writer.write( CLOSE_2 ); |
| writer.write( chars ); |
| writer.write( '>' ); |
| } |
| |
| endOnSameLine = false; |
| |
| if ( writer.checkError() ) |
| { |
| throw new IOException( "Failure ending element." ); |
| } |
| } |
| |
| /** |
| * Write the documents if not already done. |
| * |
| * @return <code>true</code> if the document headers have freshly been written. |
| */ |
| private boolean ensureDocumentStarted() |
| { |
| if ( !documentStarted ) |
| { |
| if ( docType != null || encoding != null ) |
| { |
| writeDocumentHeader(); |
| } |
| |
| documentStarted = true; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private void writeDocumentHeader() |
| { |
| writer.write( "<?xml version=\"1.0\"" ); |
| |
| if ( encoding != null ) |
| { |
| writer.write( " encoding=\"" ); |
| writer.write( encoding ); |
| writer.write( '\"' ); |
| } |
| |
| writer.write( "?>" ); |
| |
| newLine(); |
| |
| if ( docType != null ) |
| { |
| writer.write( "<!DOCTYPE " ); |
| writer.write( docType ); |
| writer.write( '>' ); |
| newLine(); |
| } |
| } |
| |
| private void newLine() |
| { |
| writer.write( lineSeparator ); |
| |
| for ( int i = 0; i < depth; i++ ) |
| { |
| writer.write( lineIndent ); |
| } |
| } |
| |
| private void completePreviouslyOpenedElement() |
| { |
| if ( processingElement ) |
| { |
| writer.write( '>' ); |
| processingElement = false; |
| } |
| } |
| |
| } |