package org.codehaus.plexus.util.xml;
/*
* Copyright The Codehaus Foundation.
*
* Licensed 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.PrintWriter;
import java.io.Writer;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.plexus.util.StringUtils;
Implementation of XMLWriter which emits nicely formatted documents.
Version: $Id$
/**
* Implementation of XMLWriter which emits nicely formatted documents.
*
* @version $Id$
*/
public class PrettyPrintXMLWriter
implements XMLWriter
{
Line separator ("\n" on UNIX) /** Line separator ("\n" on UNIX) */
protected static final String LS = System.getProperty( "line.separator" );
private PrintWriter writer;
private LinkedList<String> elementStack = new LinkedList<String>();
private boolean tagInProgress;
private int depth;
private String lineIndenter;
private String lineSeparator;
private String encoding;
private String docType;
private boolean readyForNewLine;
private boolean tagIsEmpty;
Params: - writer – not null
- lineIndenter – could be null, but the normal way is some spaces.
/**
* @param writer not null
* @param lineIndenter could be null, but the normal way is some spaces.
*/
public PrettyPrintXMLWriter( PrintWriter writer, String lineIndenter )
{
this( writer, lineIndenter, null, null );
}
Params: - writer – not null
- lineIndenter – could be null, but the normal way is some spaces.
/**
* @param writer not null
* @param lineIndenter could be null, but the normal way is some spaces.
*/
public PrettyPrintXMLWriter( Writer writer, String lineIndenter )
{
this( new PrintWriter( writer ), lineIndenter );
}
Params: - writer – not null
/**
* @param writer not null
*/
public PrettyPrintXMLWriter( PrintWriter writer )
{
this( writer, null, null );
}
Params: - writer – not null
/**
* @param writer not null
*/
public PrettyPrintXMLWriter( Writer writer )
{
this( new PrintWriter( writer ) );
}
Params: - writer – not null
- lineIndenter – could be null, but the normal way is some spaces.
- encoding – could be null or invalid.
- doctype – could be null.
/**
* @param writer not null
* @param lineIndenter 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 lineIndenter, String encoding, String doctype )
{
this( writer, lineIndenter, LS, encoding, doctype );
}
Params: - writer – not null
- lineIndenter – could be null, but the normal way is some spaces.
- encoding – could be null or invalid.
- doctype – could be null.
/**
* @param writer not null
* @param lineIndenter 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 lineIndenter, String encoding, String doctype )
{
this( new PrintWriter( writer ), lineIndenter, encoding, doctype );
}
Params: - writer – not null
- encoding – could be null or invalid.
- doctype – could be null.
/**
* @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, " ", encoding, doctype );
}
Params: - writer – not null
- encoding – could be null or invalid.
- doctype – could be null.
/**
* @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 );
}
Params: - writer – not null
- lineIndenter – could be null, but the normal way is some spaces.
- lineSeparator – could be null, but the normal way is valid line separator ("\n" on UNIX).
- encoding – could be null or invalid.
- doctype – could be null.
/**
* @param writer not null
* @param lineIndenter could be null, but the normal way is some spaces.
* @param lineSeparator could be null, but the normal way is valid line separator ("\n" on UNIX).
* @param encoding could be null or invalid.
* @param doctype could be null.
*/
public PrettyPrintXMLWriter( PrintWriter writer, String lineIndenter, String lineSeparator, String encoding,
String doctype )
{
setWriter( writer );
setLineIndenter( lineIndenter );
setLineSeparator( lineSeparator );
setEncoding( encoding );
setDocType( doctype );
if ( doctype != null || encoding != null )
{
writeDocumentHeaders();
}
}
{@inheritDoc} /** {@inheritDoc} */
public void startElement( String name )
{
tagIsEmpty = false;
finishTag();
write( "<" );
write( name );
elementStack.addLast( name );
tagInProgress = true;
setDepth( getDepth() + 1 );
readyForNewLine = true;
tagIsEmpty = true;
}
{@inheritDoc} /** {@inheritDoc} */
public void writeText( String text )
{
writeText( text, true );
}
{@inheritDoc} /** {@inheritDoc} */
public void writeMarkup( String text )
{
writeText( text, false );
}
private void writeText( String text, boolean escapeXml )
{
readyForNewLine = false;
tagIsEmpty = false;
finishTag();
if ( escapeXml )
{
text = escapeXml( text );
}
write( StringUtils.unifyLineSeparators( text, lineSeparator ) );
}
private static final Pattern amp = Pattern.compile( "&" );
private static final Pattern lt = Pattern.compile( "<" );
private static final Pattern gt = Pattern.compile( ">" );
private static final Pattern dqoute = Pattern.compile( "\"" );
private static final Pattern sqoute = Pattern.compile( "\'" );
private static String escapeXml( String text )
{
if ( text.indexOf( '&' ) >= 0 )
{
text = amp.matcher( text ).replaceAll( "&" );
}
if ( text.indexOf( '<' ) >= 0 )
{
text = lt.matcher( text ).replaceAll( "<" );
}
if ( text.indexOf( '>' ) >= 0 )
{
text = gt.matcher( text ).replaceAll( ">" );
}
if ( text.indexOf( '"' ) >= 0 )
{
text = dqoute.matcher( text ).replaceAll( """ );
}
if ( text.indexOf( '\'' ) >= 0 )
{
text = sqoute.matcher( text ).replaceAll( "'" );
}
return text;
}
private static final String crlf_str = "\r\n";
private static final Pattern crlf = Pattern.compile( crlf_str );
private static final Pattern lowers = Pattern.compile( "([\000-\037])" );
private static String escapeXmlAttribute( String text )
{
text = escapeXml( text );
// Windows
Matcher crlfmatcher = crlf.matcher( text );
if ( text.contains( crlf_str ) )
{
text = crlfmatcher.replaceAll( " " );
}
Matcher m = lowers.matcher( text );
StringBuffer b = new StringBuffer();
while ( m.find() )
{
m = m.appendReplacement( b, "&#" + Integer.toString( m.group( 1 ).charAt( 0 ) ) + ";" );
}
m.appendTail( b );
return b.toString();
}
{@inheritDoc} /** {@inheritDoc} */
public void addAttribute( String key, String value )
{
write( " " );
write( key );
write( "=\"" );
write( escapeXmlAttribute( value ) );
write( "\"" );
}
{@inheritDoc} /** {@inheritDoc} */
public void endElement()
{
setDepth( getDepth() - 1 );
if ( tagIsEmpty )
{
write( "/" );
readyForNewLine = false;
finishTag();
elementStack.removeLast();
}
else
{
finishTag();
// see issue #51: https://github.com/codehaus-plexus/plexus-utils/issues/51
// Rationale: replaced 1 write() with string concatenations with 3 write()
// (this avoids the string concatenation optimization bug detected in Java 7)
// TODO: change the below code to a more efficient expression when the library
// be ready to target Java 8.
write( "</" );
write( elementStack.removeLast() );
write( ">" );
}
readyForNewLine = true;
}
Write a string to the underlying writer
Params: - str –
/**
* Write a string to the underlying writer
*
* @param str
*/
private void write( String str )
{
getWriter().write( str );
}
private void finishTag()
{
if ( tagInProgress )
{
write( ">" );
}
tagInProgress = false;
if ( readyForNewLine )
{
endOfLine();
}
readyForNewLine = false;
tagIsEmpty = false;
}
Get the string used as line indenter
Returns: the line indenter
/**
* Get the string used as line indenter
*
* @return the line indenter
*/
protected String getLineIndenter()
{
return lineIndenter;
}
Set the string used as line indenter
Params: - lineIndenter – new line indenter, could be null, but the normal way is some spaces.
/**
* Set the string used as line indenter
*
* @param lineIndenter new line indenter, could be null, but the normal way is some spaces.
*/
protected void setLineIndenter( String lineIndenter )
{
this.lineIndenter = lineIndenter;
}
Get the string used as line separator or LS if not set.
See Also: Returns: the line separator
/**
* Get the string used as line separator or LS if not set.
*
* @return the line separator
* @see #LS
*/
protected String getLineSeparator()
{
return lineSeparator;
}
Set the string used as line separator
Params: - lineSeparator – new line separator, could be null but the normal way is valid line separator ("\n" on UNIX).
/**
* Set the string used as line separator
*
* @param lineSeparator new line separator, could be null but the normal way is valid line separator ("\n" on UNIX).
*/
protected void setLineSeparator( String lineSeparator )
{
this.lineSeparator = lineSeparator;
}
Write the end of line character (using specified line separator) and start new line with indentation
See Also: - getLineIndenter()
- getLineSeparator()
/**
* Write the end of line character (using specified line separator) and start new line with indentation
*
* @see #getLineIndenter()
* @see #getLineSeparator()
*/
protected void endOfLine()
{
write( getLineSeparator() );
for ( int i = 0; i < getDepth(); i++ )
{
write( getLineIndenter() );
}
}
private void writeDocumentHeaders()
{
write( "<?xml version=\"1.0\"" );
if ( getEncoding() != null )
{
write( " encoding=\"" + getEncoding() + "\"" );
}
write( "?>" );
endOfLine();
if ( getDocType() != null )
{
write( "<!DOCTYPE " );
write( getDocType() );
write( ">" );
endOfLine();
}
}
Set the underlying writer
Params: - writer – not null writer
/**
* Set the underlying writer
*
* @param writer not null writer
*/
protected void setWriter( PrintWriter writer )
{
if ( writer == null )
{
throw new IllegalArgumentException( "writer could not be null" );
}
this.writer = writer;
}
Get the underlying writer
Returns: the underlying writer
/**
* Get the underlying writer
*
* @return the underlying writer
*/
protected PrintWriter getWriter()
{
return writer;
}
Set the depth in the xml indentation
Params: - depth – new depth
/**
* Set the depth in the xml indentation
*
* @param depth new depth
*/
protected void setDepth( int depth )
{
this.depth = depth;
}
Get the current depth in the xml indentation
Returns: the current depth
/**
* Get the current depth in the xml indentation
*
* @return the current depth
*/
protected int getDepth()
{
return depth;
}
Set the encoding in the xml
Params: - encoding – new encoding
/**
* Set the encoding in the xml
*
* @param encoding new encoding
*/
protected void setEncoding( String encoding )
{
this.encoding = encoding;
}
Get the current encoding in the xml
Returns: the current encoding
/**
* Get the current encoding in the xml
*
* @return the current encoding
*/
protected String getEncoding()
{
return encoding;
}
Set the docType in the xml
Params: - docType – new docType
/**
* Set the docType in the xml
*
* @param docType new docType
*/
protected void setDocType( String docType )
{
this.docType = docType;
}
Get the docType in the xml
Returns: the current docType
/**
* Get the docType in the xml
*
* @return the current docType
*/
protected String getDocType()
{
return docType;
}
Returns: the current elementStack;
/**
* @return the current elementStack;
*/
protected LinkedList<String> getElementStack()
{
return elementStack;
}
}