blob: a84ef9ea72e50b75b64c1bbbc3b246704d2a372b [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2016 Atos Origin.
*
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Anne Haugommard (Atos Origin) anne.haugommard@atosorigin.com - Initial API and implementation
* Papa Malick Wade (Atos Origin) papa-malick.wade@atosorigin.com - extension the format of diagrams
* Antonio Campesino Robles (Ericsson) antonio.campesino.robles@ericsson.com - Bug 455640
*****************************************************************************/
package org.eclipse.gendoc.services.docx;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.gendoc.document.parser.documents.docx.DocxDocument;
import org.eclipse.gendoc.documents.AdditionalResourceService;
import org.eclipse.gendoc.documents.FileRunnable;
import org.eclipse.gendoc.documents.IDocumentService;
import org.eclipse.gendoc.documents.IImageService;
import org.eclipse.gendoc.documents.MimeTypes;
import org.eclipse.gendoc.documents.ResourceRunnable;
import org.eclipse.gendoc.services.GendocServices;
import org.eclipse.gendoc.services.ILogger;
import org.eclipse.gendoc.services.exception.AdditionalResourceException;
import org.eclipse.gendoc.services.exception.UnknownMimeTypeException;
import org.eclipse.gendoc.services.utils.DefaultImageExtensionUtils;
/**
* @author ahaugomm
*
*/
public class DOCXAdditionalResourceService extends AdditionalResourceService {
private static String DOCUMENT_XML_RELS = "document.xml.rels";
// List of file extensions supported by <altChunk> tag
private static List<String> ALTCHUNK_FILE_EXTENSIONS = Arrays.asList(new String[]{"html", "rtf", "xhtml", "txt","docx"});
private DOCXImageService imageHandler;
/**
* File relationShips to add ( between XML files and external resources :
* images, HTML content) This map contains the content of new relationships,
* that will be added inside all relationship files (document.xml.rels,
* header1.xml.rels,...)
*/
private StringBuffer relationShipsToAdd;
protected Map<String, String> externalChunkMap;
public DOCXAdditionalResourceService() {
super();
imageHandler = new DOCXImageService();
relationShipsToAdd = new StringBuffer();
externalChunkMap = new LinkedHashMap<String, String>();
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.gendoc.services.IAdditionalResourceService#
* getImageHandler()
*/
public IImageService getImageService() {
return imageHandler;
}
public void addAdditionalResourcesToDocument()
throws AdditionalResourceException {
String mediaLoc = getResourceFolder();
DOCXDocumentService documentService = GendocServices.getDefault()
.getService(IDocumentService.class);
File unzipLoc = ((DocxDocument) documentService.getDocument())
.getUnzipLocationDocumentFile();
// Get directory "media" and add it if not present
File mediaDir = new File(mediaLoc);
if (!mediaDir.exists()) {
mediaDir.mkdir();
}
// Add images resources to document
addImagesResourcesToDocument(mediaLoc);
// Add external file resources to document
addExternalResourcesToDocument(mediaLoc);
// Add mappings between images and files
String relsLocation = addRelationShips(unzipLoc);
// Add mapping files for each document XML (document.xml, headers,
// footers)
addMappingFiles(unzipLoc, relsLocation);
// Modify file with content types [Content_Types].xml to add unknown
// file extensions
modifyContentTypes(unzipLoc);
}
/**
* Copy relationships to all headers and footers
*
* @param unzipLoc
* unzip location containing all files from the DOCX archive
* @param relsLocation
* location of the word/rels directory containing the future
* mapping files
*/
private void addMappingFiles(File unzipLoc, String relsLocation)
throws AdditionalResourceException {
ILogger logger = GendocServices.getDefault().getService(ILogger.class);
// Get Word directory
File wordDir = new File(unzipLoc.getAbsolutePath() + SEPARATOR + "word");
if (!wordDir.isDirectory()) {
throw new AdditionalResourceException(
"Invalid mapping files directory :" + wordDir);
}
String[] fileNames = wordDir.list(new FilenameFilter() {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
return name.endsWith(".xml")
&& (name.contains("header") || name.contains("footer"));
}
});
try {
// Add new relationships to all headers and footers
for (String fileName : fileNames) {
File rels = new File(relsLocation + fileName + ".rels");
StringBuffer relsContent;
if (relationShipsToAdd != null) {
if (rels.exists()) {
relsContent = new StringBuffer(readFileContent(rels));
Pattern p = Pattern.compile("</Relationships>");
Matcher m = p.matcher(relsContent);
if (m.find()) {
int index = m.start();
relsContent.insert(index,
relationShipsToAdd.toString());
writeFileContent(rels, relsContent);
}
} else {
// handle case when header or footer rels file do not
// exist : create it and insert relationships
rels.createNewFile();
relsContent = new StringBuffer(
"<?xml version=\"1.0\" encoding=\"windows-1250\"?> <Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">");
relsContent.append(relationShipsToAdd);
relsContent.append("</Relationships>");
writeFileContent(rels, relsContent);
}
}
}
} catch (IOException e) {
logger.log(
"Mapping files for headers and footers are not copied properly. Some images in headers and footers can be missing.",
IStatus.INFO);
}
}
/**
* Update file [Content_Types].xml, that contains the list of content types
* of the additional resources
*
* @param unzipLoc
* the location of
* @throws AdditionalResourceException
*/
private void modifyContentTypes(File unzipLoc)
throws AdditionalResourceException {
File contentTypes = new File(unzipLoc.getAbsolutePath() + SEPARATOR
+ "[Content_Types].xml");
try {
String contents = readFileContent(contentTypes);
StringBuffer extensionsToAdd = new StringBuffer();
for (String extension : fileExtensions) {
if (!contents.contains("<Default Extension=\"" + extension
+ "\"")) {
try {
extensionsToAdd.append("<Default Extension=\""
+ extension + "\" ContentType=\""
+ MimeTypes.getMimeTypefromExtension(extension)
+ "\"/>");
} catch (UnknownMimeTypeException e) {
throw new AdditionalResourceException(
"Additional resource with extension '"
+ extension
+ "' is not supported inside generated DOCX file.",
e);
}
}
}
extensionsToAdd.append("</Types>");
writeFileContent(
contentTypes,
new StringBuffer(contents.replace("</Types>",
extensionsToAdd.toString())));
} catch (IOException e) {
throw new AdditionalResourceException(
"DOCX document generated cannot be completed: Problem with content types");
}
}
private String addRelationShips(File unzipLoc)
throws AdditionalResourceException {
try {
// Write relationShips inside word/_rels/document.xml.rels files
Pattern p = Pattern.compile("</Relationships>");
String relsLocation = unzipLoc.getAbsolutePath() + SEPARATOR
+ "word" + SEPARATOR + "_rels" + SEPARATOR;
File rels = new File(relsLocation + DOCUMENT_XML_RELS);
StringBuffer relsContent = new StringBuffer(readFileContent(rels));
Matcher m = p.matcher(relsContent);
if (m.find()) {
int index = m.start();
relsContent.insert(index, relationShipsToAdd);
writeFileContent(rels, relsContent);
}
return relsLocation;
} catch (IOException e) {
throw new AdditionalResourceException(e);
}
}
public String addRunnableResourceToDocument(String mediaLoc,
String diagramKey) throws AdditionalResourceException {
if (runnableMap.get(diagramKey) == null) {
throw new AdditionalResourceException("Image with id '"
+ diagramKey + "' cannot be found.");
}
// Get runnableResource
ResourceRunnable runnable = runnableMap.get(diagramKey);
String extension = getFileExtensionFromRunnable(runnable);
// Run the resource
runnable.run(diagramKey, getResourceFolder());
// add relationship in document.xml.rels
String newRelationShips ="";
if( ALTCHUNK_FILE_EXTENSIONS.contains(extension)) {
newRelationShips = newExternalChunkRelationship(diagramKey, diagramKey + "."
+ extension);
}
else {
newRelationShips = newRelationship(diagramKey, diagramKey + "."
+ extension);
}
if (relationShipsToAdd.indexOf(newRelationShips) == -1) {
relationShipsToAdd.append(newRelationShips);
}
fileExtensions.add(extension);
return getResourceFolder() + SEPARATOR + diagramKey + "." + extension;
}
private String getFileExtensionFromRunnable(ResourceRunnable runnable) {
if (runnable instanceof FileRunnable) {
return ((FileRunnable) runnable).getFileExtension();
}
return DefaultImageExtensionUtils.getDefaultImageExtension();
}
private void addImagesResourcesToDocument(String mediaLoc)
throws AdditionalResourceException {
// For each image
for (String imageKey : imagesMap.keySet()) {
// add image as file in the media directory
File imageFile = new File(imagesMap.get(imageKey));
// Extract and normalize file name
String imageLink = imageKey
+ "."
+ Path.fromOSString(imageFile.getAbsolutePath())
.getFileExtension();
try {
copyImage(imageFile, mediaLoc + SEPARATOR + imageLink);
} catch (IOException e) {
throw new AdditionalResourceException("File '"
+ imageFile.getAbsolutePath() + "'cannot be copied.", e);
}
// add relationship in document.xml.rels
relationShipsToAdd.append(newRelationship(imageKey, imageLink));
if (!imageFile.exists()) {
throw new AdditionalResourceException(
"An image cannot be generated and has been replaced by a red cross. Cause: No image found at location:"
+ imageFile.getAbsolutePath());
}
}
}
private String newRelationship(String imageKey, String imageLink) {
return "<Relationship Id=\""
+ imageKey
+ "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\" Target=\"media/"
+ imageLink + "\"/>";
}
public String getRelativeResourceFolder() {
return "word" + SEPARATOR + "media";
}
public String includeFile(String fullPath) {
String id = generateUniqueId();
externalChunkMap.put(id, fullPath);
// Add file extension to extensions list
fileExtensions.add(Path.fromOSString(fullPath).getFileExtension());
return id;
}
public void includeExtensions(Set<String> fileExt) {
fileExtensions.addAll(fileExt);
}
private void addExternalResourcesToDocument(String mediaLoc)
throws AdditionalResourceException {
for (String key : externalChunkMap.keySet()) {
// add external file as file in the media directory
File file = new File(externalChunkMap.get(key));
// Extract file
String externalLink = key
+ "."
+ Path.fromOSString(file.getAbsolutePath())
.getFileExtension();// normalizeString(getFileName(externalChunkMap.get(key)));
try {
copyFile(file, mediaLoc + SEPARATOR + externalLink);
} catch (IOException e) {
throw new AdditionalResourceException("File '"
+ file.getAbsolutePath() + "'cannot be copied.", e);
}
// add relationship in document.xml.rels
relationShipsToAdd.append(newExternalChunkRelationship(key,
externalLink));
if (!file.exists()) {
throw new AdditionalResourceException(
"An external file cannot be generated. Cause: No file found at location:"
+ file.getAbsolutePath());
}
}
}
private String newExternalChunkRelationship(String key, String target) {
return "<Relationship Id=\""
+ key
+ "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk\" Target=\"media/"
+ target + "\"/>";
}
}