001/*
002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Alexandre Russel
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.diff.content.adapter;
023
024import java.io.IOException;
025import java.io.StringWriter;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Locale;
029
030import javax.xml.transform.TransformerConfigurationException;
031import javax.xml.transform.TransformerFactory;
032import javax.xml.transform.sax.SAXTransformerFactory;
033import javax.xml.transform.sax.TransformerHandler;
034import javax.xml.transform.stream.StreamResult;
035
036import org.apache.commons.lang3.StringUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.nuxeo.ecm.core.api.Blob;
040import org.nuxeo.ecm.core.api.Blobs;
041import org.nuxeo.ecm.core.api.DocumentModel;
042import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
043import org.nuxeo.ecm.diff.content.ContentDiffException;
044import org.nuxeo.ecm.diff.content.ContentDiffHelper;
045import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
046import org.outerj.daisy.diff.HtmlCleaner;
047import org.outerj.daisy.diff.XslFilter;
048import org.outerj.daisy.diff.html.HTMLDiffer;
049import org.outerj.daisy.diff.html.HtmlSaxDiffOutput;
050import org.outerj.daisy.diff.html.TextNodeComparator;
051import org.outerj.daisy.diff.html.dom.DomTreeBuilder;
052import org.xml.sax.ContentHandler;
053import org.xml.sax.InputSource;
054import org.xml.sax.SAXException;
055import org.xml.sax.helpers.AttributesImpl;
056
057/**
058 * @author Antoine Taillefer
059 * @since 5.6
060 */
061public class HtmlContentDiffer implements MimeTypeContentDiffer {
062
063    private static final Log LOGGER = LogFactory.getLog(HtmlContentDiffer.class);
064
065    protected static final String NUXEO_DEFAULT_CONTEXT_PATH = "/nuxeo";
066
067    @Override
068    public List<Blob> getContentDiff(DocumentModel leftDoc, DocumentModel rightDoc, String xpath, Locale locale)
069            throws ContentDiffException {
070        Blob leftBlob;
071        Blob rightBlob;
072        BlobHolder leftBlobHolder;
073        BlobHolder rightBlobHolder;
074        if (StringUtils.isBlank(xpath) || ContentDiffHelper.DEFAULT_XPATH.equals(xpath)) {
075            leftBlobHolder = leftDoc.getAdapter(BlobHolder.class);
076            rightBlobHolder = rightDoc.getAdapter(BlobHolder.class);
077        } else {
078            leftBlobHolder = ContentDiffHelper.getBlobHolder(leftDoc, xpath);
079            rightBlobHolder = ContentDiffHelper.getBlobHolder(rightDoc, xpath);
080        }
081        if (leftBlobHolder == null || rightBlobHolder == null) {
082            throw new ContentDiffException("Can not make a content diff of documents without a blob");
083        }
084        leftBlob = leftBlobHolder.getBlob();
085        rightBlob = rightBlobHolder.getBlob();
086        if (leftBlob == null || rightBlob == null) {
087            throw new ContentDiffException("Can not make a content diff of documents without a blob");
088        }
089        return getContentDiff(leftBlob, rightBlob, locale);
090    }
091
092    @Override
093    public List<Blob> getContentDiff(Blob leftBlob, Blob rightBlob, Locale locale) throws ContentDiffException {
094        try {
095            List<Blob> blobResults = new ArrayList<>();
096            StringWriter sw = new StringWriter();
097
098            SAXTransformerFactory stf = (SAXTransformerFactory) TransformerFactory.newInstance();
099            TransformerHandler transformHandler = stf.newTransformerHandler();
100            transformHandler.setResult(new StreamResult(sw));
101
102            XslFilter htmlHeaderXslFilter = new XslFilter();
103
104            String htmlHeaderXslPath = String.format("xslfilter/htmldiffheader_%s.xsl", locale.getLanguage());
105            ContentHandler postProcess;
106            try {
107                postProcess = htmlHeaderXslFilter.xsl(transformHandler, htmlHeaderXslPath);
108            } catch (IllegalStateException ise) {
109                LOGGER.error(String.format(
110                        "Could not find the HTML diff header xsl file '%s', falling back on the default one.",
111                        htmlHeaderXslPath), ise);
112                postProcess = htmlHeaderXslFilter.xsl(transformHandler, "xslfilter/htmldiffheader.xsl");
113            }
114
115            String prefix = "diff";
116
117            HtmlCleaner cleaner = new HtmlCleaner();
118
119            InputSource leftIS = new InputSource(leftBlob.getStream());
120            InputSource rightIS = new InputSource(rightBlob.getStream());
121
122            DomTreeBuilder leftHandler = new DomTreeBuilder();
123            cleaner.cleanAndParse(leftIS, leftHandler);
124            TextNodeComparator leftComparator = new TextNodeComparator(leftHandler, locale);
125
126            DomTreeBuilder rightHandler = new DomTreeBuilder();
127            cleaner.cleanAndParse(rightIS, rightHandler);
128            TextNodeComparator rightComparator = new TextNodeComparator(rightHandler, locale);
129
130            postProcess.startDocument();
131            postProcess.startElement("", "diffreport", "diffreport", new AttributesImpl());
132            postProcess.startElement("", "diff", "diff", new AttributesImpl());
133            HtmlSaxDiffOutput output = new HtmlSaxDiffOutput(postProcess, prefix);
134
135            HTMLDiffer differ = new HTMLDiffer(output);
136            differ.diff(leftComparator, rightComparator);
137
138            postProcess.endElement("", "diff", "diff");
139            postProcess.endElement("", "diffreport", "diffreport");
140            postProcess.endDocument();
141
142            String stringBlob = sw.toString().replaceAll(NUXEO_DEFAULT_CONTEXT_PATH,
143                    VirtualHostHelper.getContextPathProperty());
144            Blob mainBlob = Blobs.createBlob(stringBlob);
145            sw.close();
146
147            mainBlob.setFilename("contentDiff.html");
148            mainBlob.setMimeType("text/html");
149
150            blobResults.add(mainBlob);
151            return blobResults;
152
153        } catch (TransformerConfigurationException | SAXException | IOException e) {
154            throw new ContentDiffException(e);
155        }
156    }
157}