KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * XmlMode.java
3  *
4  * Copyright (C) 1998-2004 Peter Graves
5  * $Id: XmlMode.java,v 1.17 2004/04/22 17:49:38 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 gnu.regexp.UncheckedRE;
28 import java.awt.event.KeyEvent JavaDoc;
29 import java.io.BufferedReader JavaDoc;
30 import java.io.IOException JavaDoc;
31 import java.io.InputStreamReader JavaDoc;
32 import java.io.StringReader JavaDoc;
33 import javax.swing.tree.DefaultMutableTreeNode JavaDoc;
34 import javax.swing.tree.TreeModel JavaDoc;
35 import javax.swing.tree.TreeNode JavaDoc;
36 import javax.swing.tree.TreePath JavaDoc;
37 import javax.swing.undo.CompoundEdit JavaDoc;
38 import org.xml.sax.SAXParseException JavaDoc;
39
40 public final class XmlMode extends AbstractMode implements Constants, Mode
41 {
42     private static final String JavaDoc COMMENT_START = "<!--";
43     private static final String JavaDoc COMMENT_END = "-->";
44     private static final String JavaDoc CDATA_START = "<![CDATA[";
45     private static final String JavaDoc CDATA_END = "]]>";
46
47     private static final XmlMode mode = new XmlMode();
48
49     private static XmlErrorBuffer errorBuffer;
50
51     private static RE tagNameRE;
52     private static RE attributeNameRE;
53     private static RE quotedValueRE;
54     private static RE unquotedValueRE;
55
56     private XmlMode()
57     {
58         super(XML_MODE, XML_MODE_NAME);
59         setProperty(Property.INDENT_SIZE, 2);
60     }
61
62     public static final XmlMode getMode()
63     {
64         return mode;
65     }
66
67     public static final XmlErrorBuffer getErrorBuffer()
68     {
69         return errorBuffer;
70     }
71
72     public NavigationComponent getSidebarComponent(Editor editor)
73     {
74         Debug.assertTrue(editor.getBuffer().getMode() == getMode());
75         if (!editor.getBuffer().getBooleanProperty(Property.ENABLE_TREE))
76             return null;
77         View view = editor.getCurrentView();
78         if (view == null)
79             return null; // Shouldn't happen.
80
if (view.getSidebarComponent() == null)
81             view.setSidebarComponent(new XmlTree(editor, null));
82         return view.getSidebarComponent();
83     }
84
85     public String JavaDoc getCommentStart()
86     {
87         return COMMENT_START;
88     }
89
90     public String JavaDoc getCommentEnd()
91     {
92         return COMMENT_END;
93     }
94
95     public Formatter getFormatter(Buffer buffer)
96     {
97         return new XmlFormatter(buffer);
98     }
99
100     protected void setKeyMapDefaults(KeyMap km)
101     {
102         km.mapKey(KeyEvent.VK_TAB, 0, "tab");
103         km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab");
104         km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
105         km.mapKey(KeyEvent.VK_ENTER, CTRL_MASK, "newline");
106         km.mapKey(KeyEvent.VK_M, CTRL_MASK, "xmlFindMatch");
107         km.mapKey('=', "xmlElectricEquals");
108         km.mapKey('>', "electricCloseAngleBracket");
109         km.mapKey(KeyEvent.VK_E, CTRL_MASK, "xmlInsertMatchingEndTag");
110         km.mapKey('/', "xmlElectricSlash");
111         km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
112         km.mapKey(KeyEvent.VK_COMMA, CTRL_MASK | SHIFT_MASK, "xmlInsertTag");
113         km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | SHIFT_MASK,
114                   "xmlInsertEmptyElementTag");
115         km.mapKey(KeyEvent.VK_P, CTRL_MASK, "xmlParseBuffer");
116         km.mapKey(KeyEvent.VK_P, CTRL_MASK | SHIFT_MASK, "xmlValidateBuffer");
117         km.mapKey(KeyEvent.VK_EQUALS, CTRL_MASK, "xmlFindCurrentNode");
118         km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK, "fold");
119         km.mapKey(KeyEvent.VK_CLOSE_BRACKET, CTRL_MASK, "unfold");
120
121         // build.xml
122
km.mapKey(KeyEvent.VK_F9, 0, "compile");
123         km.mapKey(KeyEvent.VK_F9, CTRL_MASK, "recompile");
124     }
125
126     public void populateModeMenu(Editor editor, Menu menu)
127     {
128         menu.add(editor, "Insert Element", 'I', "xmlInsertTag");
129         menu.add(editor, "End Current Element", 'E', "xmlInsertMatchingEndTag");
130         menu.addSeparator();
131         menu.add(editor, "Parse Buffer", 'P', "xmlParseBuffer");
132         menu.add(editor, "Validate Buffer", 'V', "xmlValidateBuffer");
133         boolean enabled = errorBuffer != null;
134         menu.addSeparator();
135         menu.add(editor, "Next Error", 'N', "nextError", enabled);
136         menu.add(editor, "Previous Error", 'R', "previousError", enabled);
137         menu.add(editor, "Show Error Message", 'M', "showMessage", enabled);
138     }
139
140     public void loadFile(Buffer buffer, File file)
141     {
142         String JavaDoc encoding = null;
143         try {
144             BufferedReader JavaDoc reader =
145                 new BufferedReader JavaDoc(new InputStreamReader JavaDoc(file.getInputStream()));
146             String JavaDoc s = reader.readLine();
147             reader.close();
148             if (s != null && s.toLowerCase().startsWith("<?xml")) {
149                 int end = s.indexOf("?>");
150                 if (end >= 0) {
151                     s = s.substring(5, end);
152                     RE re = new UncheckedRE("encoding[ \t]*=[ \t]*");
153                     REMatch match = re.getMatch(s);
154                     if (match != null) {
155                         // First char after match will be single or double
156
// quote.
157
s = s.substring(match.getEndIndex());
158                         if (s.length() > 0) {
159                             char quoteChar = s.charAt(0);
160                             // Find matching quote char.
161
end = s.indexOf(quoteChar, 1);
162                             if (end >= 0) {
163                                 encoding = s.substring(1, end);
164                                 if (Utilities.isSupportedEncoding(encoding))
165                                     file.setEncoding(encoding);
166                                 else
167                                     Log.error("unsupported encoding \"" +
168                                         encoding + '"');
169                             }
170                         }
171                     }
172                 }
173             }
174         }
175         catch (IOException JavaDoc e) {
176             Log.error(e);
177         }
178         if (encoding == null)
179             encoding = "UTF8"; // Default for XML.
180
try {
181             buffer.load(file.getInputStream(), file.getEncoding());
182         }
183         catch (IOException JavaDoc e) {
184             Log.error(e);
185         }
186     }
187
188     public boolean canIndent()
189     {
190         return true;
191     }
192
193     public int getCorrectIndentation(Line line, Buffer buffer)
194     {
195         final Line model = getModel(line);
196         if (model == null)
197             return 0;
198         int indent = buffer.getIndentation(model);
199         if (line.flags() == STATE_QUOTE && model.flags() != STATE_QUOTE)
200             return indent + buffer.getIndentSize();
201         final String JavaDoc text = line.trim();
202         if (text.equals("/>")) {
203             Position pos = new Position(line, line.length());
204             while (pos.prev()) {
205                 if (pos.getChar() == '<')
206                     break;
207             }
208             return buffer.getIndentation(pos.getLine());
209         }
210         if (text.startsWith("</")) {
211             Position pos = findMatchingStartTag(line);
212             if (pos != null)
213                 return buffer.getIndentation(pos.getLine());
214             indent -= buffer.getIndentSize();
215             return indent < 0 ? 0 : indent;
216         }
217         final String JavaDoc modelText = model.trim();
218         if (modelText.startsWith("<") && !modelText.startsWith("</") &&
219             !modelText.startsWith("<!"))
220         {
221             String JavaDoc tag = getTag(modelText);
222             if (isEmptyElementTag(tag))
223                 return indent;
224             if (isProcessingInstruction(tag))
225                 return indent;
226             // Model starts with start tag.
227
String JavaDoc tagName = Utilities.getTagName(modelText);
228             String JavaDoc startTag = "<" + tagName;
229             String JavaDoc endTag = "</" + tagName;
230             int count = 1;
231             final int limit = modelText.length();
232             for (int i = startTag.length(); i < limit; i++) {
233                 if (modelText.charAt(i) == '<') {
234                     if (lookingAt(modelText, i, endTag)) {
235                         int end = i + endTag.length();
236                         if (end < limit) {
237                             char c = modelText.charAt(end);
238                             if (c == ' ' || c == '\t' || c == '>')
239                                 --count;
240                         }
241                     } else if (lookingAt(modelText, i, startTag)) {
242                         int end = i + startTag.length();
243                         if (end < limit) {
244                             char c = modelText.charAt(end);
245                             if (c == ' ' || c == '\t' || c == '>')
246                                 ++count;
247                         }
248                     }
249                 }
250             }
251             if (count > 0)
252                 indent += buffer.getIndentSize();
253         } else if (modelText.endsWith("/>")) {
254             Position pos = new Position(model, model.length());
255             while (pos.prev()) {
256                 if (pos.getChar() == '<')
257                     break;
258             }
259             indent = buffer.getIndentation(pos.getLine());
260         }
261         return indent;
262     }
263
264     // Line must start with an end tag.
265
private Position findMatchingStartTag(Line line)
266     {
267         String JavaDoc s = line.trim();
268         if (!s.startsWith("</"))
269             return null;
270         FastStringBuffer sb = new FastStringBuffer();
271         for (int i = 2; i < s.length(); i++) {
272             char c = s.charAt(i);
273             if (c <= ' ')
274                 break;
275             if (c == '>')
276                 break;
277             sb.append(c);
278         }
279         Position pos = new Position(line, 0);
280         String JavaDoc name = sb.toString();
281         return findMatchingStartTag(name, pos);
282     }
283
284     private static Position findMatchingStartTag(String JavaDoc name, Position start)
285     {
286         Position pos = start.copy();
287         String JavaDoc endTagToBeMatched = "</" + name + ">";
288         String JavaDoc lookFor = "<" + name;
289         int count = 1;
290         boolean succeeded = false;
291         if (pos.lookingAt(endTagToBeMatched))
292             pos.prev();
293         // Search backward.
294
while (!pos.atStart()) {
295             if (pos.lookingAt(COMMENT_END)) {
296                 do {
297                     pos.prev();
298                 } while (!pos.atStart() && !pos.lookingAt(COMMENT_START));
299             } else if (pos.lookingAt(CDATA_END)) {
300                 do {
301                     pos.prev();
302                 } while (!pos.atStart() && !pos.lookingAt(CDATA_START));
303             } else if (pos.lookingAt(endTagToBeMatched)) {
304                 ++count;
305             } else if (pos.lookingAt(lookFor)) {
306                 // getTag() skips past the tag, so use pos.copy() here since
307
// we're moving backwards not forwards.
308
String JavaDoc tag = getTag(pos.copy());
309                 if (Utilities.getTagName(tag).equals(name)) {
310                     if (!tag.endsWith("/>")) {
311                         --count;
312                         if (count == 0) {
313                             succeeded = true;
314                             break;
315                         }
316                     }
317                 }
318             }
319             pos.prev();
320         }
321         if (succeeded)
322             return pos;
323         // Not found.
324
return null;
325     }
326
327     private static Position findMatchingEndTag(String JavaDoc name, Position start)
328     {
329         Position pos = start.copy();
330         String JavaDoc startTagToBeMatched = "<" + name;
331         String JavaDoc lookFor = "</" + name + ">";
332         int count = 1;
333         if (pos.lookingAt(startTagToBeMatched))
334             pos.skip(startTagToBeMatched.length());
335         // Search forward.
336
while (!pos.atEnd()) {
337             if (pos.lookingAt(COMMENT_START)) {
338                 do {
339                     pos.next();
340                 } while (!pos.atEnd() && !pos.lookingAt(COMMENT_END));
341                 if (pos.atEnd()) {
342                     break;
343                 } else {
344                     pos.skip(COMMENT_END.length());
345                     continue;
346                 }
347             }
348             if (pos.lookingAt(CDATA_START)) {
349                 do {
350                     pos.next();
351                 } while (!pos.atEnd() && !pos.lookingAt(CDATA_END));
352                 if (pos.atEnd()) {
353                     break;
354                 } else {
355                     pos.skip(CDATA_END.length());
356                     continue;
357                 }
358             }
359             if (pos.lookingAt(startTagToBeMatched)) {
360                 String JavaDoc tag = getTag(pos); // Skips past tag.
361
if (Utilities.getTagName(tag).equals(name)) {
362                     if (!tag.endsWith("/>"))
363                         ++count;
364                 }
365                 continue;
366             }
367             if (pos.lookingAt(lookFor)) {
368                 --count;
369                 if (count == 0) {
370                     return pos;
371                 } else {
372                     pos.skip(lookFor.length());
373                     continue;
374                 }
375             }
376             // None of the above...
377
pos.next();
378         }
379         // Not found.
380
return null;
381     }
382
383     private static String JavaDoc getUnmatchedStartTag(Position start)
384     {
385         Position pos = start.copy();
386         if (isInComment(pos))
387             return null;
388         if (isInTag(pos))
389             return null;
390         if (isInCDataSection(pos))
391             return null;
392         int count = 1;
393         if (!pos.lookingAt("<") || pos.prev()) {
394             do {
395                 if (pos.lookingAt(COMMENT_END)) {
396                     do {
397                         pos.prev();
398                     } while (!pos.atStart() && !pos.lookingAt(COMMENT_START));
399                 } else if (pos.lookingAt(CDATA_END)) {
400                     do {
401                         pos.prev();
402                     } while (!pos.atStart() && !pos.lookingAt(CDATA_START));
403                 } else if (pos.getChar() == '<') {
404                     if (pos.lookingAt("</"))
405                         ++count;
406                     else if (pos.lookingAt("<?"))
407                         ;
408                     else {
409                         // getTag() skips past the tag, so use pos.copy() here
410
// since we're moving backwards not forwards.
411
String JavaDoc tag = getTag(pos.copy());
412                         if (!tag.endsWith("/>")) {
413                             --count;
414                             if (count == 0)
415                                 return tag;
416                         }
417                     }
418                 }
419             } while (pos.prev());
420         }
421         // Not found.
422
return null;
423     }
424
425     private static boolean isInTag(Position position)
426     {
427         Position pos = position.copy();
428         while (pos.prev()) {
429             if (pos.lookingAt(COMMENT_END)) {
430                 do {
431                     pos.prev();
432                 } while (!pos.atStart() && !pos.lookingAt(COMMENT_START));
433                 continue;
434             }
435             char c = pos.getChar();
436             if (c == '<')
437                 return true;
438             else if (c == '>')
439                 return false;
440         }
441         return false;
442     }
443
444     private static boolean isInComment(Position position)
445     {
446         Position pos = position.copy();
447         boolean inComment = pos.getLine().flags() == STATE_COMMENT;
448         pos.setOffset(0);
449         final int limit = position.getOffset();
450         while (pos.getOffset() < limit) {
451             if (inComment) {
452                 if (pos.lookingAt(COMMENT_END)) {
453                     pos.skip(COMMENT_END.length());
454                     if (pos.getOffset() > limit)
455                         break;
456                     inComment = false;
457                     continue;
458                 }
459             } else if (pos.lookingAt(COMMENT_START)) {
460                 inComment = true;
461                 pos.skip(COMMENT_START.length());
462                 continue;
463             }
464             pos.next();
465         }
466         return inComment;
467     }
468
469     private static boolean isInCDataSection(Position position)
470     {
471         Position pos = position.copy();
472         boolean inCDataSection = pos.getLine().flags() == STATE_CDATA;
473         pos.setOffset(0);
474         final int limit = position.getOffset();
475         while (pos.getOffset() < limit) {
476             if (inCDataSection) {
477                 if (pos.lookingAt(CDATA_END)) {
478                     pos.skip(CDATA_END.length());
479                     if (pos.getOffset() > limit)
480                         break;
481                     inCDataSection = false;
482                     continue;
483                 }
484             } else if (pos.lookingAt(CDATA_START)) {
485                 inCDataSection = true;
486                 pos.skip(CDATA_START.length());
487                 continue;
488             }
489             pos.next();
490         }
491         return inCDataSection;
492     }
493
494     private static String JavaDoc getTag(String JavaDoc s)
495     {
496         if (s == null || s.length() == 0 || s.charAt(0) != '<')
497             return null;
498         FastStringBuffer sb = new FastStringBuffer();
499         final int limit = s.length();
500         char quoteChar = 0;
501         for (int i = 0; i < limit; i++) {
502             char c = s.charAt(i);
503             sb.append(c);
504             if (quoteChar != 0) {
505                 // We're in a quoted section.
506
if (c == quoteChar)
507                     quoteChar = 0;
508             } else {
509                 // We're not in a quoted section.
510
if (c == '\'' || c == '"')
511                     quoteChar = c;
512                 else if (c == '>')
513                     break;
514             }
515         }
516         return sb.toString();
517     }
518
519     // Advances position to first char past end of tag.
520
private static String JavaDoc getTag(Position pos)
521     {
522         if (pos == null || pos.getChar() != '<')
523             return null;
524         FastStringBuffer sb = new FastStringBuffer('<');
525         char quoteChar = 0;
526         while (pos.next()) {
527             char c = pos.getChar();
528             sb.append(c);
529             if (quoteChar != 0) {
530                 // We're in a quoted section.
531
if (c == quoteChar)
532                     quoteChar = 0;
533             } else {
534                 // We're not in a quoted section.
535
if (c == '\'' || c == '"')
536                     quoteChar = c;
537                 else if (c == '>') {
538                     pos.next();
539                     break;
540                 }
541             }
542         }
543         return sb.toString();
544     }
545
546     private static boolean isProcessingInstruction(String JavaDoc tag)
547     {
548         if (tag.startsWith("<?") && tag.endsWith("?>"))
549             return true;
550         return false;
551     }
552
553     private static boolean isEmptyElementTag(String JavaDoc tag)
554     {
555         if (tag == null)
556             return false;
557         return tag.endsWith("/>");
558     }
559
560     private static final boolean lookingAt(String JavaDoc s, int i, String JavaDoc pattern)
561     {
562         return s.regionMatches(i, pattern, 0, pattern.length());
563     }
564
565     private static Line getModel(Line line)
566     {
567         Line model = line;
568         while ((model = model.previous()) != null) {
569             int flags = model.flags();
570             if (flags == STATE_COMMENT || flags == STATE_QUOTE)
571                 continue;
572             else if (model.trim().startsWith(COMMENT_START))
573                 continue;
574             else if (model.isBlank())
575                 continue;
576             else
577                 break;
578         }
579         return model;
580     }
581
582     public char fixCase(Editor editor, char c)
583     {
584         if (!Character.isUpperCase(c) && !Character.isLowerCase(c))
585             return c;
586         final Buffer buffer = editor.getBuffer();
587         if (!buffer.getBooleanProperty(Property.FIX_CASE))
588             return c;
589         if (!initRegExps())
590             return c;
591         Position pos = findStartOfTag(editor.getDot());
592         if (pos != null) {
593             int index = pos.getOffset();
594             String JavaDoc text = pos.getLine().getText();
595             REMatch match = tagNameRE.getMatch(text, index);
596             if (match == null)
597                 return c;
598             if (match.getEndIndex() >= editor.getDotOffset()) {
599                 // Tag name.
600
if (buffer.getBooleanProperty(Property.UPPER_CASE_TAG_NAMES))
601                     return Character.toUpperCase(c);
602                 else
603                     return Character.toLowerCase(c);
604             }
605             while (true) {
606                 index = match.getEndIndex();
607                 match = attributeNameRE.getMatch(text, index);
608                 if (match == null)
609                     return c;
610                 if (match.getEndIndex() >= editor.getDotOffset()) {
611                     // Attribute name.
612
if (buffer.getBooleanProperty(Property.UPPER_CASE_ATTRIBUTE_NAMES))
613                         return Character.toUpperCase(c);
614                     else
615                         return Character.toLowerCase(c);
616                 }
617                 index = match.getEndIndex();
618                 match = quotedValueRE.getMatch(text, index);
619                 if (match == null) {
620                     match = unquotedValueRE.getMatch(text, index);
621                     if (match == null)
622                         return c;
623                 }
624                 if (match.getEndIndex() >= editor.getDotOffset()) {
625                     // Attribute value.
626
return c;
627                 }
628             }
629         }
630         return c;
631     }
632
633     private static boolean checkElectricEquals(Editor editor)
634     {
635         Position pos = findStartOfTag(editor.getDot());
636         if (pos == null)
637             return false;
638         char c = editor.getDotChar();
639         if (c == '>' || c == '/' || Character.isWhitespace(c))
640             return true;
641         return false;
642     }
643
644     // Scans backward on same line for '<'.
645
private static Position findStartOfTag(Position pos)
646     {
647         final String JavaDoc text = pos.getLine().getText();
648         int offset = pos.getOffset();
649         if (offset >= pos.getLine().length())
650             offset = pos.getLine().length()-1;
651         else if (text.charAt(offset) == '>')
652             --offset;
653         while (offset >= 0) {
654             char c = text.charAt(offset);
655             if (c == '<')
656                 return new Position(pos.getLine(), offset);
657             if (c == '>')
658                 return null;
659             --offset;
660         }
661         return null;
662     }
663
664     private static boolean initRegExps()
665     {
666         if (tagNameRE == null) {
667             try {
668                 tagNameRE = new RE("</?[A-Za-z0-9]*");
669                 attributeNameRE = new RE("\\s+[A-Za-z0-9]*");
670                 quotedValueRE = new RE("\\s*=\\s*\"[^\"]*");
671                 unquotedValueRE = new RE("\\s*=\\s*\\S*");
672             }
673             catch (REException e) {
674                 tagNameRE = null;
675                 return false;
676             }
677         }
678         return true;
679     }
680
681     public static void xmlFindCurrentNode()
682     {
683         final Editor editor = Editor.currentEditor();
684         if (editor.getModeId() == XML_MODE) {
685             final Sidebar sidebar = editor.getSidebar();
686             if (sidebar != null) {
687                 XmlTree tree = (XmlTree) sidebar.getBottomComponent();
688                 if (tree != null)
689                     ensureCurrentNodeIsVisible(editor, tree);
690             }
691         }
692     }
693
694     public static void xmlParseBuffer()
695     {
696         final Editor editor = Editor.currentEditor();
697         if (editor.getModeId() == XML_MODE) {
698             XmlTree tree = null;
699             final Sidebar sidebar = editor.getSidebar();
700             if (sidebar != null) {
701                 tree = (XmlTree) sidebar.getBottomComponent();
702
703                 // If there's no tree...
704
if (tree == null) {
705                     sidebar.setUpdateFlag(SIDEBAR_NAVIGATION_COMPONENT_ALL);
706                     sidebar.refreshSidebar();
707                     tree = (XmlTree) sidebar.getBottomComponent();
708                     if (tree != null)
709                         ensureCurrentNodeIsVisible(editor, tree);
710                     return;
711                 }
712             }
713             final Buffer buffer = editor.getBuffer();
714             XmlParserImpl parser = new XmlParserImpl(buffer);
715             if (parser.initialize()) {
716                 editor.setWaitCursor();
717                 try {
718                     parser.setReader(new StringReader JavaDoc(buffer.getText()));
719                     parser.run();
720                 }
721                 catch (OutOfMemoryError JavaDoc e) {
722                     outOfMemory();
723                     return;
724                 }
725                 finally {
726                     editor.setDefaultCursor();
727                 }
728                 String JavaDoc output = parser.getOutput();
729                 // Note that with the current implementation, there will always
730
// be output...
731
if (output != null && output.length() > 0) {
732                     if (errorBuffer == null) {
733                         errorBuffer =
734                             new XmlErrorBuffer(buffer.getFile(), output);
735                     } else
736                         errorBuffer.recycle(buffer.getFile(), output);
737                     Editor otherEditor = editor.getOtherEditor();
738                     if (otherEditor != null) {
739                         errorBuffer.setUnsplitOnClose(
740                             otherEditor.getBuffer().unsplitOnClose());
741                         otherEditor.makeNext(errorBuffer);
742                     } else
743                         errorBuffer.setUnsplitOnClose(true);
744                     editor.displayInOtherWindow(errorBuffer);
745                 }
746                 if (parser.getException() == null) {
747                     if (tree != null) {
748                         TreeModel JavaDoc treeModel = parser.getTreeModel();
749                         if (treeModel != null) {
750                             tree.setParserClassName(parser.getParserClassName());
751                             tree.setModel(treeModel);
752                             Debug.assertTrue(tree.getModel() != null);
753                             Debug.assertTrue(tree.getModel().getRoot() != null);
754                             ensureCurrentNodeIsVisible(editor, tree);
755                         }
756                     }
757                     editor.status("No errors");
758                 }
759             }
760         }
761     }
762
763     public static void xmlValidateBuffer()
764     {
765         final Editor editor = Editor.currentEditor();
766         if (editor.getModeId() == XML_MODE) {
767             final Buffer buffer = editor.getBuffer();
768             XmlParserImpl parser = new XmlParserImpl(buffer);
769             if (parser.initialize()) {
770                 try {
771                     editor.setWaitCursor();
772                     parser.enableValidation(true);
773                     parser.setReader(new StringReader JavaDoc(buffer.getText()));
774                     parser.run();
775                 }
776                 catch (OutOfMemoryError JavaDoc e) {
777                     outOfMemory();
778                     return;
779                 }
780                 finally {
781                     editor.setDefaultCursor();
782                 }
783                 String JavaDoc output = parser.getOutput();
784                 if (output != null && output.length() > 0) {
785                     if (errorBuffer == null) {
786                         errorBuffer =
787                             new XmlErrorBuffer(buffer.getFile(), output);
788                     } else
789                         errorBuffer.recycle(buffer.getFile(), output);
790                     Editor otherEditor = editor.getOtherEditor();
791                     if (otherEditor != null) {
792                         errorBuffer.setUnsplitOnClose(
793                             otherEditor.getBuffer().unsplitOnClose());
794                         otherEditor.makeNext(errorBuffer);
795                     } else
796                         errorBuffer.setUnsplitOnClose(true);
797                     editor.displayInOtherWindow(errorBuffer);
798                 } else
799                     editor.status("No errors");
800             }
801         }
802     }
803
804     private static void outOfMemory()
805     {
806         MessageDialog.showMessageDialog(
807             "Not enough memory to run parser",
808             "XML Mode");
809     }
810
811     public static void xmlFindError(Editor editor, SAXParseException JavaDoc e)
812     {
813         Line line = editor.getBuffer().getLine(e.getLineNumber()-1);
814         if (line != null) {
815             int offset = e.getColumnNumber()-1;
816             if (offset < 0)
817                 offset = 0;
818             if (offset > line.length())
819                 offset = line.length();
820             if (line != editor.getDotLine() || offset != editor.getDotOffset()) {
821                 editor.addUndo(SimpleEdit.MOVE);
822                 editor.updateDotLine();
823                 editor.setDot(line, offset);
824                 editor.updateDotLine();
825                 editor.moveCaretToDotCol();
826                 editor.updateDisplay();
827             }
828         }
829     }
830
831     public static void ensureCurrentNodeIsVisible(Editor editor, XmlTree tree)
832     {
833         if (tree == null)
834             return;
835         DefaultMutableTreeNode JavaDoc currentNode = tree.getNodeAtPos(editor.getDot());
836         if (currentNode != null) {
837             tree.scrollPathToVisible(new TreePath JavaDoc(currentNode.getPath()));
838             tree.updatePosition();
839         }
840     }
841
842     public static void copyXPath()
843     {
844         final Editor editor = Editor.currentEditor();
845         if (editor.getModeId() == XML_MODE) {
846             final Sidebar sidebar = editor.getSidebar();
847             if (sidebar != null) {
848                 XmlTree tree = (XmlTree) sidebar.getBottomComponent();
849                 if (tree != null) {
850                     DefaultMutableTreeNode JavaDoc currentNode =
851                         tree.getNodeAtPos(editor.getDot());
852                     if (currentNode != null) {
853                         TreeNode JavaDoc[] array = currentNode.getPath();
854                         if (array != null) {
855                             FastStringBuffer sb = new FastStringBuffer();
856                             for (int i = 0; i < array.length; i++) {
857                                 DefaultMutableTreeNode JavaDoc node =
858                                     (DefaultMutableTreeNode JavaDoc) array[i];
859                                 XmlTreeElement element =
860                                     (XmlTreeElement) node.getUserObject();
861                                 sb.append('/');
862                                 sb.append(element.getName());
863                             }
864                             if (sb.length() > 0) {
865                                 KillRing killRing = editor.getKillRing();
866                                 killRing.appendNew(sb.toString());
867                                 killRing.copyLastKillToSystemClipboard();
868                                 editor.status("XPath copied to clipboard");
869                             }
870                         }
871                     }
872                 }
873             }
874         }
875     }
876
877     public static void xmlElectricEquals()
878     {
879         final Editor editor = Editor.currentEditor();
880         if (!editor.checkReadOnly())
881             return;
882         boolean ok = false;
883         if (editor.getModeId() == XML_MODE) {
884             // Attributes always require quotes in XML mode.
885
if (checkElectricEquals(editor))
886                 ok = true;
887         }
888         if (ok) {
889             CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
890             editor.fillToCaret();
891             editor.addUndo(SimpleEdit.INSERT_STRING);
892             editor.insertStringInternal("=\"\"");
893             editor.addUndo(SimpleEdit.MOVE);
894             editor.getDot().moveLeft();
895             editor.moveCaretToDotCol();
896             editor.endCompoundEdit(compoundEdit);
897         } else
898             editor.insertNormalChar('=');
899     }
900
901     public static void xmlInsertTag()
902     {
903         final Editor editor = Editor.currentEditor();
904         if (!editor.checkReadOnly())
905             return;
906         InsertTagDialog d = new InsertTagDialog(editor);
907         editor.centerDialog(d);
908         d.show();
909         _xmlInsertTag(editor, d.getInput());
910     }
911
912     public static void xmlInsertTag(String JavaDoc input)
913     {
914         final Editor editor = Editor.currentEditor();
915         if (!editor.checkReadOnly())
916             return;
917         _xmlInsertTag(editor, input);
918     }
919
920     private static void _xmlInsertTag(Editor editor, String JavaDoc input)
921     {
922         if (input != null) {
923             final String JavaDoc tagName, extra;
924             int index = input.indexOf(' ');
925             if (index >= 0) {
926                 tagName = input.substring(0, index);
927                 extra = input.substring(index);
928             } else {
929                 tagName = input;
930                 extra = "";
931             }
932             // We always want the end tag in XML mode.
933
InsertTagDialog.insertTag(editor, tagName, extra, true);
934         }
935     }
936
937     public static void xmlInsertEmptyElementTag()
938     {
939         final Editor editor = Editor.currentEditor();
940         if (!editor.checkReadOnly())
941             return;
942         InputDialog d =
943             new InputDialog(editor, "Tag:", "Insert Empty Element Tag", null);
944         d.setHistory(new History("xmlInsertEmptyElementTag"));
945         editor.centerDialog(d);
946         d.show();
947         String JavaDoc input = d.getInput();
948         if (input == null)
949             return;
950         String JavaDoc tagName;
951         String JavaDoc extra;
952         int index = input.indexOf(' ');
953         if (index >= 0) {
954             tagName = input.substring(0, index);
955             extra = input.substring(index);
956         } else {
957             tagName = input;
958             extra = "";
959         }
960         final Buffer buffer = editor.getBuffer();
961         if (buffer.getBooleanProperty(Property.FIX_CASE)) {
962             if (buffer.getBooleanProperty(Property.UPPER_CASE_TAG_NAMES))
963                 tagName = tagName.toUpperCase();
964             else
965                 tagName = tagName.toLowerCase();
966         }
967         CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
968         editor.fillToCaret();
969         final int offset = editor.getDotOffset();
970         editor.addUndo(SimpleEdit.INSERT_STRING);
971         FastStringBuffer sb = new FastStringBuffer('<');
972         sb.append(tagName);
973         sb.append(extra);
974         sb.append("/>");
975         editor.insertStringInternal(sb.toString());
976         Editor.updateInAllEditors(editor.getDotLine());
977         editor.addUndo(SimpleEdit.MOVE);
978         editor.getDot().setOffset(offset + 1 + input.length());
979         editor.moveCaretToDotCol();
980         editor.endCompoundEdit(compoundEdit);
981     }
982
983     public static void xmlFindMatch()
984     {
985         final Editor editor = Editor.currentEditor();
986         final Position dot = editor.getDot();
987         if (isInComment(dot)) {
988             editor.status("In comment");
989             return;
990         }
991         if (isInCDataSection(dot)) {
992             editor.status("In CDATA section");
993             return;
994         }
995         Position pos = findStartOfTag(dot);
996         if (pos == null) {
997             final Line dotLine = dot.getLine();
998             int offset = dot.getOffset();
999             if (dotLine.substring(0, offset).trim().length() == 0) {
1000                // We're in the whitespace to the left of the text on the line.
1001
// Skip to first non-whitespace char.
1002
while (Character.isWhitespace(dotLine.charAt(offset)) &&
1003                    offset < dotLine.length())
1004                    ++offset;
1005                if (dotLine.charAt(offset) == '<')
1006                    pos = new Position(dotLine, offset);
1007            }
1008            if (pos == null) {
1009                offset =
1010                    dotLine.getText().lastIndexOf(COMMENT_END, dot.getOffset());
1011                if (offset >= 0 && dot.getOffset() >= offset &&
1012                    dot.getOffset() < offset + COMMENT_END.length())
1013                    pos = new Position(dotLine, offset);
1014                else if (dotLine.trim().equals(COMMENT_END))
1015                    pos = new Position(dotLine,
1016                        dotLine.getText().indexOf(COMMENT_END));
1017            }
1018        }
1019
1020        if (pos == null) {
1021            editor.status("Nothing to match");
1022            return;
1023        }
1024
1025        Position match = null;
1026        if (pos.lookingAt(COMMENT_START)) {
1027            match = findCommentEnd(pos);
1028        } else if (pos.lookingAt(COMMENT_END)) {
1029            match = findCommentStart(pos);
1030        } else if (pos.lookingAt("</")) {
1031            // End tag.
1032
String JavaDoc name =
1033                Utilities.getTagName(pos.getLine().substring(pos.getOffset()));
1034            name = name.substring(1); // Remove "/".
1035
match = findMatchingStartTag(name, pos);
1036        } else if (pos.lookingAt("<")) {
1037            // Start tag.
1038
String JavaDoc tag = getTag(pos.copy());
1039            if (tag.endsWith("/>")) {
1040                editor.status("Nothing to match (empty-element tag)");
1041                return;
1042            }
1043            String JavaDoc name = Utilities.getTagName(tag);
1044            match = findMatchingEndTag(name, pos);
1045        }
1046
1047        if (match != null) {
1048            editor.updateDotLine();
1049            editor.addUndo(SimpleEdit.MOVE);
1050            dot.moveTo(match);
1051            editor.updateDotLine();
1052            editor.moveCaretToDotCol();
1053        } else
1054            editor.status("No match");
1055    }
1056
1057    public static void xmlInsertMatchingEndTag()
1058    {
1059        final Editor editor = Editor.currentEditor();
1060        if (!editor.checkReadOnly())
1061            return;
1062        final Buffer buffer = editor.getBuffer();
1063        if (buffer.needsRenumbering())
1064            buffer.renumber();
1065        if (buffer.needsParsing())
1066            buffer.getFormatter().parseBuffer();
1067        final Position dot = editor.getDot();
1068        String JavaDoc tag = getUnmatchedStartTag(dot);
1069        if (tag != null) {
1070            final String JavaDoc name = Utilities.getTagName(tag);
1071            final String JavaDoc endTag = "</" + name + ">";
1072            try {
1073                buffer.lockWrite();
1074            }
1075            catch (InterruptedException JavaDoc e) {
1076                Log.error(e);
1077                return;
1078            }
1079            try {
1080                CompoundEdit JavaDoc compoundEdit = editor.beginCompoundEdit();
1081                editor.fillToCaret();
1082                editor.addUndo(SimpleEdit.INSERT_STRING);
1083                editor.insertStringInternal(endTag);
1084                buffer.modified();
1085                editor.addUndo(SimpleEdit.MOVE);
1086                editor.moveCaretToDotCol();
1087                editor.endCompoundEdit(compoundEdit);
1088            }
1089            finally {
1090                buffer.unlockWrite();
1091            }
1092        }
1093    }
1094
1095    public static void xmlElectricSlash()
1096    {
1097        final Editor editor = Editor.currentEditor();
1098        if (!editor.checkReadOnly())
1099            return;
1100        final Buffer buffer = editor.getBuffer();
1101        if (buffer.needsRenumbering())
1102            buffer.renumber();
1103        final Position dot = editor.getDotCopy();
1104        final int offset = dot.getOffset();
1105        if (offset > 0 && dot.getLine().charAt(offset-1) == '<') {
1106            dot.setOffset(offset-1);
1107            String JavaDoc tag = getUnmatchedStartTag(dot);
1108            if (tag != null) {
1109                final String JavaDoc name = Utilities.getTagName(tag);
1110                final String JavaDoc endTag = "/" + name + ">";
1111                try {
1112                    buffer.lockWrite();
1113                }
1114                catch (InterruptedException JavaDoc e) {
1115                    Log.error(e);
1116                    return;
1117                }
1118                try {
1119                    // We don't need a compound edit here since all the
1120
// changes are line edits.
1121
editor.fillToCaret();
1122                    editor.addUndo(SimpleEdit.LINE_EDIT);
1123                    editor.insertStringInternal(endTag);
1124                    buffer.modified();
1125                    editor.moveCaretToDotCol();
1126                    if (buffer.getBooleanProperty(Property.AUTO_INDENT))
1127                        editor.indentLine();
1128                }
1129                finally {
1130                    buffer.unlockWrite();
1131                }
1132                return;
1133            }
1134
1135        }
1136        // Not electric.
1137
editor.insertNormalChar('/');
1138    }
1139
1140    // Scan backward for "<!--".
1141
private static Position findCommentStart(Position start)
1142    {
1143        Position pos = start.copy();
1144        do {
1145            if (pos.lookingAt(COMMENT_START))
1146                return pos;
1147        } while (pos.prev());
1148        // Not found.
1149
return null;
1150    }
1151
1152    // Scan forward for "-->".
1153
private static Position findCommentEnd(Position start)
1154    {
1155        Position pos = start.copy();
1156        do {
1157            if (pos.lookingAt(COMMENT_END))
1158                return pos;
1159        } while (pos.next());
1160        // Not found.
1161
return null;
1162    }
1163}
1164
Popular Tags