/*
Jreepad - personal information manager.
Copyright (C) 2004-2006 Dan Stowell
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
The full license can be read online here:
http://www.gnu.org/copyleft/gpl.html
*/
package jreepad;
import java.text.CharacterIterator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.StringCharacterIterator;
import java.util.Date;
import javax.swing.undo.UndoManager;
import org.philwilson.JTextile;
/**
* Class representing a single article without its tree context.
*
* @version $Id$
*/
public class JreepadArticle
{
/**
* The default name for unnamed nodes.
*/
public static final String UNTITLED_NODE_TEXT = "<Untitled node>";
public static final int ARTICLEMODE_ORDINARY = 1;
public static final int ARTICLEMODE_HTML = 2;
public static final int ARTICLEMODE_CSV = 3;
public static final int ARTICLEMODE_TEXTILEHTML = 4;
public static final int CSVPARSE_MODE_INQUOTES = 1;
public static final int CSVPARSE_MODE_EXPECTINGDELIMITER = 2;
public static final int CSVPARSE_MODE_EXPECTINGDATA = 3;
public static final int EXPORT_HTML_NORMAL = 0;
public static final int EXPORT_HTML_PREFORMATTED = 1;
public static final int EXPORT_HTML_HTML = 2;
public static final int EXPORT_HTML_TEXTILEHTML = 3;
public static final int EXPORT_HTML_ANCHORS_PATH = 0;
public static final int EXPORT_HTML_ANCHORS_WIKI = 1;
private String title;
private String content;
private int articleMode;
private UndoManager undoMgr;
public JreepadArticle()
{
this(getNewContent());
}
public JreepadArticle(String content)
{
this(UNTITLED_NODE_TEXT, content);
}
public JreepadArticle(String title, String content)
{
this(title, content, ARTICLEMODE_ORDINARY);
}
public JreepadArticle(String title, String content, int articleMode)
{
if (title == null || title.equals(""))
this.title = UNTITLED_NODE_TEXT;
else
this.title = title;
this.content = content;
this.articleMode = articleMode;
undoMgr = new UndoManager();
}
public int getArticleMode()
{
return articleMode;
}
public void setArticleMode(int articleMode)
{
this.articleMode = articleMode;
}
public String getContent()
{
return content;
}
public void setContent(String content)
{
this.content = content;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
if (title == null || title.equals(""))
this.title = UNTITLED_NODE_TEXT;
else
this.title = title;
}
public UndoManager getUndoMgr()
{
return undoMgr;
}
public String toString()
{
return title;
}
public static final String[] getHtmlExportArticleTypes()
{
return new String[] { JreepadViewer.lang.getString("PREFS_EXPORTTYPE_TEXT"),
JreepadViewer.lang.getString("PREFS_EXPORTTYPE_PREFORMATTED"),
JreepadViewer.lang.getString("PREFS_EXPORTTYPE_HTML"),
JreepadViewer.lang.getString("PREFS_EXPORTTYPE_TEXTILEHTML") };
}
public static final String[] getHtmlExportAnchorLinkTypes()
{
return new String[] { "node:// links", "WikiLike links" };
}
public String exportAsHtml(int exportMode, boolean urlsToLinks, int anchorType,
boolean causeToPrint)
{
StringBuffer ret = new StringBuffer();
ret.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n<title>");
ret.append(htmlSpecialChars(getTitle()));
ret.append("</title>\n<style type=\"text/css\">\n"
+ "dl {}\ndl dt { font-weight: bold; margin-top: 10px; font-size: 24pt; }\ndl dd {margin-left: 20px; padding-left: 0px;}\ndl dd dl dt {background: black; color: white; font-size: 12pt; }\ndl dd dl dd dl dt {background: white; color: black; }"
+ "\n</style>\n</head>\n\n<body" + (causeToPrint ? " onload='print();'" : "")
+ ">\n<!-- Exported from Jreepad -->\n<dl>");
ret.append(toHtml(exportMode, urlsToLinks, anchorType));
ret.append("\n</dl>\n</body>\n</html>");
return ret.toString();
}
public String toHtml(int exportMode, boolean urlsToLinks, int anchorType)
{
switch (articleMode)
{
case ARTICLEMODE_HTML:
return getContent();
case ARTICLEMODE_TEXTILEHTML:
try
{
return JTextile.textile(getContent());
}
catch (Exception e)
{
return getContent();
}
case ARTICLEMODE_CSV:
String[][] csv = interpretContentAsCsv();
StringBuffer csvHtml = new StringBuffer(
"\n <table border='1' cellspacing='0' cellpadding='2'>");
for (int i = 0; i < csv.length; i++)
{
csvHtml.append("\n <tr>");
for (int j = 0; j < csv[0].length; j++)
csvHtml.append("\n <td>" + htmlSpecialChars(csv[i][j]) + "</td>");
csvHtml.append("\n </tr>");
}
csvHtml.append("\n </table>");
return csvHtml.toString();
case ARTICLEMODE_ORDINARY:
default:
switch (exportMode)
{
case EXPORT_HTML_PREFORMATTED:
return "<pre>"
+ (urlsToLinks ? urlsToHtmlLinksAndHtmlSpecialChars(getContent(), anchorType)
: htmlSpecialChars(getContent())) + "</pre>";
case EXPORT_HTML_HTML:
return getContent();
case EXPORT_HTML_TEXTILEHTML:
try
{
return JTextile.textile(getContent());
}
catch (Exception e)
{
return getContent();
}
case EXPORT_HTML_NORMAL:
default:
return (urlsToLinks ? urlsToHtmlLinksAndHtmlSpecialChars(getContent(), anchorType)
: htmlSpecialChars(getContent()));
}
}
}
private static String htmlSpecialChars(String in)
{
char[] c = in.toCharArray();
StringBuffer ret = new StringBuffer();
for (int i = 0; i < c.length; i++)
if (c[i] == '<')
ret.append("<");
else if (c[i] == '>')
ret.append(">");
else if (c[i] == '&')
ret.append("&");
else if (c[i] == '\n')
ret.append(" <br />\n");
else if (c[i] == '"')
ret.append(""");
else
ret.append(c[i]);
return ret.toString();
}
// Search through the String, replacing URI-like substrings (containing ://) with HTML links
private String urlsToHtmlLinksAndHtmlSpecialChars(String in, int anchorType)
{
StringCharacterIterator iter = new StringCharacterIterator(in);
StringBuffer out = new StringBuffer("");
StringBuffer currentWord = new StringBuffer(""); // "space" characters get stuck straight
// back out, but words need aggregating
char c = iter.current(), c2;
while (true)
{
if (Character.isWhitespace(c) || c == '"' || c == '\'' || c == '<' || c == '>'
|| c == '\n' || c == CharacterIterator.DONE)
{
// // First check whether currentWord is empty...?
// if(c!=CharacterIterator.DONE && currentWord.length()==0)
// continue;
// Check if the current word is a URL - do weird stuff to it if so, else just output
// it
if (currentWord.toString().indexOf("://") > 0)
{
// We don't like quotes - let's remove 'em!
// Ideally, a beginning quote would signify that we need to keep on searching
// until we find an end quote
// but that aspect is NOT IMPLEMENTED YET
c2 = currentWord.charAt(0);
if (c2 == '"' || c2 == '\'')
currentWord.deleteCharAt(0);
c2 = currentWord.charAt(currentWord.length() - 1);
if (c2 == '"' || c2 == '\'')
currentWord.deleteCharAt(currentWord.length() - 1);
// At this stage, beginning with "node://" should indicate that we want an
// anchor link not a "real" HTML link
String currentWordString = currentWord.toString();
if (currentWordString.startsWith("node://"))
{
String anchorLink;
if (anchorType == EXPORT_HTML_ANCHORS_WIKI)
anchorLink = currentWordString.substring(currentWordString
.lastIndexOf('/') + 1);
else
anchorLink = currentWordString.substring(7);
out.append("<a href=\"#" + anchorLink + "\">" + currentWordString + "</a>");
}
else
out.append("<a href=\"" + currentWord + "\">" + currentWordString + "</a>");
}
else if (anchorType == EXPORT_HTML_ANCHORS_WIKI
&& JreepadView.isWikiWord(currentWord.toString()))
{
String currentWordString = currentWord.toString();
if (currentWordString.length() > 4 && currentWordString.startsWith("[[")
&& currentWordString.endsWith("]]"))
currentWordString = currentWordString.substring(2, currentWordString
.length() - 2);
out.append("<a href=\"#" + currentWordString + "\">" + currentWordString
+ "</a>");
}
else
out.append(currentWord.toString());
if (c == '<')
out.append("<");
else if (c == '>')
out.append(">");
else if (c == '\n')
out.append(" <br />\n");
else if (c == '"')
out.append(""");
else if (c == '&')
out.append("&");
else if (c != CharacterIterator.DONE)
out.append(c);
currentWord.setLength(0);
if (c == CharacterIterator.DONE)
break;
}
else
{
currentWord.append(c); // Just aggregate character onto current "Word"
}
c = iter.next();
} // End "while"
return out.toString();
}
public synchronized void wrapContentToCharWidth(int charWidth)
{
if (charWidth < 2)
return;
StringBuffer ret = new StringBuffer();
StringCharacterIterator iter = new StringCharacterIterator(content);
int charsOnThisLine = 0;
for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
{
if (c == '\n')
charsOnThisLine = 0;
else if (++charsOnThisLine >= charWidth)
{
ret.append('\n');
charsOnThisLine = 0;
}
ret.append(c);
}
content = ret.toString();
}
public synchronized void stripAllTags()
{
StringBuffer ret = new StringBuffer();
StringCharacterIterator iter = new StringCharacterIterator(content);
boolean on = true;
for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
{
if ((!on) && c == '>')
on = true;
else if (on && c == '<')
on = false;
else if (on)
ret.append(c);
}
content = ret.toString();
}
public String[][] interpretContentAsCsv()
{
String theContent = getContent();
int rows = 1;
int cols = 1;
theContent = theContent.trim();
char c;
int curCols = 1;
int parseMode = CSVPARSE_MODE_EXPECTINGDATA;
StringBuffer curData = new StringBuffer();
// Go through once to determine the number of rows and columns
StringCharacterIterator iter = new StringCharacterIterator(theContent);
c = iter.current();
while (true)
{
if (c == CharacterIterator.DONE)
{
// / System.out.println("I've just parsed this data item: " + curData + " and I'm in
// mode " + parseMode);
cols = (curCols > cols) ? curCols : cols;
break;
}
if (parseMode == CSVPARSE_MODE_INQUOTES)
{
if (c == '"')
{
parseMode = CSVPARSE_MODE_EXPECTINGDELIMITER;
}
else
{
curData.append(c);
}
}
else if (parseMode == CSVPARSE_MODE_EXPECTINGDELIMITER
|| parseMode == CSVPARSE_MODE_EXPECTINGDATA)
{
if (c == '"')
{
parseMode = CSVPARSE_MODE_INQUOTES;
}
else if (c == '\n' || c == Character.LINE_SEPARATOR)
{
parseMode = CSVPARSE_MODE_EXPECTINGDATA;
// / System.out.println("I've just parsed this data item: " + curData + " and
// I'm in mode " + parseMode);
curData = new StringBuffer();
cols = (curCols > cols) ? curCols : cols;
curCols = 1;
rows++;
}
else if (c == ',')
{
parseMode = CSVPARSE_MODE_EXPECTINGDATA;
curCols++;
// / System.out.println("I've just parsed this data item: " + curData + " and
// I'm in mode " + parseMode);
curData = new StringBuffer();
}
else
{
curData.append(c);
}
}
c = iter.next();
}
// Now go through and actually assign the content
String[][] csv = new String[rows][cols];
for (int i = 0; i < csv.length; i++)
java.util.Arrays.fill(csv[i], "");
iter = new StringCharacterIterator(theContent);
parseMode = CSVPARSE_MODE_EXPECTINGDATA;
curData = new StringBuffer();
int col = 0;
int row = 0;
c = iter.current();
while (true)
{
if (c == CharacterIterator.DONE)
{
csv[row][col] = curData.toString();
break;
}
if (parseMode == CSVPARSE_MODE_INQUOTES)
{
if (c == '"')
{
parseMode = CSVPARSE_MODE_EXPECTINGDELIMITER;
}
else
{
curData.append(c);
}
}
else if (parseMode == CSVPARSE_MODE_EXPECTINGDELIMITER
|| parseMode == CSVPARSE_MODE_EXPECTINGDATA)
{
if (c == '"')
{
parseMode = CSVPARSE_MODE_INQUOTES;
}
else if (c == '\n' || c == Character.LINE_SEPARATOR)
{
csv[row][col] = curData.toString();
parseMode = CSVPARSE_MODE_EXPECTINGDATA;
curData = new StringBuffer();
col = 0;
row++;
}
else if (c == ',')
{
csv[row][col] = curData.toString();
parseMode = CSVPARSE_MODE_EXPECTINGDATA;
col++;
curData = new StringBuffer();
}
else
{
curData.append(c);
}
}
c = iter.next();
}
return csv;
}
protected void setContentAsCsv(String[][] in)
{
StringBuffer o = new StringBuffer();
for (int i = 0; i < in.length; i++)
{
for (int j = 0; j < in[0].length; j++)
{
o.append("\"" + in[i][j] + "\"");
}
o.append("\n");
}
setContent(o.toString());
}
/**
* Returns the content for a new node. It is either empty or with a timestamp.
*/
public static String getNewContent()
{
if (JreepadView.getPrefs().autoDateInArticles)
return getCurrentDate();
return "";
}
/**
* Returns the current time and date. The date is formatted acording to the
* preferences. If the format is not set in thepreferences, the default
* format is used.
*/
public static String getCurrentDate()
{
DateFormat dateFormat = null;
String format = JreepadView.getPrefs().dateFormat;
if (!format.equals(""))
{
try
{
dateFormat = new SimpleDateFormat(format);
}
catch (IllegalArgumentException e)
{
// Default format will be set
// TODO: Log this
}
}
if (dateFormat == null)
dateFormat = DateFormat.getDateInstance();
return dateFormat.format(new Date());
}
}