/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.modules.fo.archiver.formatters;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringEscapeUtils;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
import org.olat.core.id.Identity;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.Tracing;
import org.olat.core.util.Formatter;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.filter.FilterFactory;
import org.olat.core.util.nodes.INode;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.modules.fo.ForumManager;
import org.olat.modules.fo.MessageNode;
/**
* Initial Date: Nov 09, 2005 <br>
*
* @author Patrick Brunner, Alexander Schneider
*/
public class ForumRTFFormatter extends ForumFormatter {
private VFSContainer container;
private VFSItem vfsFil = null;
private ForumManager fm = ForumManager.getInstance();
private VFSContainer tempContainer;
final Pattern PATTERN_HTML_BOLD = Pattern.compile("<strong>(.*?)</strong>", Pattern.CASE_INSENSITIVE);
final Pattern PATTERN_HTML_ITALIC = Pattern.compile("<em>(.*?)</em>", Pattern.CASE_INSENSITIVE);
final Pattern PATTERN_HTML_BREAK = Pattern.compile("<br />", Pattern.CASE_INSENSITIVE);
final Pattern PATTERN_HTML_PARAGRAPH = Pattern.compile("<p>(.*?)</p>", Pattern.CASE_INSENSITIVE);
final Pattern PATTERN_HTML_AHREF = Pattern.compile("<a href=\"([^\"]+)\"[^>]*>(.*?)</a>", Pattern.CASE_INSENSITIVE);
final Pattern PATTERN_HTML_LIST = Pattern.compile("<li>(.*?)</li>", Pattern.CASE_INSENSITIVE);
final Pattern HTML_SPACE_PATTERN = Pattern.compile(" ");
final Pattern PATTERN_CSS_O_FOQUOTE = Pattern.compile("<div class=\"b_quote_wrapper\">\\s*<div class=\"b_quote_author mceNonEditable\">(.*?)</div>\\s*<blockquote class=\"b_quote\">\\s*(.*?)\\s*</blockquote>\\s*</div>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
final Pattern PATTERN_THREEPOINTS = Pattern.compile("…", Pattern.CASE_INSENSITIVE);
final String THREEPOINTS = "...";
//TODO: (LD) translate this!
private String HIDDEN_STR = "VERBORGEN";
/**
*
* @param container
* @param filePerThread
*/
public ForumRTFFormatter(VFSContainer container, boolean filePerThread) {
// init String Buffer in ForumFormatter
super();
// where to write
this.container = container;
this.filePerThread = filePerThread;
//
/*Translator translator = new PackageTranslator(PACKAGE, locale);
HIDDEN_STR = translator.translate("fo_hidden");*/
}
/**
* @see org.olat.core.util.tree.Visitor#visit(org.olat.core.util.nodes.INode)
*/
public void visit(INode node) {
MessageNode mn = (MessageNode) node;
if (isTopThread) {
if(filePerThread){
//make a file per thread
//to have a meaningful filename we create the file here
String filName = "Thread_" + mn.getKey().toString();
tempContainer = makeTempVFSContainer();
this.vfsFil=tempContainer.resolve(filName + ".rtf");
if(vfsFil==null){
tempContainer.createChildLeaf(filName + ".rtf");
this.vfsFil=tempContainer.resolve(filName + ".rtf");
}
}
//important!
isTopThread = false;
}
// Message Title
sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\f1\\fs30\\b ");
sb.append(getImageRTF(mn));
sb.append(getTitlePrefix(mn));
sb.append(mn.getTitle());
sb.append("\\par}");
// Message Body
sb.append("{\\pard \\f0");
sb.append(convertHTMLMarkupToRTF(mn.getBody()));
sb.append("\\par}");
// Message key
sb.append("{\\pard \\f0\\fs15 Message key: ");
sb.append(mn.getKey());
sb.append("} \\line ");
sb.append("{\\pard \\f0\\fs15 created: ");
// Creator and creation date
sb.append(mn.getCreator().getUser().getProperty(UserConstants.FIRSTNAME, null));
sb.append(", ");
sb.append(mn.getCreator().getUser().getProperty(UserConstants.LASTNAME, null));
sb.append(" ");
sb.append(mn.getCreationDate().toString());
// Modifier and modified date
Identity modifier = mn.getModifier();
if (modifier != null) {
sb.append(" \\line modified: ");
sb.append(modifier.getUser().getProperty(UserConstants.FIRSTNAME, null));
sb.append(", ");
sb.append(modifier.getUser().getProperty(UserConstants.LASTNAME, null));
sb.append(" ");
sb.append(mn.getModifiedDate().toString());
}
sb.append(" \\par}");
// attachment(s)
OlatRootFolderImpl msgContainer = fm.getMessageContainer((Long)(getMetainfo(ForumFormatter.MANDATORY_METAINFO_KEY)), mn.getKey());
List attachments = msgContainer.getItems();
if (attachments != null && attachments.size() > 0){
VFSItem item = container.resolve("attachments");
if (item == null){
item = container.createChildContainer("attachments");
}
VFSContainer attachmentContainer = (VFSContainer)item;
attachmentContainer.copyFrom(msgContainer);
sb.append("{\\pard \\f0\\fs15 Attachment(s): ");
boolean commaFlag = false;
for (Iterator iter = attachments.iterator(); iter.hasNext();) {
VFSItem attachment = (VFSItem) iter.next();
if (commaFlag) sb.append(", ");
sb.append(attachment.getName());
commaFlag = true;
}
sb.append("} \\line");
}
sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\par}");
}
/**
*
* @see org.olat.modules.fo.archiver.formatters.ForumFormatter#openThread()
*/
public void openThread() {
super.openThread();
if(filePerThread){
sb.append("{\\rtf1\\ansi\\deff0");
sb.append("{\\fonttbl {\\f0\\fswiss Arial;}} ");
sb.append("\\deflang1033\\plain");
}
sb.append("{\\pard \\brdrb \\brdrs \\brdrdb \\brsp20 \\par}{\\pard\\par}");
}
/**
*
* @see org.olat.modules.fo.archiver.formatters.ForumFormatter#getThreadResult()
*/
public StringBuilder closeThread() {
boolean append = !filePerThread;
String footerThread = "{\\pard \\brdrb \\brdrs \\brdrw20 \\brsp20 \\par}{\\pard\\par}";
sb.append(footerThread);
if(filePerThread){
sb.append("}");
}
writeToFile(append, sb);
if(this.filePerThread) {
zipContainer(tempContainer);
tempContainer.delete();
}
return super.closeThread();
}
/**
*
* @see org.olat.modules.fo.archiver.formatters.ForumFormatter#openForum()
*/
public void openForum(){
if(!filePerThread){
//make one ForumFile
Long forumKey = (Long) metaInfo.get(ForumFormatter.MANDATORY_METAINFO_KEY);
String filName = forumKey.toString();
filName = "Threads_" + filName + ".rtf";
tempContainer = makeTempVFSContainer();
this.vfsFil=tempContainer.resolve(filName);
if(vfsFil==null){
tempContainer.createChildLeaf(filName);
this.vfsFil=tempContainer.resolve(filName);
}
sb.append("{\\rtf1\\ansi\\deff0");
sb.append("{\\fonttbl {\\f0\\fswiss Arial;}} ");
sb.append("\\deflang1033\\plain");
}
}
/**
*
* @see org.olat.modules.fo.archiver.formatters.ForumFormatter#closeForum()
*/
public StringBuilder closeForum(){
if(!filePerThread){
boolean append = !filePerThread;
String footerForum = "}";
sb.append(footerForum);
writeToFile(append, sb);
zipContainer(tempContainer);
tempContainer.delete();
}
return sb;
}
/**
*
* @param append
* @param buff
*/
private void writeToFile(boolean append, StringBuilder buff){
BufferedOutputStream bos = new BufferedOutputStream(((VFSLeaf) vfsFil).getOutputStream(append));
OutputStreamWriter w;
try {
w = new OutputStreamWriter(bos, "utf-8");
BufferedWriter bw = new BufferedWriter(w);
String s = buff.toString();
StringBuilder out = new StringBuilder();
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
int val = c;
if (val > 127) {
out.append("\\u").append(String.valueOf(val)).append("?");
} else {
out.append(c);
}
}
String encoded = out.toString();
bw.write(encoded);
bw.close();
bos.close();
} catch (UnsupportedEncodingException ueEx) {
throw new AssertException("could not encode stream from forum export file: " + ueEx);
} catch (IOException e) {
throw new AssertException("could not write to forum export file: " + e);
}
}
/**
*
* @param originalText
* @return
*/
private String convertHTMLMarkupToRTF(String originalText){
String htmlText = originalText;
Matcher mb = PATTERN_HTML_BOLD.matcher(htmlText);
StringBuffer bolds = new StringBuffer();
while (mb.find()) {
mb.appendReplacement(bolds, "{\\\\b $1} ");
}
mb.appendTail(bolds);
htmlText = bolds.toString();
Matcher mi = PATTERN_HTML_ITALIC.matcher(htmlText);
StringBuffer italics = new StringBuffer();
while (mi.find()) {
mi.appendReplacement(italics, "{\\\\i $1} ");
}
mi.appendTail(italics);
htmlText = italics.toString();
Matcher mbr = PATTERN_HTML_BREAK.matcher(htmlText);
StringBuffer breaks = new StringBuffer();
while(mbr.find()){
mbr.appendReplacement(breaks, "\\\\line ");
}
mbr.appendTail(breaks);
htmlText = breaks.toString();
Matcher mofo = PATTERN_CSS_O_FOQUOTE.matcher(htmlText);
StringBuffer foquotes = new StringBuffer();
while(mofo.find()){
mofo.appendReplacement(foquotes, "\\\\line {\\\\i $1} {\\\\pard $2\\\\par}");
}
mofo.appendTail(foquotes);
htmlText = foquotes.toString();
Matcher mp = PATTERN_HTML_PARAGRAPH.matcher(htmlText);
StringBuffer paragraphs = new StringBuffer();
while(mp.find()){
mp.appendReplacement(paragraphs, "\\\\line $1 \\\\line");
}
mp.appendTail(paragraphs);
htmlText = paragraphs.toString();
Matcher mahref = PATTERN_HTML_AHREF.matcher(htmlText);
StringBuffer ahrefs = new StringBuffer();
while(mahref.find()){
mahref.appendReplacement(ahrefs, "{\\\\field{\\\\*\\\\fldinst{HYPERLINK\"$1\"}}{\\\\fldrslt{\\\\ul $2}}}");
}
mahref.appendTail(ahrefs);
htmlText = ahrefs.toString();
Matcher mli = PATTERN_HTML_LIST.matcher(htmlText);
StringBuffer lists = new StringBuffer();
while(mli.find()){
mli.appendReplacement(lists, "$1\\\\line ");
}
mli.appendTail(lists);
htmlText = lists.toString();
Matcher mtp = PATTERN_THREEPOINTS.matcher(htmlText);
StringBuffer tps = new StringBuffer();
while (mtp.find()) {
mtp.appendReplacement(tps, THREEPOINTS);
}
mtp.appendTail(tps);
htmlText = tps.toString();
// strip all other html-fragments, because not convertable that easy
htmlText = FilterFactory.getHtmlTagsFilter().filter(htmlText);
// Remove all
Matcher tmp = HTML_SPACE_PATTERN.matcher(htmlText);
htmlText = tmp.replaceAll(" ");
htmlText = StringEscapeUtils.unescapeHtml(htmlText);
return htmlText;
}
/**
*
* @param messageNode
* @return title prefix for hidden forum threads.
*/
private String getTitlePrefix(MessageNode messageNode) {
StringBuffer stringBuffer = new StringBuffer();
if(messageNode.isHidden()) {
stringBuffer.append(HIDDEN_STR);
}
if(stringBuffer.length()>1) {
stringBuffer.append(": ");
}
return stringBuffer.toString();
}
/**
* Gets the RTF image section for the input messageNode.
* @param messageNode
* @return the RTF image section for the input messageNode.
*/
private String getImageRTF(MessageNode messageNode) {
StringBuffer stringBuffer = new StringBuffer();
List<String> fileNameList = addImagesToVFSContainer(messageNode, tempContainer);
Iterator<String> listIterator = fileNameList.iterator();
while(listIterator.hasNext()) {
String fileName = listIterator.next();
stringBuffer.append("{\\field\\fldedit{\\*\\fldinst { INCLUDEPICTURE ");
stringBuffer.append("\"").append(fileName).append("\"");
stringBuffer.append(" \\\\d }}{\\fldrslt {}}}");
}
return stringBuffer.toString();
}
/**
* Retrieves the appropriate images for the input messageNode, if any,
* and adds it to the input container.
*
* @param messageNode
* @param container
* @return
*/
private List<String> addImagesToVFSContainer(MessageNode messageNode, VFSContainer container) {
List<String> fileNameList = new ArrayList<String>();
String iconPath = null;
if(messageNode.isClosed() && messageNode.isSticky()) {
iconPath = getImagePath("fo_sticky_closed");
} else if(messageNode.isClosed()) {
iconPath = getImagePath("fo_closed");
} else if(messageNode.isSticky()) {
iconPath = getImagePath("fo_sticky");
}
if (iconPath != null) {
File file = new File(iconPath);
if (file.exists()) {
LocalFileImpl imgFile = new LocalFileImpl(file);
container.copyFrom(imgFile);
fileNameList.add(file.getName());
} else {
Tracing.logError("Could not find image for forum RTF formatter::" + iconPath, ForumFormatter.class);
}
}
return fileNameList;
}
/**
* TODO: LD: to clarify whether there it a better way to get the image path?
* Gets the image path.
* @param val
* @return the path of the static icon image.
*/
private String getImagePath(Object val) {
return WebappHelper.getContextRoot() + "/static/images/forum/" + val.toString() + ".png";
}
/**
* Generates a new temporary VFSContainer.
* @return the temp container.
*/
private VFSContainer makeTempVFSContainer() {
Long forumKey = (Long) metaInfo.get(ForumFormatter.MANDATORY_METAINFO_KEY);
String dateStamp = String.valueOf(System.currentTimeMillis());
//TODO: (LD) could this filename regarded as unique or use System.nanoTime() instead?
String fileName = "forum" + forumKey.toString() + "_" + dateStamp;
LocalFolderImpl tempFolder = new OlatRootFolderImpl("/tmp/" + fileName, null);
return tempFolder;
}
/**
* Zips the input vFSContainer into the container.
* @param vFSContainer
*/
private void zipContainer(VFSContainer vFSContainer) {
String dateStamp = Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis()));
VFSLeaf zipVFSLeaf = container.createChildLeaf("forum_archive-"+dateStamp+".zip");
ZipUtil.zip(vFSContainer.getItems(), zipVFSLeaf);
}
}