/******************************************************************************* * Copyright (C) 2010 Robert Munteanu <robert.munteanu@gmail.com> * * 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 *******************************************************************************/ package com.itsolut.mantis.core.util; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * @author Robert Munteanu */ public abstract class HtmlFormatter { private static final String HTML_PRE ="" + "<html>" + " <head><style type='text/css'>" + " body { " + " margin: 0; padding: 0;" + " font-size: %spt;" + " font-family: %s;" + " font-weight: %s;" + " font-style: %s;"+ " } " + " </style></head>" + " <body>" + ""; private static final String HTML_POST = "" + " </body>" + "</html>"; /** * This function mimics the behaviour of MantisBT's <tt>string_nl2br</tt> * method * * <p> * All newlines have a <tt>{@literal <br/>}</tt> tag appended, except for * those contained in {@literal <pre></pre>} tags. * </p> * * @param input * @return */ public static String convertToDisplayHtml(String input) { List<Range> ranges = parseIntoRanges(input); StringBuilder output = new StringBuilder(input.length()); for ( Range range : ranges ) { String rangeString = input.substring(range.from, range.to); if ( range.rangeKind == RangeKind.TEXT ) { boolean applyFinalBr = range.to != input.length(); output.append(nl2br(rangeString, applyFinalBr)); } else output.append(rangeString); } return output.toString(); } private static List<Range> parseIntoRanges(String input) { List<Range> ranges = new ArrayList<Range>(); int pos = 0; while ( pos < input.length() ) { TagRange range = TagRange.find(input, pos); int startIndex = range != null ? range.getStart() : -1; int endIndex = range != null ? range.getEnd() : -1; RangeKind rangeKind; if ( range == null ) rangeKind = RangeKind.TEXT; else if ( range.getTag() == Tag.pre ) rangeKind = RangeKind.TAG_PRESERVE_NL; else rangeKind = RangeKind.TAG_CLEAN_NL; // the found tag range is after the current start // this denotes a text gap if ( startIndex > pos ) { endIndex = startIndex + 1; startIndex = pos; rangeKind = RangeKind.TEXT; } if ( pos == 0 && startIndex > 0 ) { // tag exists, but is not the first ranges.add(new Range(pos, startIndex - 1, rangeKind)); } else if ( pos == 0 && startIndex == -1 ) { // first iteration, tag does not exist at all ranges.add(new Range(pos, input.length(), RangeKind.TEXT)); break; } else if ( startIndex == -1 || endIndex == -1) { // last iteration, tag is no longer found ranges.add(new Range(pos, input.length(), RangeKind.TEXT)); break; } // tag found ranges.add(new Range(startIndex, endIndex - 1, rangeKind)); pos = endIndex -1; } return ranges; } public static String convertFromDisplayHtml(String input) { List<Range> ranges = parseIntoRanges(input); StringBuilder output = new StringBuilder(input.length()); for (Range range : ranges) { String rangeString = input.substring(range.from, range.to); if (range.rangeKind == RangeKind.TEXT) output.append(rangeString.replaceAll("<br\\s?/>\\n?", "\n")); else if ( range.rangeKind == RangeKind.TAG_CLEAN_NL ) output.append(rangeString.replaceAll("\n", "")); else output.append(rangeString); } return output.toString(); } private static String nl2br(String input, boolean applyFinalBr) { String[] lines = input.split("\n"); StringBuilder output = new StringBuilder(input.length()); for ( int i = 0; i < lines.length ; i++) { output.append(lines[i]); if ( i + 1 < lines.length || applyFinalBr ) output.append("<br/>"); } return output.toString(); } /** * Wraps the specified <tt>value</tt> in a complete HTML declaration, ensuring that is optimised for display * * @param htmlSnippet the value to wrap * @param fontFamily the font-family of the font * @param fontSizePt the size of the font in points * @param isBold true if the text should be bold * @param isItalic true if the text should be italic * @return the wrapped value */ public static String wrapForBrowserDisplay(String htmlSnippet, String fontFamily, int fontSizePt, boolean isBold, boolean isItalic) { String boldStyle = isBold ? "bold" : "normal"; String italicStyle = isItalic ? "italic" : "normal"; return String.format(HTML_PRE, fontSizePt, fontFamily, boldStyle, italicStyle) + htmlSnippet + HTML_POST; } private HtmlFormatter() { } private enum RangeKind { TAG_PRESERVE_NL, TAG_CLEAN_NL, TEXT, } private static class Range { public int from; public int to; public RangeKind rangeKind; public Range(int from, int to, RangeKind rangeKind) { this.from = from; this.to = to; this.rangeKind = rangeKind; } } private static enum Tag { pre, ol, ul; } private static class TagRange implements Comparable<TagRange> { private final int start; private final int end; private final Tag tag; public TagRange(String input, Tag tag, int offset) { String openTag = "<" + tag.toString() + ">"; String closeTag = "</" + tag.toString() + ">"; start = input.indexOf(openTag, offset); int tagEnd = input.indexOf(closeTag, offset) + closeTag.length() + 1; int allLength = input.length(); if ( tagEnd < allLength ) { char nextChar = input.charAt(tagEnd - 1); if ( nextChar == '\n') tagEnd++; } end = tagEnd; this.tag = tag; } public static TagRange find(String input, int start ) { Set<TagRange> allRanges = new TreeSet<TagRange>(); for ( Tag tag : Tag.values()) { TagRange tagRange = new TagRange(input, tag, start); if ( tagRange.exists() ) allRanges.add(tagRange); } if ( allRanges.isEmpty() ) return null; return allRanges.iterator().next(); } public int getStart() { return start; } public int getEnd() { return end; } public Tag getTag() { return tag; } public boolean exists() { return start != -1; } public int compareTo(TagRange other) { return Integer.valueOf(start).compareTo(other.start); } } }