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 static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
025
026import java.io.IOException;
027import java.io.StringWriter;
028import java.util.ArrayList;
029import java.util.List;
030import java.util.Locale;
031
032import javax.xml.transform.TransformerConfigurationException;
033import javax.xml.transform.TransformerFactory;
034import javax.xml.transform.sax.SAXTransformerFactory;
035import javax.xml.transform.sax.TransformerHandler;
036import javax.xml.transform.stream.StreamResult;
037
038import org.apache.commons.lang3.StringUtils;
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.nuxeo.ecm.core.api.Blob;
042import org.nuxeo.ecm.core.api.Blobs;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
045import org.nuxeo.ecm.diff.content.ContentDiffException;
046import org.nuxeo.ecm.diff.content.ContentDiffHelper;
047import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
048import org.outerj.daisy.diff.HtmlCleaner;
049import org.outerj.daisy.diff.XslFilter;
050import org.outerj.daisy.diff.html.HTMLDiffer;
051import org.outerj.daisy.diff.html.HtmlSaxDiffOutput;
052import org.outerj.daisy.diff.html.TextNodeComparator;
053import org.outerj.daisy.diff.html.dom.DomTreeBuilder;
054import org.xml.sax.ContentHandler;
055import org.xml.sax.InputSource;
056import org.xml.sax.SAXException;
057import org.xml.sax.helpers.AttributesImpl;
058
059/**
060 * @author Antoine Taillefer
061 * @since 5.6
062 */
063public class HtmlContentDiffer implements MimeTypeContentDiffer {
064
065    private static final Log LOGGER = LogFactory.getLog(HtmlContentDiffer.class);
066
067    protected static final String NUXEO_DEFAULT_CONTEXT_PATH = "/nuxeo";
068
069    @Override
070    public List<Blob> getContentDiff(DocumentModel leftDoc, DocumentModel rightDoc, String xpath, Locale locale)
071            throws ContentDiffException {
072        Blob leftBlob;
073        Blob rightBlob;
074        BlobHolder leftBlobHolder;
075        BlobHolder rightBlobHolder;
076        if (StringUtils.isBlank(xpath) || ContentDiffHelper.DEFAULT_XPATH.equals(xpath)) {
077            leftBlobHolder = leftDoc.getAdapter(BlobHolder.class);
078            rightBlobHolder = rightDoc.getAdapter(BlobHolder.class);
079        } else {
080            leftBlobHolder = ContentDiffHelper.getBlobHolder(leftDoc, xpath);
081            rightBlobHolder = ContentDiffHelper.getBlobHolder(rightDoc, xpath);
082        }
083        if (leftBlobHolder == null || rightBlobHolder == null) {
084            throw new ContentDiffException("Can not make a content diff of documents without a blob");
085        }
086        leftBlob = leftBlobHolder.getBlob();
087        rightBlob = rightBlobHolder.getBlob();
088        if (leftBlob == null || rightBlob == null) {
089            throw new ContentDiffException("Can not make a content diff of documents without a blob");
090        }
091        return getContentDiff(leftBlob, rightBlob, locale);
092    }
093
094    @Override
095    public List<Blob> getContentDiff(Blob leftBlob, Blob rightBlob, Locale locale) throws ContentDiffException {
096        try {
097            List<Blob> blobResults = new ArrayList<>();
098            StringWriter sw = new StringWriter();
099
100            TransformerFactory factory = TransformerFactory.newInstance();
101            factory.setFeature(FEATURE_SECURE_PROCESSING, true);
102            SAXTransformerFactory stf = (SAXTransformerFactory) factory;
103            TransformerHandler transformHandler = stf.newTransformerHandler();
104            transformHandler.setResult(new StreamResult(sw));
105
106            XslFilter htmlHeaderXslFilter = new XslFilter();
107
108            String htmlHeaderXslPath = String.format("xslfilter/htmldiffheader_%s.xsl", locale.getLanguage());
109            ContentHandler postProcess;
110            try {
111                postProcess = htmlHeaderXslFilter.xsl(transformHandler, htmlHeaderXslPath);
112            } catch (IllegalStateException ise) {
113                LOGGER.error(String.format(
114                        "Could not find the HTML diff header xsl file '%s', falling back on the default one.",
115                        htmlHeaderXslPath), ise);
116                postProcess = htmlHeaderXslFilter.xsl(transformHandler, "xslfilter/htmldiffheader.xsl");
117            }
118
119            String prefix = "diff";
120
121            HtmlCleaner cleaner = new HtmlCleaner();
122
123            InputSource leftIS = new InputSource(leftBlob.getStream());
124            InputSource rightIS = new InputSource(rightBlob.getStream());
125
126            DomTreeBuilder leftHandler = new DomTreeBuilder();
127            cleaner.cleanAndParse(leftIS, leftHandler);
128            TextNodeComparator leftComparator = new TextNodeComparator(leftHandler, locale);
129
130            DomTreeBuilder rightHandler = new DomTreeBuilder();
131            cleaner.cleanAndParse(rightIS, rightHandler);
132            TextNodeComparator rightComparator = new TextNodeComparator(rightHandler, locale);
133
134            postProcess.startDocument();
135            postProcess.startElement("", "diffreport", "diffreport", new AttributesImpl());
136            postProcess.startElement("", "diff", "diff", new AttributesImpl());
137            HtmlSaxDiffOutput output = new HtmlSaxDiffOutput(postProcess, prefix);
138
139            HTMLDiffer differ = new HTMLDiffer(output);
140            differ.diff(leftComparator, rightComparator);
141
142            postProcess.endElement("", "diff", "diff");
143            postProcess.endElement("", "diffreport", "diffreport");
144            postProcess.endDocument();
145
146            String stringBlob = sw.toString().replaceAll(NUXEO_DEFAULT_CONTEXT_PATH,
147                    VirtualHostHelper.getContextPathProperty());
148            Blob mainBlob = Blobs.createBlob(stringBlob);
149            sw.close();
150
151            mainBlob.setFilename("contentDiff.html");
152            mainBlob.setMimeType("text/html");
153
154            blobResults.add(mainBlob);
155            return blobResults;
156
157        } catch (TransformerConfigurationException | SAXException | IOException e) {
158            throw new ContentDiffException(e);
159        }
160    }
161}