DiffService.java

/*
 * Copyright (c) 2002-2023, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.wiki.service;

import fr.paris.lutece.portal.service.util.AppLogService;

import org.ccil.cowan.tagsoup.AttributesImpl;

import org.outerj.daisy.diff.HtmlCleaner;
import org.outerj.daisy.diff.XslFilter;
import org.outerj.daisy.diff.html.HTMLDiffer;
import org.outerj.daisy.diff.html.HtmlSaxDiffOutput;
import org.outerj.daisy.diff.html.TextNodeComparator;
import org.outerj.daisy.diff.html.dom.DomTreeBuilder;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;

import java.util.Locale;

import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

/**
 * Diff Service
 */
public final class DiffService
{
    private static final String XSL_OUTPUT = "fr/paris/lutece/plugins/wiki/service/output.xsl";

    /** Private constuctor */
    private DiffService( )
    {
    }

    /**
     * Get diff
     * 
     * @param strOld
     *            Old version
     * @param strNew
     *            New version
     * @return The diff
     */
    public static String getDiff( String strOld, String strNew )
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream( );
        String [ ] css = new String [ ] { };

        try
        {
            SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance( );

            TransformerHandler result = factory.newTransformerHandler( );
            result.setResult( new StreamResult( baos ) );

            XslFilter filter = new XslFilter( );

            ContentHandler postProcess = filter.xsl( result, XSL_OUTPUT );
            Locale locale = Locale.getDefault( );
            String prefix = "diff";

            HtmlCleaner cleaner = new HtmlCleaner( );

            InputSource oldSource = new InputSource( new StringReader( strOld ) );
            InputSource newSource = new InputSource( new StringReader( strNew ) );

            DomTreeBuilder oldHandler = new DomTreeBuilder( );
            cleaner.cleanAndParse( oldSource, oldHandler );

            TextNodeComparator leftComparator = new TextNodeComparator( oldHandler, locale );

            DomTreeBuilder newHandler = new DomTreeBuilder( );
            cleaner.cleanAndParse( newSource, newHandler );

            TextNodeComparator rightComparator = new TextNodeComparator( newHandler, locale );

            postProcess.startDocument( );
            postProcess.startElement( "", "diffreport", "diffreport", new AttributesImpl( ) );
            doCSS( css, postProcess );
            postProcess.startElement( "", "diff", "diff", new AttributesImpl( ) );

            HtmlSaxDiffOutput output = new HtmlSaxDiffOutput( postProcess, prefix );

            HTMLDiffer differ = new HTMLDiffer( output );
            differ.diff( leftComparator, rightComparator );
            postProcess.endElement( "", "diff", "diff" );
            postProcess.endElement( "", "diffreport", "diffreport" );
            postProcess.endDocument( );
        }
        catch( TransformerConfigurationException | IOException | SAXException ex )
        {
            AppLogService.error( "DiffService Error : " + ex.getMessage( ), ex );
        }

        String strOutput = baos.toString( );

        // Remove XML header
        strOutput = strOutput.substring( strOutput.indexOf( '>' ) + 1 );

        return strOutput;
    }

    /**
     * Process CSS
     * 
     * @param css
     *            The CSS
     * @param handler
     *            The Handler
     * @throws SAXException
     *             if an error occurs
     */
    private static void doCSS( String [ ] css, ContentHandler handler ) throws SAXException
    {
        handler.startElement( "", "css", "css", new AttributesImpl( ) );

        for ( String cssLink : css )
        {
            AttributesImpl attr = new AttributesImpl( );
            attr.addAttribute( "", "href", "href", "CDATA", cssLink );
            attr.addAttribute( "", "type", "type", "CDATA", "text/css" );
            attr.addAttribute( "", "rel", "rel", "CDATA", "stylesheet" );
            handler.startElement( "", "link", "link", attr );
            handler.endElement( "", "link", "link" );
        }

        handler.endElement( "", "css", "css" );
    }
}