package org.ops4j.pax.construct.util; /* * Copyright 2007 Stuart McCulloch, Alin Dreghiciu * * 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.IOException; import java.io.Writer; import java.util.Arrays; import java.util.List; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.pull.MXParser; import org.codehaus.plexus.util.xml.pull.MXSerializer; import org.codehaus.plexus.util.xml.pull.XmlPullParser; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.codehaus.plexus.util.xml.pull.XmlSerializer; /** * Provide XML parser and serializer that work in tandem to preserve comments (and some formatting) */ public final class RoundTripXml { /** * Hide constructor for utility class */ private RoundTripXml() { /* * nothing to do */ } /** * @return round-trip XML parser */ public static XmlPullParser createParser() { return new RoundTripParser(); } /** * @return round-trip XML serializer */ public static XmlSerializer createSerializer() { return new RoundTripSerializer(); } /** * Customize parser to preserve comments as special tags */ private static final class RoundTripParser extends MXParser { /** * Are we parsing a comment? */ private boolean m_handleComment = false; /** * Use default config */ RoundTripParser() { super(); } /** * {@inheritDoc} */ public int next() throws XmlPullParserException, IOException { if( m_handleComment ) { // end pseudo-tag m_handleComment = false; return END_TAG; } int type = super.nextToken(); if( COMMENT == eventType && 0 < depth ) { // start pseudo-tag m_handleComment = true; return START_TAG; } return type; } /** * {@inheritDoc} */ public String getName() { if( m_handleComment ) { // use comment text as name return "!--" + getText(); } return super.getName(); } /** * {@inheritDoc} */ public boolean isEmptyElementTag() throws XmlPullParserException { if( m_handleComment ) { // comments don't have embedded tags return true; } return super.isEmptyElementTag(); } /** * {@inheritDoc} */ public int getAttributeCount() { if( m_handleComment ) { // comments don't have attributes return 0; } return super.getAttributeCount(); } } /** * Customize serializer to output comments stored in special tags */ private static final class RoundTripSerializer extends MXSerializer { private static final String NEWLINE = System.getProperty( "line.separator" ); /** * Are we serializing a comment? */ private boolean m_handleComment = false; /** * Tweak config to use standard Maven layout */ RoundTripSerializer() { super(); setProperty( PROPERTY_SERIALIZER_INDENTATION, " " ); setProperty( PROPERTY_SERIALIZER_LINE_SEPARATOR, NEWLINE ); } /** * {@inheritDoc} */ public XmlSerializer startTag( String namespace, String name ) throws IOException { // special comment tag if( name.startsWith( "!--" ) ) { m_handleComment = true; closeStartTag(); if( getDepth() <= 1 ) { // newline before top-level comments out.write( lineSeparator ); } writeIndent(); String text = name.substring( 3 ); comment( text.replaceAll( "[\r\n\f]", NEWLINE ) ); return this; } List stickyTags = Arrays.asList( new String[] { "groupId", "artifactId", "version" } ); if( getDepth() <= 1 && !stickyTags.contains( name ) ) { // newline before top-level groups closeStartTag(); out.write( lineSeparator ); } if( m_handleComment ) { m_handleComment = false; writeIndent(); } return super.startTag( namespace, name ); } /** * {@inheritDoc} */ public XmlSerializer endTag( String namespace, String name ) throws IOException { if( !name.startsWith( "!--" ) ) { if( getDepth() == 1 ) { // newline after final group out.write( lineSeparator ); } if( m_handleComment ) { m_handleComment = false; --depth; writeIndent(); ++depth; } super.endTag( namespace, name ); } return this; } /** * {@inheritDoc} */ public XmlSerializer attribute( String namespace, String name, String value ) throws IOException { // don't output any internal attributes used when merging XML if( !Xpp3Dom.CHILDREN_COMBINATION_MODE_ATTRIBUTE.equals( name ) ) { return super.attribute( namespace, name, value ); } return this; } /** * {@inheritDoc} */ protected void writeElementContent( String text, Writer writer ) throws IOException { super.writeElementContent( text.trim(), writer ); } } }