KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > HtmlMode


1 /*
2  * HtmlMode.java
3  *
4  * Copyright (C) 1998-2004 Peter Graves
5  * $Id: HtmlMode.java,v 1.2 2004/04/22 14:58:09 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

21
22 package org.armedbear.j;
23
24 import gnu.regexp.RE;
25 import gnu.regexp.REException;
26 import gnu.regexp.REMatch;
27 import java.awt.event.KeyEvent JavaDoc;
28 import java.io.BufferedReader JavaDoc;
29 import java.io.FileInputStream JavaDoc;
30 import java.io.FileNotFoundException JavaDoc;
31 import java.io.IOException JavaDoc;
32 import java.io.InputStream JavaDoc;
33 import java.io.InputStreamReader JavaDoc;
34 import java.util.List JavaDoc;
35 import javax.swing.undo.CompoundEdit JavaDoc;
36
37 public final class HtmlMode extends AbstractMode implements Constants, Mode
38 {
39     private static final Mode mode = new HtmlMode();
40     private static List JavaDoc elements;
41     private static RE tagNameRE;
42     private static RE attributeNameRE;
43     private static RE quotedValueRE;
44     private static RE unquotedValueRE;
45
46     private HtmlMode()
47     {
48         super(HTML_MODE, HTML_MODE_NAME);
49         // Support embedded JavaScript.
50
keywords = new Keywords(JavaScriptMode.getMode());
51     }
52
53     public static final Mode getMode()
54     {
55         return mode;
56     }
57
58     public Formatter getFormatter(Buffer buffer)
59     {
60         return new HtmlFormatter(buffer);
61     }
62
63     protected void setKeyMapDefaults(KeyMap km)
64     {
65         km.mapKey(KeyEvent.VK_TAB, 0, "tab");
66         km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab");
67         km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
68         km.mapKey(KeyEvent.VK_ENTER, CTRL_MASK, "newline");
69         km.mapKey(KeyEvent.VK_M, CTRL_MASK, "htmlFindMatch");
70         km.mapKey(KeyEvent.VK_E, CTRL_MASK, "htmlInsertMatchingEndTag");
71         km.mapKey(KeyEvent.VK_B, CTRL_MASK, "htmlBold");
72         km.mapKey('=', "htmlElectricEquals");
73         km.mapKey('>', "electricCloseAngleBracket");
74         km.mapKey(KeyEvent.VK_V, CTRL_MASK | ALT_MASK, "viewPage");
75         km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
76
77         // These are the "normal" mappings.
78
km.mapKey(KeyEvent.VK_COMMA, CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
79         km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | SHIFT_MASK, "htmlEndTag");
80
81         // The "normal" mappings don't work for Linux, but these do.
82
km.mapKey(0x7c, CTRL_MASK | SHIFT_MASK, "htmlInsertTag");
83         km.mapKey(0x7e, CTRL_MASK | SHIFT_MASK, "htmlEndTag");
84     }
85
86     public boolean canIndent()
87     {
88         return true;
89     }
90
91     public boolean canIndentPaste()
92     {
93         return false;
94     }
95
96     public int getCorrectIndentation(Line line, Buffer buffer)
97     {
98         if (line.flags() == STATE_SCRIPT)
99             return JavaScriptMode.getMode().getCorrectIndentation(line, buffer);
100         // Ignore comments.
101
if (line.flags() == STATE_HTML_COMMENT)
102             return buffer.getIndentation(line); // Unchanged.
103
if (line.trim().startsWith("<!--"))
104             return buffer.getIndentation(line); // Unchanged.
105
Line model = getModel(line);
106         if (model == null)
107             return 0;
108         int indent = buffer.getIndentation(model);
109         if (line.trim().startsWith("</")) {
110             Position pos = findMatchingStartTag(line);
111             if (pos != null)
112                 return buffer.getIndentation(pos.getLine());
113             indent -= buffer.getIndentSize();
114             if (indent < 0)
115                 indent = 0;
116             return indent;
117         }
118         final String JavaDoc trim = model.trim();
119         if (trim.startsWith("<") && !trim.startsWith("</")) {
120             if (trim.startsWith("<!")) // Document type declaration.
121
return indent;
122             // Model starts with start tag.
123
String JavaDoc name = Utilities.getTagName(trim).toLowerCase();
124             if (name.equals("html") || name.equals("head") || name.equals("body") || name.equals("form"))
125                 return indent;
126             boolean wantsEndTag = wantsEndTag(name);
127             if (wantsEndTag) {
128                 String JavaDoc startTag = "<" + name;
129                 String JavaDoc endTag = "</" + name;
130                 int count = 1;
131                 int limit = trim.length();
132                 for (int i = startTag.length(); i < limit; i++) {
133                     if (trim.charAt(i) == '<') {
134                         if (lookingAtIgnoreCase(trim, i, endTag))
135                             --count;
136                         else if (lookingAtIgnoreCase(trim, i, startTag))
137                             ++count;
138                     }
139                 }
140                 if (count > 0)
141                     indent += buffer.getIndentSize();
142             }
143         }
144         return indent;
145     }
146
147     // Line must start with an end tag.
148
private Position findMatchingStartTag(Line line)
149     {
150         String JavaDoc s = line.trim();
151         if (!s.startsWith("</"))
152             return null;
153         FastStringBuffer sb = new FastStringBuffer();
154         for (int i = 2; i < s.length(); i++) {
155             char c = s.charAt(i);
156             if (c <= ' ')
157                 break;
158             if (c == '>')
159                 break;
160             sb.append(c);
161         }
162         String JavaDoc name = sb.toString();
163         String JavaDoc toBeMatched = "</" + name + ">";
164         String JavaDoc match = "<" + name;
165         int count = 1;
166         boolean foundIt = false;
167         Position pos = new Position(line, 0);
168         final String JavaDoc commentStart = "<!--";
169         final String JavaDoc commentEnd = "-->";
170         // Search backward.
171
while (!pos.atStart()) {
172             pos.prev();
173             if (pos.lookingAt(commentEnd)) {
174                 do {
175                     pos.prev();
176                 } while (!pos.atStart() && !pos.lookingAt(commentStart));
177             } else if (pos.lookingAtIgnoreCase(toBeMatched)) {
178                 ++count;
179             } else if (pos.lookingAtIgnoreCase(match)) {
180                 if (pos.lookingAtIgnoreCase(match + ">"))
181                     --count;
182                 else if (pos.lookingAtIgnoreCase(match + " "))
183                     --count;
184                 else if (pos.lookingAtIgnoreCase(match + "\t"))
185                     --count;
186                 if (count == 0) {
187                     foundIt = true;
188                     break;
189                 }
190             }
191         }
192         if (foundIt)
193             return pos;
194         // Not found.
195
return null;
196     }
197
198     private static final boolean lookingAtIgnoreCase(String JavaDoc s, int i, String JavaDoc pattern)
199     {
200         return s.regionMatches(true, i, pattern, 0, pattern.length());
201     }
202
203     private static Line getModel(Line line)
204     {
205         Line model = line;
206         while ((model = model.previous()) != null) {
207             if (model.flags() == STATE_HTML_COMMENT)
208                 continue;
209             if (model.trim().startsWith("<!--"))
210                 continue;
211             if (model.isBlank())
212                 continue;
213             break;
214         }
215         return model;
216     }
217
218     public char fixCase(Editor editor, char c)
219     {
220         if (!editor.getBuffer().getBooleanProperty(Property.FIX_CASE))
221             return c;
222         if (!initRegExps())
223             return c;
224         Position pos = findStartOfTag(editor.getDot());
225         if (pos != null) {
226             int index = pos.getOffset();
227             String JavaDoc text = pos.getLine().getText();
228             REMatch match = tagNameRE.getMatch(text, index);
229             if (match == null)
230                 return c;
231             if (match.getEndIndex() >= editor.getDotOffset()) {
232                 // Tag name.
233
if (editor.getBuffer().getBooleanProperty(Property.UPPER_CASE_TAG_NAMES))
234                     return Character.toUpperCase(c);
235                 else
236                     return Character.toLowerCase(c);
237             }
238             while (true) {
239                 index = match.getEndIndex();
240                 match = attributeNameRE.getMatch(text, index);
241                 if (match == null)
242                     return c;
243                 if (match.getEndIndex() >= editor.getDotOffset()) {
244                     // Attribute name.
245
if (editor.getBuffer().getBooleanProperty(Property.UPPER_CASE_ATTRIBUTE_NAMES))
246                         return Character.toUpperCase(c);
247                     else
248                         return Character.toLowerCase(c);
249                 }
250                 index = match.getEndIndex();
251                 match = quotedValueRE.getMatch(text, index);
252                 if (match == null) {
253                     match = unquotedValueRE.getMatch(text, index);
254                     if (match == null)
255                         return c;
256                 }
257                 if (match.getEndIndex() >= editor.getDotOffset()) {
258                     // Attribute value.
259
return c;
260                 }
261             }
262         }
263         return c;
264     }
265
266     private static boolean checkElectricEquals(Editor editor)
267     {
268         Position pos = findStartOfTag(editor.getDot());
269         if (pos == null)
270             return false;
271         char c = editor.getDotChar();
272         if (c == '>' || c == '/' || Character.isWhitespace(c))
273             return true;
274         return false;
275     }
276
277     private static Position findStartOfTag(Position pos)
278     {
279         int offset = pos.getOffset();
280         String JavaDoc text = pos.getLine().getText();
281         while (--offset >= 0) {
282             char c = text.charAt(offset);
283             if (c == '>')
284                 return null;
285             if (c == '<')
286                 return new Position(pos.getLine(), offset);
287         }
288         return null;
289     }
290
291     public static List JavaDoc elements()
292     {
293         if (elements == null)
294             loadElementList();
295         return elements;
296     }
297
298     private static boolean wantsEndTag(String JavaDoc elementName)
299     {
300         elementName = elementName.trim().toLowerCase();
301         if (elements == null)
302             loadElementList();
303         if (elements != null) {
304             final int limit = elements.size();
305             for (int i = 0; i < limit; i++) {
306                 HtmlElement element = (HtmlElement) elements.get(i);
307                 if (element.getName().equals(elementName))
308                     return element.wantsEndTag();
309             }
310         }
311         return true; // Default.
312
}
313
314     private static void loadElementList()
315     {
316         elements = HtmlElement.getDefaultElements();
317         String JavaDoc filename = Editor.preferences().getStringProperty(Property.HTML_MODE_TAGS);
318         if (filename != null && filename.length() > 0) {
319             try {
320                 FileInputStream JavaDoc istream = new FileInputStream JavaDoc(filename);
321                 loadElementsFromStream(istream);
322             }
323             catch (FileNotFoundException JavaDoc e) {
324                 Log.error(e);
325             }
326         }
327     }
328
329     private static void loadElementsFromStream(InputStream JavaDoc istream)
330     {
331         Debug.assertTrue(elements != null);
332         if (istream != null) {
333             try {
334                 BufferedReader JavaDoc in =
335                     new BufferedReader JavaDoc(new InputStreamReader JavaDoc(istream));
336                 while (true) {
337                     String JavaDoc s = in.readLine();
338                     if (s == null)
339                         break; // Reached end of file.
340
s = s.trim();
341                     // Ignore blank lines.
342
if (s.trim().length() == 0)
343                         continue;
344                     // Ignore comment lines.
345
if (s.charAt(0) == '#')
346                         continue;
347                     int index = s.indexOf('=');
348                     if (index >= 0) {
349                         // Element names are always stored in lower case.
350
String JavaDoc name = s.substring(0, index).trim().toLowerCase();
351                         String JavaDoc value = s.substring(index + 1).trim();
352                         boolean wantsEndTag = value.equals("1") || value.equals("true");
353                         boolean found = false;
354                         for (int i = 0; i < elements.size(); i++) {
355                             HtmlElement element = (HtmlElement) elements.get(i);
356                             if (element.getName().equals(name)) {
357                                 element.setWantsEndTag(wantsEndTag);
358                                 found = true;
359                                 break;
360                             }
361                         }
362                         if (!found)
363                             elements.add(new HtmlElement(name, wantsEndTag));
364                     }
365                 }
366             }
367             catch (IOException JavaDoc e) {
368                 Log.error(e);
369             }
370         }
371     }
372
373     private static boolean initRegExps()
374     {
375         if (tagNameRE == null) {
376             try {
377                 tagNameRE = new RE("</?[A-Za-z0-9]*");
378                 attributeNameRE = new RE("\\s+[A-Za-z0-9]*");
379                 quotedValueRE = new RE("\\s*=\\s*\"[^\"]*");
380                 unquotedValueRE = new RE("\\s*=\\s*\\S*");
381             }
382             catch (REException e) {
383                 tagNameRE = null;
384                 return false;
385             }
386         }
387         return true;
388     }
389
390     public static void htmlStartTag()
391     {
392         htmlTag(Editor.currentEditor(), false);
393     }
394
395     public static void htmlEndTag()
396     {
397         htmlTag(Editor.currentEditor(), true);
398     }
399
400     private static void htmlTag(Editor editor, boolean isEndTag)
401     {
402         if (!editor.checkReadOnly())
403             return;
404         CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
405         editor.insertChar('<');
406         if (isEndTag)
407             editor.insertChar('/');
408         editor.insertChar('>');
409         editor.addUndo(SimpleEdit.MOVE);
410         editor.getDot().moveLeft();
411         editor.moveCaretToDotCol();
412         editor.endCompoundEdit(compoundEdit);
413     }
414
415     public static void htmlInsertTag()
416     {
417         final Editor editor = Editor.currentEditor();
418         if (!editor.checkReadOnly())
419             return;
420         InsertTagDialog d = new InsertTagDialog(editor);
421         editor.centerDialog(d);
422         d.show();
423         _htmlInsertTag(editor, d.getInput());
424     }
425
426     public static void htmlInsertTag(String JavaDoc input)
427     {
428         final Editor editor = Editor.currentEditor();
429         if (!editor.checkReadOnly())
430             return;
431         _htmlInsertTag(editor, input);
432     }
433
434     private static void _htmlInsertTag(Editor editor, String JavaDoc input)
435     {
436         if (input != null && input.length() > 0) {
437             final String JavaDoc tagName, extra;
438             int index = input.indexOf(' ');
439             if (index >= 0) {
440                 tagName = input.substring(0, index);
441                 extra = input.substring(index);
442             } else {
443                 tagName = input;
444                 extra = "";
445             }
446             InsertTagDialog.insertTag(editor, tagName, extra, wantsEndTag(tagName));
447         }
448     }
449
450     public static void htmlInsertMatchingEndTag()
451     {
452         final Editor editor = Editor.currentEditor();
453         if (!editor.checkReadOnly())
454             return;
455         Position pos = editor.getDotCopy();
456         while (pos.prev()) {
457             // If we find an end tag, we've got nothing to match.
458
if (pos.lookingAt("</"))
459                 return;
460
461             if (pos.getChar() == '<') {
462                 if (pos.next()) {
463                     FastStringBuffer sb = new FastStringBuffer();
464                     char c;
465                     while (!Character.isWhitespace(c = pos.getChar()) && c != '>') {
466                         sb.append(c);
467                         if (!pos.next())
468                             return;
469                     }
470                     if (sb.length() == 0)
471                         return;
472                     final String JavaDoc endTag = "</" + sb.toString() + ">";
473                     final Buffer buffer = editor.getBuffer();
474                     try {
475                         buffer.lockWrite();
476                     }
477                     catch (InterruptedException JavaDoc e) {
478                         Log.error(e);
479                         return;
480                     }
481                     try {
482                         CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
483                         editor.fillToCaret();
484                         editor.addUndo(SimpleEdit.INSERT_STRING);
485                         editor.insertStringInternal(endTag);
486                         buffer.modified();
487                         editor.addUndo(SimpleEdit.MOVE);
488                         editor.moveCaretToDotCol();
489                         if (buffer.getBooleanProperty(Property.AUTO_INDENT))
490                             editor.indentLine();
491                         editor.endCompoundEdit(compoundEdit);
492                     }
493                     finally {
494                         buffer.unlockWrite();
495                     }
496                 }
497                 return;
498             }
499         }
500     }
501
502     public static void htmlBold()
503     {
504         final Editor editor = Editor.currentEditor();
505         final Buffer buffer = editor.getBuffer();
506         if (!editor.checkReadOnly())
507             return;
508         CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
509         if (editor.getMark() == null)
510             editor.fillToCaret();
511         boolean upper = buffer.getBooleanProperty(Property.UPPER_CASE_TAG_NAMES);
512         InsertTagDialog.insertTag(editor, upper ? "B" : "b", "", true);
513         editor.endCompoundEdit(compoundEdit);
514     }
515
516     public static void htmlElectricEquals()
517     {
518         final Editor editor = Editor.currentEditor();
519         if (!editor.checkReadOnly())
520             return;
521         boolean ok = false;
522         if (editor.getModeId() == HTML_MODE) {
523             if (editor.getBuffer().getBooleanProperty(Property.ATTRIBUTES_REQUIRE_QUOTES))
524                 if (checkElectricEquals(editor))
525                     ok = true;
526         }
527         if (ok) {
528             CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
529             editor.fillToCaret();
530             editor.addUndo(SimpleEdit.INSERT_STRING);
531             editor.insertStringInternal("=\"\"");
532             editor.addUndo(SimpleEdit.MOVE);
533             editor.getDot().moveLeft();
534             editor.moveCaretToDotCol();
535             editor.endCompoundEdit(compoundEdit);
536         } else
537             editor.insertNormalChar('=');
538     }
539
540     public static void htmlFindMatch()
541     {
542         final Editor editor = Editor.currentEditor();
543         final String JavaDoc special = "{([})]";
544         final String JavaDoc commentStart = "<!--";
545         final String JavaDoc commentEnd = "-->";
546         Position dot = editor.getDot();
547         char c = dot.getChar();
548         if (special.indexOf(c) >= 0) {
549             editor.findMatchingChar();
550             return;
551         }
552         Position saved = dot.copy();
553         while ((c = dot.getChar()) > ' ' && c != '<' && dot.getOffset() > 0) {
554             if (dot.lookingAt(commentEnd))
555                 break;
556             dot.prev();
557         }
558         if (c <= ' ')
559             dot.next();
560         Position start = dot.copy();
561         FastStringBuffer sb = new FastStringBuffer(dot.getChar());
562         dot.next();
563         while ((c = dot.getChar()) > ' ' && c != '>') {
564             sb.append(c);
565             if (!dot.next()) {
566                 editor.status("Nothing to match");
567                 dot.moveTo(saved);
568                 return;
569             }
570         }
571         if (c == '>')
572             sb.append(c);
573         String JavaDoc toBeMatched = sb.toString();
574         String JavaDoc match = null;
575         boolean searchForward = true;
576         if (toBeMatched.equals(commentStart))
577             match = commentEnd;
578         else if (toBeMatched.equals(commentEnd)) {
579             match = commentStart;
580             searchForward = false;
581         } else if (toBeMatched.startsWith("</")) {
582             match = "<".concat(toBeMatched.substring(2));
583             if (match.endsWith(">"))
584                 match = match.substring(0, match.length()-1);
585             searchForward = false;
586         } else if (toBeMatched.startsWith("<")) {
587             if (toBeMatched.endsWith(">"))
588                 toBeMatched = toBeMatched.substring(0, toBeMatched.length()-1);
589             match = "</" + toBeMatched.substring(1);
590             if (!match.endsWith(">"))
591                 match += '>';
592         } else {
593             editor.status("Nothing to match");
594             dot.moveTo(saved);
595             return;
596         }
597         editor.setWaitCursor();
598         int count = 1;
599         boolean succeeded = false;
600         dot.moveTo(start);
601         if (searchForward) {
602             dot.skip(toBeMatched.length());
603             if (toBeMatched.equals(commentStart)) {
604                 while (!dot.atEnd()) {
605                     if (dot.lookingAt(commentEnd)) {
606                         succeeded = true;
607                         break;
608                     }
609                     dot.next();
610                 }
611             } else {
612                 // Find matching end tag.
613
while (!dot.atEnd()) {
614                     if (dot.lookingAt(commentStart)) {
615                         dot.skip(commentStart.length());
616                         while (!dot.atEnd()) {
617                             if (dot.lookingAt(commentEnd)) {
618                                 dot.skip(commentEnd.length());
619                                 break;
620                             }
621                             dot.next();
622                         }
623                     } else if (dot.lookingAtIgnoreCase(toBeMatched)) {
624                         dot.skip(toBeMatched.length());
625                         c = dot.getChar();
626                         if (c <= ' ' || c == '>') {
627                             ++count;
628                             dot.next();
629                         }
630                     } else if (dot.lookingAtIgnoreCase(match)) {
631                         --count;
632                         if (count == 0) {
633                             succeeded = true;
634                             break;
635                         }
636                         dot.skip(match.length());
637                     } else
638                         dot.next();
639                 }
640             }
641         } else {
642             // Search backward.
643
while (!dot.atStart()) {
644                 dot.prev();
645                 if (dot.lookingAt(commentEnd)) {
646                     do {
647                         dot.prev();
648                     }
649                     while (!dot.atStart() && !dot.lookingAt(commentStart));
650                 } else if (dot.lookingAtIgnoreCase(toBeMatched)) {
651                     ++count;
652                 } else if (dot.lookingAtIgnoreCase(match)) {
653                     if (dot.lookingAtIgnoreCase(match + ">"))
654                         --count;
655                     else if (dot.lookingAtIgnoreCase(match + " "))
656                         --count;
657                     else if (dot.lookingAtIgnoreCase(match + "\t"))
658                         --count;
659                     if (count == 0) {
660                         succeeded = true;
661                         break;
662                     }
663                 }
664             }
665         }
666         if (succeeded) {
667             Position matchPos = dot.copy();
668             dot.moveTo(saved);
669             editor.updateDotLine();
670             editor.addUndo(SimpleEdit.MOVE);
671             dot.moveTo(matchPos);
672             editor.updateDotLine();
673             editor.moveCaretToDotCol();
674         } else {
675             dot.moveTo(saved);
676             editor.status("No match");
677         }
678         editor.setDefaultCursor();
679     }
680 }
681
Popular Tags