blob: a4861f37eeedb9a1edc0129c55acacba52a25026 [file] [log] [blame]
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 org.apache.maven.shared.utils.xml.pull.XmlPullParserException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.annotation.Nonnull;
import javax.annotation.WillClose;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* @author Kristian Rosenvold
*/
public class Xpp3DomBuilder
{
private static final boolean DEFAULT_TRIM = true;
/**
* @param reader {@link Reader}
* @return the built dom.
* @throws XmlPullParserException in case of an error.
*/
public static Xpp3Dom build( @WillClose @Nonnull Reader reader )
throws XmlPullParserException
{
return build( reader, DEFAULT_TRIM );
}
/**
* @param is {@link InputStream}
* @param encoding The encoding.
* @return the built dom.
* @throws XmlPullParserException in case of an error.
*/
public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding )
throws XmlPullParserException
{
return build( is, encoding, DEFAULT_TRIM );
}
/**
* @param is {@link InputStream}
* @param encoding The encoding.
* @param trim true/false.
* @return the built dom.
* @throws XmlPullParserException in case of an error.
*/
public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding, boolean trim )
throws XmlPullParserException
{
try
{
Reader reader = new InputStreamReader( is, encoding );
return build( reader, trim );
}
catch ( UnsupportedEncodingException e )
{
throw new RuntimeException( e );
}
}
/**
* @param reader {@link Reader}
* @param trim true/false.
* @return the built dom.
* @throws XmlPullParserException in case of an error.
*/
public static Xpp3Dom build( @WillClose Reader reader, boolean trim )
throws XmlPullParserException
{
try ( Reader r = reader )
{
DocHandler docHandler = parseSax( new InputSource( r ), trim );
return docHandler.result;
}
catch ( final IOException e )
{
throw new XmlPullParserException( e );
}
}
private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
throws XmlPullParserException
{
try
{
DocHandler ch = new DocHandler( trim );
XMLReader parser = createXmlReader();
parser.setContentHandler( ch );
parser.parse( inputSource );
return ch;
}
catch ( IOException e )
{
throw new XmlPullParserException( e );
}
catch ( SAXException e )
{
throw new XmlPullParserException( e );
}
}
private static XMLReader createXmlReader()
throws SAXException
{
XMLReader comSunXmlReader = instantiate( "com.sun.org.apache.xerces.internal.parsers.SAXParser" );
if ( comSunXmlReader != null )
{
return comSunXmlReader;
}
String key = "org.xml.sax.driver";
String oldParser = System.getProperty( key );
System.clearProperty( key ); // There's a "slight" problem with this an parallel maven: It does not work ;)
try
{
return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
}
finally
{
if ( oldParser != null )
{
System.setProperty( key, oldParser );
}
}
}
private static XMLReader instantiate( String s )
{
try
{
Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass( s );
return (XMLReader) aClass.newInstance();
}
catch ( ClassNotFoundException e )
{
return null;
}
catch ( InstantiationException e )
{
return null;
}
catch ( IllegalAccessException e )
{
return null;
}
}
private static class DocHandler
extends DefaultHandler
{
private final List<Xpp3Dom> elemStack = new ArrayList<>();
private final List<StringBuilder> values = new ArrayList<>();
// Todo: Use these for something smart !
private final List<SAXParseException> warnings = new ArrayList<>();
private final List<SAXParseException> errors = new ArrayList<>();
private final List<SAXParseException> fatals = new ArrayList<>();
Xpp3Dom result = null;
private final boolean trim;
private boolean spacePreserve = false;
DocHandler( boolean trim )
{
this.trim = trim;
}
@Override
public void startElement( String uri, String localName, String qName, Attributes attributes )
throws SAXException
{
spacePreserve = false;
Xpp3Dom child = new Xpp3Dom( localName );
attachToParent( child );
pushOnStack( child );
// Todo: Detecting tags that close immediately seem to be impossible in sax ?
// http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
values.add( new StringBuilder() );
int size = attributes.getLength();
for ( int i = 0; i < size; i++ )
{
String name = attributes.getQName( i );
String value = attributes.getValue( i );
child.setAttribute( name, value );
spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
}
}
private boolean pushOnStack( Xpp3Dom child )
{
return elemStack.add( child );
}
private void attachToParent( Xpp3Dom child )
{
int depth = elemStack.size();
if ( depth > 0 )
{
elemStack.get( depth - 1 ).addChild( child );
}
}
@Override
public void warning( SAXParseException e )
throws SAXException
{
warnings.add( e );
}
@Override
public void error( SAXParseException e )
throws SAXException
{
errors.add( e );
}
@Override
public void fatalError( SAXParseException e )
throws SAXException
{
fatals.add( e );
}
private Xpp3Dom pop()
{
int depth = elemStack.size() - 1;
return elemStack.remove( depth );
}
@Override
public void endElement( String uri, String localName, String qName )
throws SAXException
{
int depth = elemStack.size() - 1;
Xpp3Dom element = pop();
/* this Object could be null if it is a singleton tag */
Object accumulatedValue = values.remove( depth );
if ( element.getChildCount() == 0 )
{
if ( accumulatedValue == null )
{
element.setValue( "" ); // null in xpp3dom, but we don't do that around here
}
else
{
element.setValue( accumulatedValue.toString() );
}
}
if ( depth == 0 )
{
result = element;
}
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
String text = new String( ch, start, length );
appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
}
private void appendToTopValue( String toAppend )
{
// noinspection MismatchedQueryAndUpdateOfStringBuilder
StringBuilder stringBuilder = values.get( values.size() - 1 );
stringBuilder.append( toAppend );
}
}
}