001/*
002 * (C) Copyright 2006-2008 Nuxeo SA (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.lang.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 = null;
071        Blob rightBlob = null;
072        BlobHolder leftBlobHolder = null;
073        BlobHolder rightBlobHolder = null;
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
095        try {
096            List<Blob> blobResults = new ArrayList<Blob>();
097            StringWriter sw = new StringWriter();
098
099            SAXTransformerFactory stf = (SAXTransformerFactory) TransformerFactory.newInstance();
100            TransformerHandler transformHandler = stf.newTransformerHandler();
101            transformHandler.setResult(new StreamResult(sw));
102
103            XslFilter htmlHeaderXslFilter = new XslFilter();
104
105            StringBuilder sb = new StringBuilder("xslfilter/htmldiffheader");
106            sb.append("_");
107            sb.append(locale.getLanguage());
108            sb.append(".xsl");
109            String htmlHeaderXslPath = sb.toString();
110            ContentHandler postProcess;
111            try {
112                postProcess = htmlHeaderXslFilter.xsl(transformHandler, htmlHeaderXslPath);
113            } catch (IllegalStateException ise) {
114                LOGGER.error(String.format(
115                        "Could not find the HTML diff header xsl file '%s', falling back on the default one.",
116                        htmlHeaderXslPath), ise);
117                postProcess = htmlHeaderXslFilter.xsl(transformHandler, "xslfilter/htmldiffheader.xsl");
118            }
119
120            String prefix = "diff";
121
122            HtmlCleaner cleaner = new HtmlCleaner();
123
124            InputSource leftIS = new InputSource(leftBlob.getStream());
125            InputSource rightIS = new InputSource(rightBlob.getStream());
126
127            DomTreeBuilder leftHandler = new DomTreeBuilder();
128            cleaner.cleanAndParse(leftIS, leftHandler);
129            TextNodeComparator leftComparator = new TextNodeComparator(leftHandler, locale);
130
131            DomTreeBuilder rightHandler = new DomTreeBuilder();
132            cleaner.cleanAndParse(rightIS, rightHandler);
133            TextNodeComparator rightComparator = new TextNodeComparator(rightHandler, locale);
134
135            postProcess.startDocument();
136            postProcess.startElement("", "diffreport", "diffreport", new AttributesImpl());
137            postProcess.startElement("", "diff", "diff", new AttributesImpl());
138            HtmlSaxDiffOutput output = new HtmlSaxDiffOutput(postProcess, prefix);
139
140            HTMLDiffer differ = new HTMLDiffer(output);
141            differ.diff(leftComparator, rightComparator);
142
143            postProcess.endElement("", "diff", "diff");
144            postProcess.endElement("", "diffreport", "diffreport");
145            postProcess.endDocument();
146
147            String stringBlob = sw.toString().replaceAll(NUXEO_DEFAULT_CONTEXT_PATH,
148                    VirtualHostHelper.getContextPathProperty());
149            Blob mainBlob = Blobs.createBlob(stringBlob);
150            sw.close();
151
152            mainBlob.setFilename("contentDiff.html");
153            mainBlob.setMimeType("text/html");
154
155            blobResults.add(mainBlob);
156            return blobResults;
157
158        } catch (TransformerConfigurationException | SAXException | IOException e) {
159            throw new ContentDiffException(e);
160        }
161    }
162}