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}