Java tutorial
/* * ConcourseConnect * Copyright 2009 Concursive Corporation * http://www.concursive.com * * This file is part of ConcourseConnect, an open source social business * software and community platform. * * Concursive ConcourseConnect is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, version 3 of the License. * * Under the terms of the GNU Affero General Public License you must release the * complete source code for any application that uses any part of ConcourseConnect * (system header files and libraries used by the operating system are excluded). * These terms must be included in any work that has ConcourseConnect components. * If you are developing and distributing open source applications under the * GNU Affero General Public License, then you are free to use ConcourseConnect * under the GNU Affero General Public License. * * If you are deploying a web site in which users interact with any portion of * ConcourseConnect over a network, the complete source code changes must be made * available. For example, include a link to the source archive directly from * your web site. * * For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their * products, and do not license and distribute their source code under the GNU * Affero General Public License, Concursive provides a flexible commercial * license. * * To anyone in doubt, we recommend the commercial license. Our commercial license * is competitively priced and will eliminate any confusion about how * ConcourseConnect can be used and distributed. * * ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>. * * Attribution Notice: ConcourseConnect is an Original Work of software created * by Concursive Corporation */ package com.concursive.connect.web.modules.wiki.utils; import com.concursive.commons.date.DateUtils; import com.concursive.commons.db.DatabaseUtils; import com.concursive.commons.phone.PhoneNumberBean; import com.concursive.commons.phone.PhoneNumberUtils; import com.concursive.commons.text.StringUtils; import com.concursive.connect.web.modules.login.dao.User; import com.concursive.connect.web.modules.login.utils.UserUtils; import com.concursive.connect.web.modules.profile.dao.Project; import com.concursive.connect.web.modules.profile.utils.ProjectUtils; import com.concursive.connect.web.modules.wiki.dao.*; import com.concursive.connect.web.utils.HtmlSelect; import com.concursive.connect.web.utils.HtmlSelectCurrencyCode; import com.concursive.connect.web.utils.LookupElement; import com.concursive.connect.web.utils.LookupList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.BufferedReader; import java.io.StringReader; import java.sql.Connection; import java.sql.SQLException; import java.sql.Timestamp; import java.text.DateFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.*; /** * Class to manipulate wiki objects * * @author matt rajkowski * @version $Id$ * @created February 7, 2006 */ public class WikiToHTMLUtils { private static Log LOG = LogFactory.getLog(WikiToHTMLUtils.class); public static String CRLF = "\n"; public static String CONTENT_NEEDS_FORMATTING = "needs_formatting"; public static String CONTENT_PREFORMATTED = "preformatted"; public static String CONTENT_PRE = "pre-preformatted"; public static String CONTENT_CODE = "code-preformatted"; public static String getHTML(WikiToHTMLContext context, Connection db) { String content = context.getWiki().getContent(); if (content == null) { return null; } // Chunk the content into manageable pieces LinkedHashMap<String, String> chunks = chunkContent(content, context.isEditMode()); StringBuffer sb = new StringBuffer(); for (String type : chunks.keySet()) { String chunk = chunks.get(type); LOG.trace("========= CHUNK [" + type + "] ========="); if (type.endsWith(CONTENT_PREFORMATTED)) { if (!context.canAppend()) { continue; } LOG.trace(chunk); if (type.endsWith(CONTENT_CODE + "remove-test")) { sb.append("<code>"); } else { sb.append("<pre>"); } sb.append(chunk); if (type.endsWith(CONTENT_CODE + "remove-test")) { sb.append("</code>"); } else { sb.append("</pre>"); } sb.append(CRLF); } else if (type.endsWith(CONTENT_NEEDS_FORMATTING)) { String formatted = getHTML(context, db, chunk); LOG.trace(formatted); sb.append(formatted); } } return sb.toString(); // Tables (With linewraps) // Header || // Cell | // Ordered List // * // ** // Unordered List // # // ## // Links // [[Wiki link]] // [[Wiki Link|Renamed]] // [[http://external]] // Images // [[Image:Filename.jpg]] // [[Image:Filename.jpg|A caption]] // [[Image:Filename.jpg|link=wiki]] // [[Image:Filename.jpg|thumb]] // [[Image:Filename.jpg|right]] // [[Image:Filename.jpg|left]] // Videos // [[Video:http://www.youtube.com/watch?v=3LkNlTNHZzE]] // [[Video:http://www.ustream.tv/flash/live/1/371673]] // [[Video:http://www.justin.tv/vasalini]] // [[Video:http://www.livestream.com/spaceflightnow]] // [[Video:http://qik.com/zeroio]] // Forms // [{form name="wikiForm"}] // --- // [{group value="New Group" display="false"}] // --- // [{label value="This is a text field" display="false"}] // [{field type="text" name="cf10" maxlength="30" size="30" value="" required="false"}] // [{description value="This is the text to display after"}] // +++ } public static LinkedHashMap<String, String> chunkContent(String content, boolean editMode) { // Reduce the content into formatted and unformatted chunks... LinkedHashMap<String, String> chunks = new LinkedHashMap<String, String>(); int preIndex = -1; int codeIndex = -1; int chunkCount = 0; while ((preIndex = content.indexOf("<pre>")) > -1 || (codeIndex = content.indexOf("<code")) > -1) { int blockStart = -1; int startIndex = -1; int endIndex = -1; int blockEnd = -1; String type = CONTENT_PREFORMATTED; if (preIndex > -1 && codeIndex > -1) { if (preIndex < codeIndex) { blockStart = preIndex; startIndex = preIndex + 5; endIndex = content.indexOf("</pre>", startIndex); blockEnd = endIndex + 6; type = CONTENT_PRE; } else { blockStart = codeIndex; startIndex = codeIndex + 6; endIndex = content.indexOf("</code>", startIndex); blockEnd = endIndex + 7; type = CONTENT_CODE; } } else if (preIndex > -1) { blockStart = preIndex; startIndex = preIndex + 5; endIndex = content.indexOf("</pre>", startIndex); blockEnd = endIndex + 6; type = CONTENT_PRE; } else { blockStart = codeIndex; startIndex = codeIndex + 6; endIndex = content.indexOf("</code>", startIndex); blockEnd = endIndex + 7; type = CONTENT_CODE; } // Store non-preformatted chunks in a map for further processing if (blockStart > 0) { chunks.put(++chunkCount + CONTENT_NEEDS_FORMATTING, content.substring(0, blockStart)); } // Process pre-formatted text here... String preText = content.substring(startIndex, endIndex); while (preText.startsWith(CRLF)) { preText = preText.substring(1); } while (preText.endsWith(CRLF)) { preText = preText.substring(0, preText.length() - 1); } String text = StringUtils.toHtmlValue(preText, false, false); text = processMarkupCharacters(text); if (editMode) { text = StringUtils.toBasicHtmlChars(text); } chunks.put(++chunkCount + type, text); if (blockEnd < content.length()) { content = content.substring(blockEnd); } else { content = ""; } } // Store the rest of the content if (content.length() > 0) { chunks.put(++chunkCount + CONTENT_NEEDS_FORMATTING, content); } return chunks; } public static String getHTML(WikiToHTMLContext context, Connection db, String content) { return getHTML(context, db, content, false); } private static String getHTML(WikiToHTMLContext context, Connection db, String content, boolean inTable) { boolean inParagraph = false; boolean unorderedList = false; int unorderedIndent = 0; boolean orderedList = false; int orderedIndent = 0; boolean header = true; try { StringBuffer sb = new StringBuffer(); BufferedReader in = new BufferedReader(new StringReader(content)); String line = null; while ((line = in.readLine()) != null) { // Tables if (line.startsWith("|")) { if (orderedList) { for (int i = 0; i < orderedIndent; i++) { append(context, sb, "</ol>"); } orderedList = false; orderedIndent = 0; append(context, sb, CRLF); } if (unorderedList) { for (int i = 0; i < unorderedIndent; i++) { append(context, sb, "</ul>"); } unorderedList = false; unorderedIndent = 0; append(context, sb, CRLF); } if (inParagraph) { append(context, sb, "</p>"); append(context, sb, CRLF); inParagraph = false; } // parseTable operates over all the lines that make up the table, // it will have to look forward so it returns an unparsed line line = parseTable(context, db, in, line, sb); if (line == null) { continue; } } // Forms if (line.startsWith("[{form")) { if (orderedList) { for (int i = 0; i < orderedIndent; i++) { append(context, sb, "</ol>"); } orderedList = false; orderedIndent = 0; append(context, sb, CRLF); } if (unorderedList) { for (int i = 0; i < unorderedIndent; i++) { append(context, sb, "</ul>"); } unorderedList = false; unorderedIndent = 0; append(context, sb, CRLF); } if (inParagraph) { append(context, sb, "</p>"); append(context, sb, CRLF); inParagraph = false; } // parseForm operates over all the lines that make up the form, // it will have to look forward so it returns an unparsed line parseForm(context, db, in, line, sb); continue; } // Section if (line.startsWith("=") && line.endsWith("=")) { if (orderedList) { for (int i = 0; i < orderedIndent; i++) { append(context, sb, "</ol>"); } orderedList = false; orderedIndent = 0; append(context, sb, CRLF); } if (unorderedList) { for (int i = 0; i < unorderedIndent; i++) { append(context, sb, "</ul>"); } unorderedList = false; unorderedIndent = 0; append(context, sb, CRLF); } if (inParagraph) { append(context, sb, "</p>"); append(context, sb, CRLF); inParagraph = false; } int hCount = parseHCount(line, "="); if (hCount > 6) { hCount = 6; } String section = line.substring(line.indexOf("=") + hCount, line.lastIndexOf("=") - hCount + 1); header = true; context.foundHeader(hCount); String headerAnchor = null; // Store the h2's with anchors if (hCount == 2) { headerAnchor = StringUtils.toHtmlValue(section).replace(" ", "_"); context.getHeaderAnchors().put(headerAnchor, section); } append(context, sb, "<h" + hCount + (headerAnchor != null ? " id=\"" + headerAnchor + "\"" : "") + ">"); if (context.canAppend()) { if (!context.isEditMode() && context.getShowEditSection() && !inTable) { if (hasUserProjectAccess(db, context.getUserId(), context.getProjectId(), "wiki", "add")) { sb.append("<span class=\"editsection\"><a href=\"" + context.getServerUrl() + "/modify/" + context.getProject().getUniqueId() + "/wiki" + (StringUtils.hasText(context.getWiki().getSubject()) ? "/" + context.getWiki().getSubjectLink() : "") + "?section=" + context.getSectionIdCount() + "\">edit</a></span>"); } } } if (context.isEditMode()) { append(context, sb, "<span>"); } append(context, sb, StringUtils.toHtml(section)); if (context.isEditMode()) { append(context, sb, "</span>"); } append(context, sb, "</h" + hCount + ">"); append(context, sb, CRLF); continue; } if (header) { header = false; if (line.trim().equals("")) { // remove the extra space a user may leave after a header continue; } } // Determine if this is a bulleted list if (line.startsWith("*")) { int hCount = parseHCount(line, "*"); if (!unorderedList) { unorderedList = true; } if (hCount != unorderedIndent) { if (hCount > unorderedIndent) { append(context, sb, "<ul>"); } else { for (int i = hCount; i < unorderedIndent; i++) { append(context, sb, "</ul>"); } } unorderedIndent = hCount; } append(context, sb, "<li>"); parseLine(context, db, line.substring(hCount).trim(), sb); append(context, sb, "</li>"); append(context, sb, CRLF); continue; } else { if (unorderedList) { for (int i = 0; i < unorderedIndent; i++) { append(context, sb, "</ul>"); } unorderedList = false; unorderedIndent = 0; append(context, sb, CRLF); } } // Determine if this is a numbered list if (line.startsWith("#")) { int hCount = parseHCount(line, "#"); if (!orderedList) { orderedList = true; } if (hCount != orderedIndent) { if (hCount > orderedIndent) { append(context, sb, "<ol>"); } else { for (int i = hCount; i < orderedIndent; i++) { append(context, sb, "</ol>"); } } orderedIndent = hCount; } append(context, sb, "<li>"); parseLine(context, db, line.substring(hCount).trim(), sb); append(context, sb, "</li>"); append(context, sb, CRLF); continue; } else { if (orderedList) { for (int i = 0; i < orderedIndent; i++) { append(context, sb, "</ol>"); } orderedList = false; orderedIndent = 0; append(context, sb, CRLF); } } if (inParagraph && line.length() != 0) { append(context, sb, "<br />"); } // Parse the line if (!inParagraph && line.length() > 0) { append(context, sb, "<p>"); inParagraph = true; } if (line.length() == 0) { if (inParagraph) { append(context, sb, "</p>"); append(context, sb, CRLF); inParagraph = false; } } else { boolean hasReturn = parseLine(context, db, line, sb); if (hasReturn) { //append(context, sb, "<br />"); } else { append(context, sb, CRLF); } } } // Cleanup now that the lines are finished if (orderedList) { for (int i = 0; i < orderedIndent; i++) { append(context, sb, "</ol>"); } append(context, sb, CRLF); } if (unorderedList) { for (int i = 0; i < unorderedIndent; i++) { append(context, sb, "</ul>"); } append(context, sb, CRLF); } if (inParagraph) { append(context, sb, "</p>"); append(context, sb, CRLF); } in.close(); return sb.toString(); } catch (Exception e) { LOG.error("getHTML", e); } LOG.warn("Could not convert wiki markup"); return "Content cannot be displayed due to a parsing error, use markup mode to find the error"; } protected static int parseHCount(String line, String id) { int count = 0; for (int i = 0; i < line.length(); i++) { char c = line.charAt(i); if (id.equals(String.valueOf(c))) { ++count; } else { return count; } } return 1; } protected static String parseTable(WikiToHTMLContext context, Connection db, BufferedReader in, String line, StringBuffer sb) throws Exception { if (line == null) { return line; } // Implement tables as... // ||header col1||header col2 // !continued|| // |colA1|colA2 // !continued // !continued| // |colB1|colB2 [[Security, Registration, Invitation|Installation Options]]| append(context, sb, "<table class=\"wikiTable\">"); append(context, sb, CRLF); int rowHighlight = 0; int rowCount = 0; // Keep track of the table's custom styles HashMap<Integer, String> cStyle = new HashMap<Integer, String>(); while (line != null && (line.startsWith("|") || line.startsWith("!"))) { // Build a complete line String lineToParse = line; while (!line.endsWith("|")) { line = in.readLine(); if (line == null) { // there is an error in the line to process return null; } if (line.startsWith("!")) { lineToParse += CRLF + line.substring(1); } } line = lineToParse; // Determine if the row can output boolean canOutput = true; // Track the row being processed ++rowCount; String cellType = null; Scanner sc = null; if (line.startsWith("||") && line.endsWith("||")) { cellType = "th"; sc = new Scanner(line).useDelimiter("[|][|]"); // sc = new Scanner(line.substring(2, line.length() - 2)).useDelimiter("[|][|]"); } else if (line.startsWith("|")) { cellType = "td"; sc = new Scanner(line.substring(1, line.length() - 1)).useDelimiter("\\|(?=[^\\]]*(?:\\[|$))"); } if (sc != null) { // Determine the column span int colSpan = 1; // Determine the cell being output int cellCount = 0; while (sc.hasNext()) { String cellData = sc.next(); if (cellData.length() == 0) { ++colSpan; continue; } // Track the cell count being output ++cellCount; if (rowCount == 1) { // Parse and validate the style input LOG.debug("Checking style value: " + cellData); if (cellData.startsWith("{") && cellData.endsWith("}")) { String[] style = cellData.substring(1, cellData.length() - 1).split(":"); String attribute = style[0].trim(); String value = style[1].trim(); if ("width".equals(attribute)) { // Validate the width style if (StringUtils.hasAllowedOnly("0123456789%.", value)) { cStyle.put(cellCount, attribute + ": " + value + ";"); } } else { LOG.debug("Unsupported style: " + cellData); } canOutput = false; } } // Output the header if (canOutput) { if (cellCount == 1) { // This is the table header row if ("td".equals(cellType)) { rowHighlight = (rowHighlight != 1 ? 1 : 2); append(context, sb, "<tr class=\"row" + rowHighlight + "\">"); } else { append(context, sb, "<tr>"); } } // Start the cell output append(context, sb, "<" + cellType); // Output the colspan if (colSpan > 1) { append(context, sb, " colspan=\"" + colSpan + "\""); } // Output any style if (cStyle.size() > 0 && rowCount == 2) { String style = cStyle.get(cellCount); if (style != null) { append(context, sb, " style=\"" + style + "\""); } } // End the cell output append(context, sb, ">"); // Output the data if (" ".equals(cellData) || "".equals(cellData)) { // Put a blank space in blank cells for output consistency append(context, sb, " "); } else { // Output the cell as a complete wiki String formatted = getHTML(context, db, cellData, true).trim(); LOG.trace(formatted); sb.append(formatted); } // Close the cell append(context, sb, "</" + cellType + ">"); } } if (canOutput) { append(context, sb, "</tr>"); append(context, sb, CRLF); } } // read another line to see if it's part of the table line = in.readLine(); } append(context, sb, "</table>"); append(context, sb, CRLF); return line; } public static CustomForm retrieveForm(BufferedReader in, String line) throws Exception { // Forms // [{form name="wikiForm"}] // --- // [{group value="New Group" display="false"}] // --- // [{label value="This is a text field" display="false"}] // [{field type="text" name="cf10" maxlength="30" size="30" value="" required="false"}] // [{description value="This is the text to display after"}] // xxx // Convert wiki to objects... CustomForm form = new CustomForm(); form.setName(extractValue("name", line)); LOG.debug("Form name: " + form.getName()); CustomFormGroup currentGroup = null; while (line != null && !line.startsWith("+++") && (line = in.readLine()) != null) { if (line.startsWith("[{group")) { LOG.debug("found group"); // Process the line as a group currentGroup = new CustomFormGroup(); currentGroup.setName(extractValue("value", line)); currentGroup.setDisplay(extractValue("display", line)); form.add(currentGroup); } else if (line.startsWith("[{label")) { LOG.debug("found a field label"); // Process this block as a field CustomFormField field = new CustomFormField(); field.setLabel(extractValue("value", line)); field.setLabelDisplay(extractValue("display", line)); while (!line.startsWith("---") && !line.startsWith("___") && !line.startsWith("+++") && (line = in.readLine()) != null) { if (line.startsWith("[{field")) { LOG.debug(" found field"); field.setType(extractValue("type", line)); field.setName(extractValue("name", line)); field.setRequired(extractValue("required", line)); field.setDefaultValue(extractValue("value", line)); field.setSize(extractValue("size", line)); field.setMaxLength(extractValue("maxlength", line)); field.setColumns(extractValue("cols", line)); field.setRows(extractValue("rows", line)); field.setOptions(extractValue("options", line)); } else if (line.startsWith("[{description")) { LOG.debug(" found description"); field.setAdditionalText(extractValue("value", line)); } else if (line.startsWith("[{entry")) { LOG.debug(" found entry"); field.setValue(extractValue("value", line)); field.setValueCurrency(extractValue("currency", line)); } } if (currentGroup == null) { currentGroup = new CustomFormGroup(); currentGroup.setDisplay(false); } currentGroup.add(field); } } return form; } protected static String parseForm(WikiToHTMLContext context, Connection db, BufferedReader in, String line, StringBuffer sb) throws Exception { if (line == null) { return line; } CustomForm form = retrieveForm(in, line); context.foundForm(form); if (context.canAppend()) { // Output the form based on editmode if (context.isEditMode()) { LOG.debug("parseForm: editMode"); // Construct an HTML form for filling out int groupCount = 0; for (CustomFormGroup group : form) { ++groupCount; if (groupCount > 1) { sb.append("<br />"); } sb.append("<table cellpadding=\"4\" cellspacing=\"0\" width=\"100%\" class=\"pagedList\">"); if (StringUtils.hasText(group.getName())) { sb.append("<tr><th colspan=\"2\">").append(StringUtils.toHtml(group.getName())) .append("</th></tr>"); } for (CustomFormField field : group) { // Determine if there is a property supplied for this field's value if (context.getFormPropertyMap() != null) { Map<String, String> propertyMap = context.getFormPropertyMap(); LOG.debug("Looking up propertyMap for name: " + field.getName()); if (propertyMap.containsKey(field.getName())) { String thisValue = propertyMap.get(field.getName()); if (StringUtils.hasText(thisValue)) { LOG.debug(" Setting value: " + thisValue); field.setValue(thisValue); } } } sb.append("<tr class=\"containerBody\">" + "<td valign=\"top\" class=\"formLabel\">") .append(StringUtils.toHtml(field.getLabel())) .append("</td>" + "<td valign=\"top\">").append(toHtmlFormField(field, context)); if (StringUtils.hasText(field.getAdditionalText())) { sb.append(" ").append(StringUtils.toHtml(field.getAdditionalText())); } sb.append("</td></tr>"); } sb.append("</table>"); } sb.append(CRLF); } else { LOG.debug("parseForm: displayMode"); // Construct HTML output for viewing the form data LOG.debug("constructing html output..."); boolean dataOutput = false; sb.append("<div class=\"infobox\">"); sb.append("<table class=\"pagedList\">"); for (CustomFormGroup group : form) { LOG.debug(" group..."); if (group.getDisplay() && StringUtils.hasText(group.getName())) { if (!dataOutput) { dataOutput = true; } sb.append("<tr><th colspan=\"2\">").append(StringUtils.toHtml(group.getName())) .append("</th></tr>"); } for (CustomFormField field : group) { LOG.debug(" field..."); if (field.hasValue()) { if (!dataOutput) { dataOutput = true; } sb.append("<tr class=\"containerBody\">"); if (field.getLabelDisplay()) { LOG.debug(" output w/label"); sb.append("<td class=\"formLabel\">").append(StringUtils.toHtml(field.getLabel())) .append("</td>"); sb.append("<td>"); sb.append(toHtml(field, context.getWiki(), context.getServerUrl())); sb.append("</td>"); } else { LOG.debug(" output"); sb.append("<td colspan=\"2\">"); sb.append("<center>"); sb.append(toHtml(field, context.getWiki(), context.getServerUrl())); sb.append("</center>"); sb.append("</td>"); } sb.append("</tr>"); } } } // Show the group names to the user if there are no fields to show if (!dataOutput) { LOG.debug("!dataOutput"); sb.append("<tr><td colspan=\"2\" align=\"center\">"); int count = 0; for (CustomFormGroup group : form) { ++count; if (count > 1) { sb.append("<br />"); } sb.append(StringUtils.toHtml(group.getName())); } sb.append("</td></tr>"); } LOG.debug("Check permissions"); if (context.getProject() != null && hasUserProjectAccess(db, context.getUserId(), context.getProject().getId(), "wiki", "add")) { sb.append("<tr><td colspan=\"2\" align=\"center\">"); sb.append("<a href=\"" + context.getServerUrl() + "/modify/" + context.getProject().getUniqueId() + "/wiki" + (StringUtils.hasText(context.getWiki().getSubject()) ? "/" + context.getWiki().getSubjectLink() : "") + "?form=1\">Fill out this form</a>"); sb.append("</td></tr>"); } sb.append("</table>"); sb.append("</div>"); } } LOG.debug("finished with form."); context.foundFormEnd(); return null; } protected static boolean parseLine(WikiToHTMLContext context, Connection db, String line, StringBuffer main) throws Exception { if (!context.canAppend()) { return false; } boolean needsCRLF = true; boolean bold = false; boolean italic = false; boolean bolditalic = false; boolean underline = false; StringBuffer subject = new StringBuffer(); StringBuffer sb = new StringBuffer(); StringBuffer data = new StringBuffer(); int linkL = 0; int linkR = 0; int attr = 0; int underlineAttr = 0; // parse characters for (int i = 0; i < line.length(); i++) { char c1 = line.charAt(i); String c = String.valueOf(c1); // False attr/links if (!"'".equals(c) && attr == 1) { data.append("'").append(c); attr = 0; continue; } if (!"_".equals(c) && underlineAttr == 1) { data.append("_").append(c); underlineAttr = 0; continue; } if (!"[".equals(c) && linkL == 1) { data.append("[").append(c); linkL = 0; continue; } if (!"]".equals(c) && linkR == 1) { data.append("]").append(c); linkR = 0; continue; } // Links if ("[".equals(c)) { ++linkL; continue; } if ("]".equals(c)) { ++linkR; if (linkL == 2 && linkR == 2) { flushData(data, sb); // Different type of links... String link = subject.toString(); if (link.startsWith("Image:") || link.startsWith("image:")) { // Image link WikiImageLink wikiImageLink = new WikiImageLink(link, context.getProjectId(), context.getImageList(), (i + 1 == line.length()), context.isEditMode(), context.getServerUrl()); sb.append(wikiImageLink.getValue()); needsCRLF = wikiImageLink.getNeedsCRLF(); } else if (link.startsWith("Video:") || link.startsWith("video:")) { // Video link WikiVideoLink wikiVideoLink = new WikiVideoLink(link, context.isEditMode(), context.getServerUrl()); sb.append(wikiVideoLink.getValue()); needsCRLF = wikiVideoLink.getNeedsCRLF(); } else { // Any other kind of link // Parser for inter-project wiki links WikiLink wikiLink = new WikiLink(link, context.getProjectId()); // Place a wiki link String cssClass = "wikiLink"; String url = null; if (WikiLink.REFERENCE.equals(wikiLink.getStatus())) { sb.append("<a class=\"wikiLink external\" target=\"_blank\" href=\"" + wikiLink.getEntity() + "\">" + StringUtils.toHtml(wikiLink.getName()) + "</a>"); } else { LOG.debug("Found wiki link: " + wikiLink.toString()); Project thisProject = null; if (wikiLink.getProjectId() > -1) { LOG.debug("Loading wiki link project: " + wikiLink.getProjectId()); thisProject = ProjectUtils.loadProject(wikiLink.getProjectId()); } else { thisProject = new Project(); } // Links... if ("profile".equalsIgnoreCase(wikiLink.getArea())) { // Links to a profile page cssClass = "wikiLink external"; url = context.getServerUrl() + "/show/" + thisProject.getUniqueId(); } else if ("badge".equalsIgnoreCase(wikiLink.getArea())) { // Links to a badge cssClass = "wikiLink external"; url = context.getServerUrl() + "/badge/" + wikiLink.getEntityId(); } else if ("wiki".equalsIgnoreCase(wikiLink.getArea())) { // Links to another wiki page if (StringUtils.hasText(wikiLink.getEntity())) { url = context.getServerUrl() + "/show/" + thisProject.getUniqueId() + "/wiki/" + wikiLink.getEntityTitle(); } else { url = context.getServerUrl() + "/show/" + thisProject.getUniqueId() + "/wiki"; } // Check to see if this is an external wiki if (context.getProjectId() > -1 && context.getProjectId() != thisProject.getId()) { cssClass = "wikiLink external"; } // Check to see if the target wiki exists to draw the wiki entry differently if (!WikiList.checkExistsBySubject(db, wikiLink.getEntity(), wikiLink.getProjectId())) { cssClass = "wikiLink newWiki"; // If user has access to edit, then use an edit link if (hasUserProjectAccess(db, context.getUserId(), wikiLink.getProjectId(), wikiLink.getArea(), "edit")) { String wikiSubject = StringUtils.hasText(wikiLink.getEntity()) ? "/" + wikiLink.getEntityTitle() : ""; url = context.getServerUrl() + "/modify/" + thisProject.getUniqueId() + "/wiki" + wikiSubject; } } } else { cssClass = "wikiLink external"; url = context.getServerUrl() + "/show/" + thisProject.getUniqueId() + "/" + wikiLink.getArea().toLowerCase() + (StringUtils.hasText(wikiLink.getEntity()) ? "/" + wikiLink.getEntityId() : ""); } // Display the resulting URL if (wikiLink.getProjectId() == -1 || wikiLink.getProjectId() == context.getProjectId() || hasUserProjectAccess(db, context.getUserId(), wikiLink.getProjectId(), wikiLink.getPermissionArea(), "view")) { String rel = ""; if ("app".equals(wikiLink.getArea())) { // open apps in a panel rel = " rel=\"shadowbox\""; } sb.append("<a class=\"" + cssClass + "\" href=\"" + url + "\"" + rel + ">" + StringUtils.toHtml(wikiLink.getName().trim()) + "</a>"); if (wikiLink.getName().endsWith(" ")) { sb.append(" "); } } else { LOG.debug("USING DENIED LINK"); cssClass = "wikiLink denied"; sb.append("<a class=\"" + cssClass + "\" href=\"#\" onmouseover=\"window.status='" + StringUtils.jsStringEscape(url) + ";'\">" + StringUtils.toHtml(wikiLink.getName()) + "</a>"); } } } subject.setLength(0); linkL = 0; linkR = 0; } continue; } if (!"[".equals(c) && linkL == 2 && !"]".equals(c)) { subject.append(c); continue; } // Attribute properties // TODO: Handle when there are more than 5 ''''' if ("'".equals(c)) { ++attr; continue; } if ("_".equals(c)) { ++underlineAttr; continue; } if (!"_".equals(c) && underlineAttr == 2) { if (!underline) { flushData(data, sb); sb.append("<span style=\"text-decoration: underline;\">"); data.append(c); underline = true; } else { flushData(data, sb); sb.append("</span>"); data.append(c); underline = false; } underlineAttr = 0; continue; } if (!"'".equals(c) && attr > 1) { if (attr == 2) { if (!italic) { flushData(data, sb); sb.append("<em>"); data.append(c); italic = true; } else { flushData(data, sb); sb.append("</em>"); data.append(c); italic = false; } attr = 0; continue; } if (attr == 3) { if (!bold) { flushData(data, sb); sb.append("<strong>"); data.append(c); bold = true; } else { flushData(data, sb); sb.append("</strong>"); data.append(c); bold = false; } attr = 0; continue; } if (attr >= 5) { if (!bolditalic) { flushData(data, sb); sb.append("<strong><em>"); data.append(c); bolditalic = true; } else { flushData(data, sb); sb.append("</em></strong>"); data.append(c); bolditalic = false; } attr = attr - 5; // TODO: if attr > 0 then need to set bold/itals cout continue; } } data.append(c); } for (int x = 0; x < linkR; x++) { data.append("]"); } for (int x = 0; x < linkL; x++) { data.append("["); } if (attr == 1) { data.append("'"); } if (underlineAttr == 1) { data.append("_"); } flushData(data, sb); if (italic) { sb.append("</em>"); } if (underline) { sb.append("</span>"); } if (bold) { sb.append("</strong>"); } if (bolditalic) { sb.append("</em></strong>"); } String newLine = sb.toString(); // handle strikethrough newLine = StringUtils.replace(newLine, StringUtils.toHtmlValue("<s>"), "<span style=\"text-decoration: line-through;\">"); newLine = StringUtils.replace(newLine, StringUtils.toHtmlValue("</s>"), "</span>"); newLine = StringUtils.replace(newLine, "\n", "<br />"); if (" ".equals(newLine)) { newLine = " "; } main.append(newLine); return needsCRLF; } protected static void append(WikiToHTMLContext context, StringBuffer sb, String content) { if (context.canAppend()) { String value = content; value = processMarkupCharacters(value); sb.append(value); } } protected static void flushData(StringBuffer data, StringBuffer sb) { if (data.length() > 0) { String value = data.toString(); value = processMarkupCharacters(value); sb.append(StringUtils.toHtmlValue(value, false, false)); data.setLength(0); } } private static boolean hasUserProjectAccess(Connection db, int userId, int projectId, String section, String permissionAction) throws SQLException { if (db == null) { LOG.error("hasUserProjectAccess: failed - database is null"); return false; } // Load the user (will not be found if a guest) User thisUser = null; try { thisUser = UserUtils.loadUser(userId); } catch (Exception notAUser) { LOG.warn("hasUserProjectAccess: userId error - " + userId); } // Setup the user as a guest if (thisUser == null) { LOG.debug("hasUserProjectAccess: userId not found - " + userId + " - using a guest user"); thisUser = UserUtils.createGuestUser(); } // Check access String permission = "project-" + section.toLowerCase(Locale.ENGLISH) + "-" + permissionAction; return ProjectUtils.hasAccess(projectId, thisUser, permission); } public static String extractValue(String param, String line) { int index = line.indexOf(" " + param + "=\""); if (index == -1) { return null; } int start = index + 1 + param.length() + 2; int end = line.indexOf("\"", start); if (end == -1) { return null; } return line.substring(start, end); } public static String toHtmlFormField(CustomFormField field, WikiToHTMLContext context) { // Set a default value if (field.getValue() == null) { field.setValue(field.getDefaultValue()); } // Protect against any arbitrary input String fieldName = StringUtils.toHtmlValue(field.getName()); // Return output based on type switch (field.getType()) { case CustomFormField.TEXTAREA: String textAreaValue = StringUtils.replace(field.getValue(), "^", CRLF); return ("<textarea cols=\"" + field.getColumns() + "\" rows=\"" + field.getRows() + "\" name=\"" + fieldName + "\">" + StringUtils.toString(textAreaValue) + "</textarea>"); case CustomFormField.SELECT: LookupList lookupList = field.getLookupList(); int selectedItemId = -1; for (LookupElement thisElement : lookupList) { if (field.getValue().equals(thisElement.getDescription())) { selectedItemId = thisElement.getCode(); } } return lookupList.getHtmlSelect(fieldName, selectedItemId); case CustomFormField.CHECKBOX: return ("<input type=\"checkbox\" name=\"" + fieldName + "\" value=\"ON\" " + ("true".equals(field.getValue()) ? "checked" : "") + ">"); case CustomFormField.CALENDAR: String calendarValue = field.getValue(); if (StringUtils.hasText(calendarValue)) { try { String convertedDate = DateUtils.getUserToServerDateTimeString(null, DateFormat.SHORT, DateFormat.LONG, field.getValue()); Timestamp timestamp = DatabaseUtils.parseTimestamp(convertedDate); Locale locale = Locale.getDefault(); int dateFormat = DateFormat.SHORT; SimpleDateFormat dateFormatter = (SimpleDateFormat) SimpleDateFormat.getDateInstance(dateFormat, locale); calendarValue = dateFormatter.format(timestamp); } catch (Exception e) { LOG.error("toHtmlFormField calendar", e); } } // Output with a calendar control String language = System.getProperty("LANGUAGE"); String country = System.getProperty("COUNTRY"); return ("<input type=\"text\" name=\"" + fieldName + "\" id=\"" + fieldName + "\" size=\"10\" value=\"" + StringUtils.toHtmlValue(calendarValue) + "\" > " + "<a href=\"javascript:popCalendar('inputForm', '" + fieldName + "','" + language + "','" + country + "');\">" + "<img src=\"" + context.getServerUrl() + "/images/icons/stock_form-date-field-16.gif\" " + "border=\"0\" align=\"absmiddle\" height=\"16\" width=\"16\"/></a>"); case CustomFormField.PERCENT: return ("<input type=\"text\" name=\"" + fieldName + "\" size=\"5\" value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\"> " + "%"); case CustomFormField.INTEGER: // Determine the value to display in the field String integerValue = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(integerValue)) { try { NumberFormat formatter = NumberFormat.getInstance(); integerValue = formatter.format(StringUtils.getIntegerNumber(field.getValue())); } catch (Exception e) { LOG.warn("Could not format integer: " + field.getValue()); } } return ("<input type=\"text\" name=\"" + fieldName + "\" size=\"8\" value=\"" + integerValue + "\"> "); case CustomFormField.FLOAT: // Determine the value to display in the field String decimalValue = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(decimalValue)) { try { NumberFormat formatter = NumberFormat.getInstance(); decimalValue = formatter.format(StringUtils.getDoubleNumber(field.getValue())); } catch (Exception e) { LOG.warn("Could not decimal format: " + field.getValue()); } } return ("<input type=\"text\" name=\"" + fieldName + "\" size=\"8\" value=\"" + decimalValue + "\"> "); case CustomFormField.CURRENCY: // Use a currencyCode for formatting String currencyCode = field.getValueCurrency(); if (currencyCode == null) { currencyCode = field.getCurrency(); } if (!StringUtils.hasText(currencyCode)) { currencyCode = "USD"; } HtmlSelect currencyCodeList = HtmlSelectCurrencyCode.getSelect(fieldName + "Currency", currencyCode); // Determine the valut to display in the field String currencyValue = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(currencyValue)) { try { NumberFormat formatter = NumberFormat.getNumberInstance(); formatter.setMaximumFractionDigits(2); currencyValue = formatter.format(StringUtils.getDoubleNumber(field.getValue())); } catch (Exception e) { LOG.warn("Could not currencyCode format: " + field.getValue()); } } return (currencyCodeList.getHtml() + "<input type=\"text\" name=\"" + fieldName + "\" size=\"8\" value=\"" + currencyValue + "\"> "); case CustomFormField.EMAIL: return ("<input type=\"text\" " + "name=\"" + fieldName + "\" maxlength=\"255\" size=\"40\" value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\" />"); case CustomFormField.PHONE: return ("<input type=\"text\" " + "name=\"" + fieldName + "\" maxlength=\"60\" size=\"20\" value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\" />"); case CustomFormField.URL: String value = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(value)) { if (!value.contains("://")) { value = "http://" + field.getValue(); } } return ("<input type=\"text\" " + "name=\"" + fieldName + "\" maxlength=\"255\" size=\"40\" value=\"" + StringUtils.toHtmlValue(value) + "\" />"); default: int maxlength = field.getMaxLength(); int size = -1; if (maxlength > -1) { if (maxlength > 40) { size = 40; } else { size = maxlength; } } return ("<input type=\"text\" " + "name=\"" + fieldName + "\" " + (maxlength == -1 ? "" : "maxlength=\"" + maxlength + "\" ") + (size == -1 ? "" : "size=\"" + size + "\" ") + "value=\"" + StringUtils.toHtmlValue(field.getValue()) + "\" />"); } } public static String toHtml(CustomFormField field, Wiki wiki, String contextPath) { // Return output based on type switch (field.getType()) { case CustomFormField.TEXTAREA: String textAreaValue = StringUtils.replace(field.getValue(), "^", CRLF); return StringUtils.toHtml(textAreaValue); case CustomFormField.SELECT: return StringUtils.toHtml(field.getValue()); case CustomFormField.CHECKBOX: if ("true".equals(field.getValue())) { return "Yes"; } else { return "No"; } case CustomFormField.CALENDAR: String calendarValue = field.getValue(); if (StringUtils.hasText(calendarValue)) { try { String convertedDate = DateUtils.getUserToServerDateTimeString(null, DateFormat.SHORT, DateFormat.LONG, field.getValue()); Timestamp timestamp = DatabaseUtils.parseTimestamp(convertedDate); Locale locale = Locale.getDefault(); int dateFormat = DateFormat.SHORT; SimpleDateFormat dateFormatter = (SimpleDateFormat) SimpleDateFormat.getDateInstance(dateFormat, locale); calendarValue = dateFormatter.format(timestamp); } catch (Exception e) { LOG.error("toHtml calendar", e); } } return StringUtils.toHtml(calendarValue); case CustomFormField.PERCENT: return StringUtils.toHtml(field.getValue()) + "%"; case CustomFormField.INTEGER: // Determine the value to display in the field String integerValue = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(integerValue)) { try { NumberFormat formatter = NumberFormat.getInstance(); integerValue = formatter.format(StringUtils.getIntegerNumber(field.getValue())); } catch (Exception e) { LOG.warn("Could not integer format: " + field.getValue()); } } return integerValue; case CustomFormField.FLOAT: // Determine the value to display in the field String decimalValue = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(decimalValue)) { try { NumberFormat formatter = NumberFormat.getInstance(); decimalValue = formatter.format(StringUtils.getDoubleNumber(field.getValue())); } catch (Exception e) { LOG.warn("Could not decimal format: " + field.getValue()); } } return decimalValue; case CustomFormField.CURRENCY: // Use a currency for formatting String currencyCode = field.getValueCurrency(); if (currencyCode == null) { currencyCode = field.getCurrency(); } if (!StringUtils.hasText(currencyCode)) { currencyCode = "USD"; } try { NumberFormat formatter = NumberFormat.getCurrencyInstance(); if (currencyCode != null) { Currency currency = Currency.getInstance(currencyCode); formatter.setCurrency(currency); } return (StringUtils.toHtml(formatter.format(StringUtils.getDoubleNumber(field.getValue())))); } catch (Exception e) { LOG.error("toHtml currency", e); } return StringUtils.toHtml(field.getValue()); case CustomFormField.EMAIL: return StringUtils.toHtml(field.getValue()); case CustomFormField.PHONE: PhoneNumberBean phone = new PhoneNumberBean(); phone.setNumber(field.getValue()); PhoneNumberUtils.format(phone, Locale.getDefault()); return StringUtils.toHtml(phone.toString()); case CustomFormField.URL: String value = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(value)) { if (!value.contains("://")) { value = "http://" + value; } if (value.contains("://")) { return ("<a href=\"" + StringUtils.toHtml(value) + "\">" + StringUtils.toHtml(value) + "</a>"); } } return StringUtils.toHtml(value); case CustomFormField.IMAGE: String image = StringUtils.toHtmlValue(field.getValue()); if (StringUtils.hasText(image)) { Project project = ProjectUtils.loadProject(wiki.getProjectId()); return ("<img src=\"" + contextPath + "/show/" + project.getUniqueId() + "/wiki-image/" + image + "\"/>"); } return StringUtils.toHtml(image); default: return StringUtils.toHtml(field.getValue()); } } /** * When round-tripping wiki markup, these characters are escaped for proper * parsing. * * @param text * @return */ public static String processMarkupCharacters(String text) { text = StringUtils.replace(text, "\\*", "*"); text = StringUtils.replace(text, "\\#", "#"); text = StringUtils.replace(text, "\\=", "="); text = StringUtils.replace(text, "\\|", "|"); text = StringUtils.replace(text, "\\{", "["); text = StringUtils.replace(text, "\\}", "]"); return text; } }