/** * 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. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.modules.fo.archiver.formatters; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.olat.core.id.Identity; import org.olat.core.id.UserConstants; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; 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.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.modules.fo.archiver.MessageNode; import org.olat.modules.fo.manager.ForumManager; /** * Initial Date: Dec 19, 2013 <br> * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * @author Patrick Brunner, Alexander Schneider */ public class ForumStreamedRTFFormatter extends ForumFormatter { private static final OLog log = Tracing.createLoggerFor(ForumStreamedRTFFormatter.class); private ZipOutputStream exportStream; private ForumManager fm = ForumManager.getInstance(); 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=\"o_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"; private final String path; /** * * @param container * @param filePerThread */ public ForumStreamedRTFFormatter(ZipOutputStream exportStream, String path, boolean filePerThread, Locale locale) { // init String Buffer in ForumFormatter super(locale); // where to write this.exportStream = exportStream; this.filePerThread = filePerThread; this.path = path; addStandardImages(); } private String fileName; /** * @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) { //important! fileName = "Thread_" + mn.getKey().toString() + ".rtf"; 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 if(StringHelper.containsNonWhitespace(mn.getPseudonym())) { sb.append(mn.getPseudonym()) .append(" ") .append(translator.translate("pseudonym.suffix")); } else if(mn.isGuest()) { sb.append(translator.translate("guest")); } else { 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) VFSContainer msgContainer = fm.getMessageContainer(getForumKey(), mn.getKey()); List<VFSItem> attachments = msgContainer.getItems(); if (attachments != null && attachments.size() > 0){ sb.append("{\\pard \\f0\\fs15 Attachment(s): "); boolean commaFlag = false; for (VFSItem attachment: attachments) { if (commaFlag) sb.append(", "); sb.append(attachment.getName()); commaFlag = true; ZipUtil.addToZip(attachment, path + "/attachments", exportStream); } sb.append("} \\line"); } sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\par}"); } private void addStandardImages() { String[] images = new String[]{ "fo_sticky_closed", "fo_closed", "fo_sticky"}; try { for(String image:images) { String iconPath = getImagePath(image); if (iconPath != null) { File file = new File(iconPath); if (file.exists()) { exportStream.putNextEntry(new ZipEntry(path + "/" + file.getName())); FileUtils.copyFile(file, exportStream); exportStream.closeEntry(); } } } } catch (IOException e) { e.printStackTrace(); } } /** * * @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() { String footerThread = "{\\pard \\brdrb \\brdrs \\brdrw20 \\brsp20 \\par}{\\pard\\par}"; sb.append(footerThread); if(filePerThread){ sb.append("}"); writeToFile(sb); sb = new StringBuilder(); } return sb; } /** * * @see org.olat.modules.fo.archiver.formatters.ForumFormatter#openForum() */ public void openForum(){ if(!filePerThread){ //make one ForumFile Long forumKey = getForumKey(); fileName = "Threads_" + forumKey.toString() + ".rtf"; 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){ String footerForum = "}"; sb.append(footerForum); writeToFile(sb); } return sb; } /** * * @param append * @param buff */ private void writeToFile(StringBuilder buff){ try { StringBuilder out = new StringBuilder(); int len = buff.length(); for (int i = 0; i < len; i++) { char c = buff.charAt(i); int val = c; if (val > 127) { out.append("\\u").append(String.valueOf(val)).append("?"); } else { out.append(c); } } String encoded = out.toString(); exportStream.putNextEntry(new ZipEntry(path + "/" + fileName)); IOUtils.write(encoded, exportStream); exportStream.closeEntry(); } 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) { StringBuilder titleSb = new StringBuilder(); if(messageNode.isHidden()) { titleSb.append(HIDDEN_STR); } if(titleSb.length()>1) { titleSb.append(": "); } return titleSb.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) { StringBuilder imgSb = new StringBuilder(); for(String imageName : addImagesToVFSContainer(messageNode)) { imgSb.append("{\\field\\fldedit{\\*\\fldinst { INCLUDEPICTURE ") .append("\"").append(imageName).append("\"") .append(" \\\\d }}{\\fldrslt {}}}"); } return imgSb.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) { 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()) { fileNameList.add(file.getName()); } else { log.error("Could not find image for forum RTF formatter::" + iconPath); } } 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.getContextRealPath("/static/images/forum/" + val.toString() + ".png"); } }