001: /*
002: * MessageFormatter.java
003: *
004: * Copyright (C) 1998-2003 Peter Graves
005: * $Id: MessageFormatter.java,v 1.8 2003/07/03 18:08:13 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import gnu.regexp.RE;
025: import gnu.regexp.UncheckedRE;
026: import org.armedbear.j.Buffer;
027: import org.armedbear.j.Debug;
028: import org.armedbear.j.DiffFormatter;
029: import org.armedbear.j.Editor;
030: import org.armedbear.j.FormatTable;
031: import org.armedbear.j.Formatter;
032: import org.armedbear.j.Line;
033: import org.armedbear.j.LineSegmentList;
034: import org.armedbear.j.Utilities;
035:
036: public final class MessageFormatter extends Formatter {
037: // Message formats must not overlap with diff formats!
038: private static final int MESSAGE_FORMAT_FIRST = DiffFormatter.DIFF_FORMAT_LAST + 1;
039:
040: private static final byte MESSAGE_FORMAT_TEXT = MESSAGE_FORMAT_FIRST;
041: private static final byte MESSAGE_FORMAT_COMMENT = MESSAGE_FORMAT_FIRST + 1;
042: private static final byte MESSAGE_FORMAT_HEADER_NAME = MESSAGE_FORMAT_FIRST + 2;
043: private static final byte MESSAGE_FORMAT_HEADER_VALUE = MESSAGE_FORMAT_FIRST + 3;
044: private static final byte MESSAGE_FORMAT_QUOTE = MESSAGE_FORMAT_FIRST + 4;
045: private static final byte MESSAGE_FORMAT_SIGNATURE = MESSAGE_FORMAT_FIRST + 5;
046: private static final byte MESSAGE_FORMAT_DIFF = MESSAGE_FORMAT_FIRST + 6;
047:
048: private static final RE quoteRE = new UncheckedRE("^[a-zA-Z]*>");
049:
050: // Includes '/' for "Parts/Attachments".
051: private static final RE headerRE = new UncheckedRE(
052: "^ *[a-zA-Z\\-/]+:");
053:
054: private Line startOfBody;
055:
056: private final DiffFormatter diffFormatter;
057:
058: public MessageFormatter(Buffer buffer) {
059: this .buffer = buffer;
060: diffFormatter = new DiffFormatter(buffer);
061: }
062:
063: public synchronized LineSegmentList formatLine(Line line) {
064: if (line.flags() == MESSAGE_FORMAT_DIFF)
065: return diffFormatter.formatLine(line);
066: final String text = getDetabbedText(line);
067: clearSegmentList();
068: if (line.flags() == MESSAGE_FORMAT_SIGNATURE) {
069: addSegment(text, MESSAGE_FORMAT_SIGNATURE);
070: return segmentList;
071: }
072: String trim = text.trim();
073: if (trim.length() == 0) {
074: // Line is empty or all whitespace.
075: addSegment(text, MESSAGE_FORMAT_TEXT);
076: return segmentList;
077: }
078: if (startOfBody == null
079: || line.lineNumber() < startOfBody.lineNumber()) {
080: // We're in the message headers.
081: if (text.length() > 0) {
082: int i = text.indexOf(':');
083: if (i >= 0 && headerRE.getMatch(text) != null) {
084: String headerName = text.substring(0, i).trim();
085: if (isKeyword(headerName)) {
086: addSegment(text, 0, i + 1,
087: MESSAGE_FORMAT_HEADER_NAME);
088: addSegment(text, i + 1,
089: MESSAGE_FORMAT_HEADER_VALUE);
090: return segmentList;
091: }
092: }
093: }
094: if (line.flags() == MESSAGE_FORMAT_HEADER_VALUE)
095: addSegment(text, MESSAGE_FORMAT_HEADER_VALUE);
096: else
097: addSegment(text, MESSAGE_FORMAT_COMMENT);
098: return segmentList;
099: }
100: char c = trim.charAt(0);
101: if (c == '>') {
102: if (trim.startsWith(">>>>> ")) {
103: // Supercite attribution line.
104: addSegment(text, MESSAGE_FORMAT_TEXT);
105: } else if (trim.startsWith(">From ")) {
106: addSegment(text, MESSAGE_FORMAT_TEXT);
107: } else {
108: addSegment(text, MESSAGE_FORMAT_QUOTE);
109: }
110: return segmentList;
111: }
112: if (quoteRE.getMatch(text) != null) {
113: addSegment(text, MESSAGE_FORMAT_QUOTE);
114: return segmentList;
115: }
116: addSegment(text, MESSAGE_FORMAT_TEXT);
117: return segmentList;
118: }
119:
120: public synchronized boolean parseBuffer() {
121: startOfBody = null;
122: boolean inDiff = false;
123: boolean inSig = false;
124: if (buffer.needsRenumbering())
125: buffer.renumber();
126: for (Line line = buffer.getFirstLine(); line != null; line = line
127: .next()) {
128: if (buffer instanceof MessageBuffer) {
129: if (line.lineNumber() == ((MessageBuffer) buffer)
130: .getHeaderLineCount()) {
131: if (startOfBody != null)
132: Debug.bug();
133: startOfBody = line;
134: }
135: }
136: String text = line.getText();
137: if (text == null)
138: continue;
139: if (startOfBody == null) {
140: if (buffer instanceof SendMail
141: && text.equals(SendMail.getHeaderSeparator()))
142: startOfBody = line.next();
143: else {
144: int i = text.indexOf(':');
145: if (i >= 0 && headerRE.getMatch(text) != null) {
146: String headerName = text.substring(0, i).trim();
147: if (isKeyword(headerName))
148: line.setFlags(MESSAGE_FORMAT_HEADER_VALUE);
149: else
150: line.setFlags(0);
151: } else if (line.previous() != null)
152: line.setFlags(line.previous().flags());
153: }
154: } else {
155: // We're in the body of the message.
156: if (text.equals("-- ")) {
157: inSig = true;
158: inDiff = false;
159: line.setFlags(MESSAGE_FORMAT_SIGNATURE);
160: continue;
161: }
162: if (inDiff) {
163: if (isDiffContinuation(line)) {
164: line.setFlags(MESSAGE_FORMAT_DIFF);
165: continue;
166: }
167: inDiff = false;
168: // Fall through...
169: } else if (isDiffStart(line)) {
170: inDiff = true;
171: line.setFlags(MESSAGE_FORMAT_DIFF);
172: continue;
173: }
174: if (inDiff)
175: Debug.bug();
176: if (inSig) {
177: line.setFlags(MESSAGE_FORMAT_SIGNATURE);
178: continue;
179: }
180: // Neutral state.
181: line.setFlags(0);
182: if (text.length() == 0)
183: continue;
184: final char c = text.charAt(0);
185: if (buffer.getLineCount() - line.lineNumber() < 16) {
186: if (c == '_' || c == '-') {
187: // See if line is all underscores or all hyphens.
188: text = text.trim();
189: boolean all = true;
190: for (int i = text.length(); i-- > 0;) {
191: if (text.charAt(i) != c) {
192: all = false;
193: break;
194: }
195: }
196: if (all) {
197: inSig = true;
198: line.setFlags(MESSAGE_FORMAT_SIGNATURE);
199: continue;
200: }
201: }
202: }
203: }
204: }
205: buffer.setNeedsParsing(false);
206: return true;
207: }
208:
209: public static boolean isDiffStart(Line line) {
210: String text = line.trim();
211: if (text.startsWith("+++ "))
212: return true;
213: if (text.startsWith("--- "))
214: return true;
215: if (text.startsWith("@@ "))
216: return true;
217: if (text.startsWith("@") && text.endsWith("@"))
218: return true;
219: if (text.startsWith("diff ")) {
220: Line nextLine = line.next();
221: if (nextLine != null && nextLine.trim().startsWith("---"))
222: return true;
223: } else if (text.startsWith("Index: ")) {
224: Line nextLine = line.next();
225: if (nextLine != null) {
226: String s = nextLine.trim();
227: if (s.startsWith("diff ") || s.startsWith("========"))
228: return true;
229: }
230: }
231: return false;
232: }
233:
234: public static boolean isDiffContinuation(Line line) {
235: if (line.length() == 0)
236: return true;
237: final char c = line.charAt(0);
238: if (c == ' ')
239: return true;
240: if (c == '+')
241: return true;
242: if (c == '-') {
243: Line nextLine = line.next();
244: return (nextLine != null && isDiffContinuation(nextLine));
245: }
246: String text = line.getText();
247: if (text.startsWith("diff "))
248: return true;
249: if (text.startsWith("Index: "))
250: return true;
251: if (text.startsWith("========"))
252: return true;
253: if (text.startsWith("RCS file: "))
254: return true;
255: if (text.startsWith("retrieving "))
256: return true;
257: if (text.startsWith("@@"))
258: return true;
259: if (text.startsWith("@") && text.endsWith("@"))
260: return true;
261: return false;
262: }
263:
264: public FormatTable getFormatTable() {
265: if (formatTable == null) {
266: formatTable = diffFormatter.getFormatTable();
267: formatTable.setModeName("MessageMode");
268: formatTable.addEntryFromPrefs(MESSAGE_FORMAT_TEXT, "text");
269: formatTable.addEntryFromPrefs(MESSAGE_FORMAT_COMMENT,
270: "comment");
271: formatTable.addEntryFromPrefs(MESSAGE_FORMAT_HEADER_NAME,
272: "headerName", "keyword");
273: formatTable.addEntryFromPrefs(MESSAGE_FORMAT_HEADER_VALUE,
274: "headerValue", "operator");
275: formatTable.addEntryFromPrefs(MESSAGE_FORMAT_QUOTE,
276: "string");
277: formatTable.addEntryFromPrefs(MESSAGE_FORMAT_SIGNATURE,
278: "signature", "comment");
279: }
280: return formatTable;
281: }
282:
283: public void reset() {
284: diffFormatter.reset();
285: super.reset();
286: }
287: }
|