0001: /*
0002: * XmlMode.java
0003: *
0004: * Copyright (C) 1998-2004 Peter Graves
0005: * $Id: XmlMode.java,v 1.17 2004/04/22 17:49:38 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j;
0023:
0024: import gnu.regexp.RE;
0025: import gnu.regexp.REException;
0026: import gnu.regexp.REMatch;
0027: import gnu.regexp.UncheckedRE;
0028: import java.awt.event.KeyEvent;
0029: import java.io.BufferedReader;
0030: import java.io.IOException;
0031: import java.io.InputStreamReader;
0032: import java.io.StringReader;
0033: import javax.swing.tree.DefaultMutableTreeNode;
0034: import javax.swing.tree.TreeModel;
0035: import javax.swing.tree.TreeNode;
0036: import javax.swing.tree.TreePath;
0037: import javax.swing.undo.CompoundEdit;
0038: import org.xml.sax.SAXParseException;
0039:
0040: public final class XmlMode extends AbstractMode implements Constants,
0041: Mode {
0042: private static final String COMMENT_START = "<!--";
0043: private static final String COMMENT_END = "-->";
0044: private static final String CDATA_START = "<![CDATA[";
0045: private static final String CDATA_END = "]]>";
0046:
0047: private static final XmlMode mode = new XmlMode();
0048:
0049: private static XmlErrorBuffer errorBuffer;
0050:
0051: private static RE tagNameRE;
0052: private static RE attributeNameRE;
0053: private static RE quotedValueRE;
0054: private static RE unquotedValueRE;
0055:
0056: private XmlMode() {
0057: super (XML_MODE, XML_MODE_NAME);
0058: setProperty(Property.INDENT_SIZE, 2);
0059: }
0060:
0061: public static final XmlMode getMode() {
0062: return mode;
0063: }
0064:
0065: public static final XmlErrorBuffer getErrorBuffer() {
0066: return errorBuffer;
0067: }
0068:
0069: public NavigationComponent getSidebarComponent(Editor editor) {
0070: Debug.assertTrue(editor.getBuffer().getMode() == getMode());
0071: if (!editor.getBuffer()
0072: .getBooleanProperty(Property.ENABLE_TREE))
0073: return null;
0074: View view = editor.getCurrentView();
0075: if (view == null)
0076: return null; // Shouldn't happen.
0077: if (view.getSidebarComponent() == null)
0078: view.setSidebarComponent(new XmlTree(editor, null));
0079: return view.getSidebarComponent();
0080: }
0081:
0082: public String getCommentStart() {
0083: return COMMENT_START;
0084: }
0085:
0086: public String getCommentEnd() {
0087: return COMMENT_END;
0088: }
0089:
0090: public Formatter getFormatter(Buffer buffer) {
0091: return new XmlFormatter(buffer);
0092: }
0093:
0094: protected void setKeyMapDefaults(KeyMap km) {
0095: km.mapKey(KeyEvent.VK_TAB, 0, "tab");
0096: km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab");
0097: km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent");
0098: km.mapKey(KeyEvent.VK_ENTER, CTRL_MASK, "newline");
0099: km.mapKey(KeyEvent.VK_M, CTRL_MASK, "xmlFindMatch");
0100: km.mapKey('=', "xmlElectricEquals");
0101: km.mapKey('>', "electricCloseAngleBracket");
0102: km.mapKey(KeyEvent.VK_E, CTRL_MASK, "xmlInsertMatchingEndTag");
0103: km.mapKey('/', "xmlElectricSlash");
0104: km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize");
0105: km.mapKey(KeyEvent.VK_COMMA, CTRL_MASK | SHIFT_MASK,
0106: "xmlInsertTag");
0107: km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | SHIFT_MASK,
0108: "xmlInsertEmptyElementTag");
0109: km.mapKey(KeyEvent.VK_P, CTRL_MASK, "xmlParseBuffer");
0110: km.mapKey(KeyEvent.VK_P, CTRL_MASK | SHIFT_MASK,
0111: "xmlValidateBuffer");
0112: km.mapKey(KeyEvent.VK_EQUALS, CTRL_MASK, "xmlFindCurrentNode");
0113: km.mapKey(KeyEvent.VK_OPEN_BRACKET, CTRL_MASK, "fold");
0114: km.mapKey(KeyEvent.VK_CLOSE_BRACKET, CTRL_MASK, "unfold");
0115:
0116: // build.xml
0117: km.mapKey(KeyEvent.VK_F9, 0, "compile");
0118: km.mapKey(KeyEvent.VK_F9, CTRL_MASK, "recompile");
0119: }
0120:
0121: public void populateModeMenu(Editor editor, Menu menu) {
0122: menu.add(editor, "Insert Element", 'I', "xmlInsertTag");
0123: menu.add(editor, "End Current Element", 'E',
0124: "xmlInsertMatchingEndTag");
0125: menu.addSeparator();
0126: menu.add(editor, "Parse Buffer", 'P', "xmlParseBuffer");
0127: menu.add(editor, "Validate Buffer", 'V', "xmlValidateBuffer");
0128: boolean enabled = errorBuffer != null;
0129: menu.addSeparator();
0130: menu.add(editor, "Next Error", 'N', "nextError", enabled);
0131: menu.add(editor, "Previous Error", 'R', "previousError",
0132: enabled);
0133: menu.add(editor, "Show Error Message", 'M', "showMessage",
0134: enabled);
0135: }
0136:
0137: public void loadFile(Buffer buffer, File file) {
0138: String encoding = null;
0139: try {
0140: BufferedReader reader = new BufferedReader(
0141: new InputStreamReader(file.getInputStream()));
0142: String s = reader.readLine();
0143: reader.close();
0144: if (s != null && s.toLowerCase().startsWith("<?xml")) {
0145: int end = s.indexOf("?>");
0146: if (end >= 0) {
0147: s = s.substring(5, end);
0148: RE re = new UncheckedRE("encoding[ \t]*=[ \t]*");
0149: REMatch match = re.getMatch(s);
0150: if (match != null) {
0151: // First char after match will be single or double
0152: // quote.
0153: s = s.substring(match.getEndIndex());
0154: if (s.length() > 0) {
0155: char quoteChar = s.charAt(0);
0156: // Find matching quote char.
0157: end = s.indexOf(quoteChar, 1);
0158: if (end >= 0) {
0159: encoding = s.substring(1, end);
0160: if (Utilities
0161: .isSupportedEncoding(encoding))
0162: file.setEncoding(encoding);
0163: else
0164: Log.error("unsupported encoding \""
0165: + encoding + '"');
0166: }
0167: }
0168: }
0169: }
0170: }
0171: } catch (IOException e) {
0172: Log.error(e);
0173: }
0174: if (encoding == null)
0175: encoding = "UTF8"; // Default for XML.
0176: try {
0177: buffer.load(file.getInputStream(), file.getEncoding());
0178: } catch (IOException e) {
0179: Log.error(e);
0180: }
0181: }
0182:
0183: public boolean canIndent() {
0184: return true;
0185: }
0186:
0187: public int getCorrectIndentation(Line line, Buffer buffer) {
0188: final Line model = getModel(line);
0189: if (model == null)
0190: return 0;
0191: int indent = buffer.getIndentation(model);
0192: if (line.flags() == STATE_QUOTE && model.flags() != STATE_QUOTE)
0193: return indent + buffer.getIndentSize();
0194: final String text = line.trim();
0195: if (text.equals("/>")) {
0196: Position pos = new Position(line, line.length());
0197: while (pos.prev()) {
0198: if (pos.getChar() == '<')
0199: break;
0200: }
0201: return buffer.getIndentation(pos.getLine());
0202: }
0203: if (text.startsWith("</")) {
0204: Position pos = findMatchingStartTag(line);
0205: if (pos != null)
0206: return buffer.getIndentation(pos.getLine());
0207: indent -= buffer.getIndentSize();
0208: return indent < 0 ? 0 : indent;
0209: }
0210: final String modelText = model.trim();
0211: if (modelText.startsWith("<") && !modelText.startsWith("</")
0212: && !modelText.startsWith("<!")) {
0213: String tag = getTag(modelText);
0214: if (isEmptyElementTag(tag))
0215: return indent;
0216: if (isProcessingInstruction(tag))
0217: return indent;
0218: // Model starts with start tag.
0219: String tagName = Utilities.getTagName(modelText);
0220: String startTag = "<" + tagName;
0221: String endTag = "</" + tagName;
0222: int count = 1;
0223: final int limit = modelText.length();
0224: for (int i = startTag.length(); i < limit; i++) {
0225: if (modelText.charAt(i) == '<') {
0226: if (lookingAt(modelText, i, endTag)) {
0227: int end = i + endTag.length();
0228: if (end < limit) {
0229: char c = modelText.charAt(end);
0230: if (c == ' ' || c == '\t' || c == '>')
0231: --count;
0232: }
0233: } else if (lookingAt(modelText, i, startTag)) {
0234: int end = i + startTag.length();
0235: if (end < limit) {
0236: char c = modelText.charAt(end);
0237: if (c == ' ' || c == '\t' || c == '>')
0238: ++count;
0239: }
0240: }
0241: }
0242: }
0243: if (count > 0)
0244: indent += buffer.getIndentSize();
0245: } else if (modelText.endsWith("/>")) {
0246: Position pos = new Position(model, model.length());
0247: while (pos.prev()) {
0248: if (pos.getChar() == '<')
0249: break;
0250: }
0251: indent = buffer.getIndentation(pos.getLine());
0252: }
0253: return indent;
0254: }
0255:
0256: // Line must start with an end tag.
0257: private Position findMatchingStartTag(Line line) {
0258: String s = line.trim();
0259: if (!s.startsWith("</"))
0260: return null;
0261: FastStringBuffer sb = new FastStringBuffer();
0262: for (int i = 2; i < s.length(); i++) {
0263: char c = s.charAt(i);
0264: if (c <= ' ')
0265: break;
0266: if (c == '>')
0267: break;
0268: sb.append(c);
0269: }
0270: Position pos = new Position(line, 0);
0271: String name = sb.toString();
0272: return findMatchingStartTag(name, pos);
0273: }
0274:
0275: private static Position findMatchingStartTag(String name,
0276: Position start) {
0277: Position pos = start.copy();
0278: String endTagToBeMatched = "</" + name + ">";
0279: String lookFor = "<" + name;
0280: int count = 1;
0281: boolean succeeded = false;
0282: if (pos.lookingAt(endTagToBeMatched))
0283: pos.prev();
0284: // Search backward.
0285: while (!pos.atStart()) {
0286: if (pos.lookingAt(COMMENT_END)) {
0287: do {
0288: pos.prev();
0289: } while (!pos.atStart()
0290: && !pos.lookingAt(COMMENT_START));
0291: } else if (pos.lookingAt(CDATA_END)) {
0292: do {
0293: pos.prev();
0294: } while (!pos.atStart() && !pos.lookingAt(CDATA_START));
0295: } else if (pos.lookingAt(endTagToBeMatched)) {
0296: ++count;
0297: } else if (pos.lookingAt(lookFor)) {
0298: // getTag() skips past the tag, so use pos.copy() here since
0299: // we're moving backwards not forwards.
0300: String tag = getTag(pos.copy());
0301: if (Utilities.getTagName(tag).equals(name)) {
0302: if (!tag.endsWith("/>")) {
0303: --count;
0304: if (count == 0) {
0305: succeeded = true;
0306: break;
0307: }
0308: }
0309: }
0310: }
0311: pos.prev();
0312: }
0313: if (succeeded)
0314: return pos;
0315: // Not found.
0316: return null;
0317: }
0318:
0319: private static Position findMatchingEndTag(String name,
0320: Position start) {
0321: Position pos = start.copy();
0322: String startTagToBeMatched = "<" + name;
0323: String lookFor = "</" + name + ">";
0324: int count = 1;
0325: if (pos.lookingAt(startTagToBeMatched))
0326: pos.skip(startTagToBeMatched.length());
0327: // Search forward.
0328: while (!pos.atEnd()) {
0329: if (pos.lookingAt(COMMENT_START)) {
0330: do {
0331: pos.next();
0332: } while (!pos.atEnd() && !pos.lookingAt(COMMENT_END));
0333: if (pos.atEnd()) {
0334: break;
0335: } else {
0336: pos.skip(COMMENT_END.length());
0337: continue;
0338: }
0339: }
0340: if (pos.lookingAt(CDATA_START)) {
0341: do {
0342: pos.next();
0343: } while (!pos.atEnd() && !pos.lookingAt(CDATA_END));
0344: if (pos.atEnd()) {
0345: break;
0346: } else {
0347: pos.skip(CDATA_END.length());
0348: continue;
0349: }
0350: }
0351: if (pos.lookingAt(startTagToBeMatched)) {
0352: String tag = getTag(pos); // Skips past tag.
0353: if (Utilities.getTagName(tag).equals(name)) {
0354: if (!tag.endsWith("/>"))
0355: ++count;
0356: }
0357: continue;
0358: }
0359: if (pos.lookingAt(lookFor)) {
0360: --count;
0361: if (count == 0) {
0362: return pos;
0363: } else {
0364: pos.skip(lookFor.length());
0365: continue;
0366: }
0367: }
0368: // None of the above...
0369: pos.next();
0370: }
0371: // Not found.
0372: return null;
0373: }
0374:
0375: private static String getUnmatchedStartTag(Position start) {
0376: Position pos = start.copy();
0377: if (isInComment(pos))
0378: return null;
0379: if (isInTag(pos))
0380: return null;
0381: if (isInCDataSection(pos))
0382: return null;
0383: int count = 1;
0384: if (!pos.lookingAt("<") || pos.prev()) {
0385: do {
0386: if (pos.lookingAt(COMMENT_END)) {
0387: do {
0388: pos.prev();
0389: } while (!pos.atStart()
0390: && !pos.lookingAt(COMMENT_START));
0391: } else if (pos.lookingAt(CDATA_END)) {
0392: do {
0393: pos.prev();
0394: } while (!pos.atStart()
0395: && !pos.lookingAt(CDATA_START));
0396: } else if (pos.getChar() == '<') {
0397: if (pos.lookingAt("</"))
0398: ++count;
0399: else if (pos.lookingAt("<?"))
0400: ;
0401: else {
0402: // getTag() skips past the tag, so use pos.copy() here
0403: // since we're moving backwards not forwards.
0404: String tag = getTag(pos.copy());
0405: if (!tag.endsWith("/>")) {
0406: --count;
0407: if (count == 0)
0408: return tag;
0409: }
0410: }
0411: }
0412: } while (pos.prev());
0413: }
0414: // Not found.
0415: return null;
0416: }
0417:
0418: private static boolean isInTag(Position position) {
0419: Position pos = position.copy();
0420: while (pos.prev()) {
0421: if (pos.lookingAt(COMMENT_END)) {
0422: do {
0423: pos.prev();
0424: } while (!pos.atStart()
0425: && !pos.lookingAt(COMMENT_START));
0426: continue;
0427: }
0428: char c = pos.getChar();
0429: if (c == '<')
0430: return true;
0431: else if (c == '>')
0432: return false;
0433: }
0434: return false;
0435: }
0436:
0437: private static boolean isInComment(Position position) {
0438: Position pos = position.copy();
0439: boolean inComment = pos.getLine().flags() == STATE_COMMENT;
0440: pos.setOffset(0);
0441: final int limit = position.getOffset();
0442: while (pos.getOffset() < limit) {
0443: if (inComment) {
0444: if (pos.lookingAt(COMMENT_END)) {
0445: pos.skip(COMMENT_END.length());
0446: if (pos.getOffset() > limit)
0447: break;
0448: inComment = false;
0449: continue;
0450: }
0451: } else if (pos.lookingAt(COMMENT_START)) {
0452: inComment = true;
0453: pos.skip(COMMENT_START.length());
0454: continue;
0455: }
0456: pos.next();
0457: }
0458: return inComment;
0459: }
0460:
0461: private static boolean isInCDataSection(Position position) {
0462: Position pos = position.copy();
0463: boolean inCDataSection = pos.getLine().flags() == STATE_CDATA;
0464: pos.setOffset(0);
0465: final int limit = position.getOffset();
0466: while (pos.getOffset() < limit) {
0467: if (inCDataSection) {
0468: if (pos.lookingAt(CDATA_END)) {
0469: pos.skip(CDATA_END.length());
0470: if (pos.getOffset() > limit)
0471: break;
0472: inCDataSection = false;
0473: continue;
0474: }
0475: } else if (pos.lookingAt(CDATA_START)) {
0476: inCDataSection = true;
0477: pos.skip(CDATA_START.length());
0478: continue;
0479: }
0480: pos.next();
0481: }
0482: return inCDataSection;
0483: }
0484:
0485: private static String getTag(String s) {
0486: if (s == null || s.length() == 0 || s.charAt(0) != '<')
0487: return null;
0488: FastStringBuffer sb = new FastStringBuffer();
0489: final int limit = s.length();
0490: char quoteChar = 0;
0491: for (int i = 0; i < limit; i++) {
0492: char c = s.charAt(i);
0493: sb.append(c);
0494: if (quoteChar != 0) {
0495: // We're in a quoted section.
0496: if (c == quoteChar)
0497: quoteChar = 0;
0498: } else {
0499: // We're not in a quoted section.
0500: if (c == '\'' || c == '"')
0501: quoteChar = c;
0502: else if (c == '>')
0503: break;
0504: }
0505: }
0506: return sb.toString();
0507: }
0508:
0509: // Advances position to first char past end of tag.
0510: private static String getTag(Position pos) {
0511: if (pos == null || pos.getChar() != '<')
0512: return null;
0513: FastStringBuffer sb = new FastStringBuffer('<');
0514: char quoteChar = 0;
0515: while (pos.next()) {
0516: char c = pos.getChar();
0517: sb.append(c);
0518: if (quoteChar != 0) {
0519: // We're in a quoted section.
0520: if (c == quoteChar)
0521: quoteChar = 0;
0522: } else {
0523: // We're not in a quoted section.
0524: if (c == '\'' || c == '"')
0525: quoteChar = c;
0526: else if (c == '>') {
0527: pos.next();
0528: break;
0529: }
0530: }
0531: }
0532: return sb.toString();
0533: }
0534:
0535: private static boolean isProcessingInstruction(String tag) {
0536: if (tag.startsWith("<?") && tag.endsWith("?>"))
0537: return true;
0538: return false;
0539: }
0540:
0541: private static boolean isEmptyElementTag(String tag) {
0542: if (tag == null)
0543: return false;
0544: return tag.endsWith("/>");
0545: }
0546:
0547: private static final boolean lookingAt(String s, int i,
0548: String pattern) {
0549: return s.regionMatches(i, pattern, 0, pattern.length());
0550: }
0551:
0552: private static Line getModel(Line line) {
0553: Line model = line;
0554: while ((model = model.previous()) != null) {
0555: int flags = model.flags();
0556: if (flags == STATE_COMMENT || flags == STATE_QUOTE)
0557: continue;
0558: else if (model.trim().startsWith(COMMENT_START))
0559: continue;
0560: else if (model.isBlank())
0561: continue;
0562: else
0563: break;
0564: }
0565: return model;
0566: }
0567:
0568: public char fixCase(Editor editor, char c) {
0569: if (!Character.isUpperCase(c) && !Character.isLowerCase(c))
0570: return c;
0571: final Buffer buffer = editor.getBuffer();
0572: if (!buffer.getBooleanProperty(Property.FIX_CASE))
0573: return c;
0574: if (!initRegExps())
0575: return c;
0576: Position pos = findStartOfTag(editor.getDot());
0577: if (pos != null) {
0578: int index = pos.getOffset();
0579: String text = pos.getLine().getText();
0580: REMatch match = tagNameRE.getMatch(text, index);
0581: if (match == null)
0582: return c;
0583: if (match.getEndIndex() >= editor.getDotOffset()) {
0584: // Tag name.
0585: if (buffer
0586: .getBooleanProperty(Property.UPPER_CASE_TAG_NAMES))
0587: return Character.toUpperCase(c);
0588: else
0589: return Character.toLowerCase(c);
0590: }
0591: while (true) {
0592: index = match.getEndIndex();
0593: match = attributeNameRE.getMatch(text, index);
0594: if (match == null)
0595: return c;
0596: if (match.getEndIndex() >= editor.getDotOffset()) {
0597: // Attribute name.
0598: if (buffer
0599: .getBooleanProperty(Property.UPPER_CASE_ATTRIBUTE_NAMES))
0600: return Character.toUpperCase(c);
0601: else
0602: return Character.toLowerCase(c);
0603: }
0604: index = match.getEndIndex();
0605: match = quotedValueRE.getMatch(text, index);
0606: if (match == null) {
0607: match = unquotedValueRE.getMatch(text, index);
0608: if (match == null)
0609: return c;
0610: }
0611: if (match.getEndIndex() >= editor.getDotOffset()) {
0612: // Attribute value.
0613: return c;
0614: }
0615: }
0616: }
0617: return c;
0618: }
0619:
0620: private static boolean checkElectricEquals(Editor editor) {
0621: Position pos = findStartOfTag(editor.getDot());
0622: if (pos == null)
0623: return false;
0624: char c = editor.getDotChar();
0625: if (c == '>' || c == '/' || Character.isWhitespace(c))
0626: return true;
0627: return false;
0628: }
0629:
0630: // Scans backward on same line for '<'.
0631: private static Position findStartOfTag(Position pos) {
0632: final String text = pos.getLine().getText();
0633: int offset = pos.getOffset();
0634: if (offset >= pos.getLine().length())
0635: offset = pos.getLine().length() - 1;
0636: else if (text.charAt(offset) == '>')
0637: --offset;
0638: while (offset >= 0) {
0639: char c = text.charAt(offset);
0640: if (c == '<')
0641: return new Position(pos.getLine(), offset);
0642: if (c == '>')
0643: return null;
0644: --offset;
0645: }
0646: return null;
0647: }
0648:
0649: private static boolean initRegExps() {
0650: if (tagNameRE == null) {
0651: try {
0652: tagNameRE = new RE("</?[A-Za-z0-9]*");
0653: attributeNameRE = new RE("\\s+[A-Za-z0-9]*");
0654: quotedValueRE = new RE("\\s*=\\s*\"[^\"]*");
0655: unquotedValueRE = new RE("\\s*=\\s*\\S*");
0656: } catch (REException e) {
0657: tagNameRE = null;
0658: return false;
0659: }
0660: }
0661: return true;
0662: }
0663:
0664: public static void xmlFindCurrentNode() {
0665: final Editor editor = Editor.currentEditor();
0666: if (editor.getModeId() == XML_MODE) {
0667: final Sidebar sidebar = editor.getSidebar();
0668: if (sidebar != null) {
0669: XmlTree tree = (XmlTree) sidebar.getBottomComponent();
0670: if (tree != null)
0671: ensureCurrentNodeIsVisible(editor, tree);
0672: }
0673: }
0674: }
0675:
0676: public static void xmlParseBuffer() {
0677: final Editor editor = Editor.currentEditor();
0678: if (editor.getModeId() == XML_MODE) {
0679: XmlTree tree = null;
0680: final Sidebar sidebar = editor.getSidebar();
0681: if (sidebar != null) {
0682: tree = (XmlTree) sidebar.getBottomComponent();
0683:
0684: // If there's no tree...
0685: if (tree == null) {
0686: sidebar
0687: .setUpdateFlag(SIDEBAR_NAVIGATION_COMPONENT_ALL);
0688: sidebar.refreshSidebar();
0689: tree = (XmlTree) sidebar.getBottomComponent();
0690: if (tree != null)
0691: ensureCurrentNodeIsVisible(editor, tree);
0692: return;
0693: }
0694: }
0695: final Buffer buffer = editor.getBuffer();
0696: XmlParserImpl parser = new XmlParserImpl(buffer);
0697: if (parser.initialize()) {
0698: editor.setWaitCursor();
0699: try {
0700: parser
0701: .setReader(new StringReader(buffer
0702: .getText()));
0703: parser.run();
0704: } catch (OutOfMemoryError e) {
0705: outOfMemory();
0706: return;
0707: } finally {
0708: editor.setDefaultCursor();
0709: }
0710: String output = parser.getOutput();
0711: // Note that with the current implementation, there will always
0712: // be output...
0713: if (output != null && output.length() > 0) {
0714: if (errorBuffer == null) {
0715: errorBuffer = new XmlErrorBuffer(buffer
0716: .getFile(), output);
0717: } else
0718: errorBuffer.recycle(buffer.getFile(), output);
0719: Editor otherEditor = editor.getOtherEditor();
0720: if (otherEditor != null) {
0721: errorBuffer.setUnsplitOnClose(otherEditor
0722: .getBuffer().unsplitOnClose());
0723: otherEditor.makeNext(errorBuffer);
0724: } else
0725: errorBuffer.setUnsplitOnClose(true);
0726: editor.displayInOtherWindow(errorBuffer);
0727: }
0728: if (parser.getException() == null) {
0729: if (tree != null) {
0730: TreeModel treeModel = parser.getTreeModel();
0731: if (treeModel != null) {
0732: tree.setParserClassName(parser
0733: .getParserClassName());
0734: tree.setModel(treeModel);
0735: Debug.assertTrue(tree.getModel() != null);
0736: Debug
0737: .assertTrue(tree.getModel()
0738: .getRoot() != null);
0739: ensureCurrentNodeIsVisible(editor, tree);
0740: }
0741: }
0742: editor.status("No errors");
0743: }
0744: }
0745: }
0746: }
0747:
0748: public static void xmlValidateBuffer() {
0749: final Editor editor = Editor.currentEditor();
0750: if (editor.getModeId() == XML_MODE) {
0751: final Buffer buffer = editor.getBuffer();
0752: XmlParserImpl parser = new XmlParserImpl(buffer);
0753: if (parser.initialize()) {
0754: try {
0755: editor.setWaitCursor();
0756: parser.enableValidation(true);
0757: parser
0758: .setReader(new StringReader(buffer
0759: .getText()));
0760: parser.run();
0761: } catch (OutOfMemoryError e) {
0762: outOfMemory();
0763: return;
0764: } finally {
0765: editor.setDefaultCursor();
0766: }
0767: String output = parser.getOutput();
0768: if (output != null && output.length() > 0) {
0769: if (errorBuffer == null) {
0770: errorBuffer = new XmlErrorBuffer(buffer
0771: .getFile(), output);
0772: } else
0773: errorBuffer.recycle(buffer.getFile(), output);
0774: Editor otherEditor = editor.getOtherEditor();
0775: if (otherEditor != null) {
0776: errorBuffer.setUnsplitOnClose(otherEditor
0777: .getBuffer().unsplitOnClose());
0778: otherEditor.makeNext(errorBuffer);
0779: } else
0780: errorBuffer.setUnsplitOnClose(true);
0781: editor.displayInOtherWindow(errorBuffer);
0782: } else
0783: editor.status("No errors");
0784: }
0785: }
0786: }
0787:
0788: private static void outOfMemory() {
0789: MessageDialog.showMessageDialog(
0790: "Not enough memory to run parser", "XML Mode");
0791: }
0792:
0793: public static void xmlFindError(Editor editor, SAXParseException e) {
0794: Line line = editor.getBuffer().getLine(e.getLineNumber() - 1);
0795: if (line != null) {
0796: int offset = e.getColumnNumber() - 1;
0797: if (offset < 0)
0798: offset = 0;
0799: if (offset > line.length())
0800: offset = line.length();
0801: if (line != editor.getDotLine()
0802: || offset != editor.getDotOffset()) {
0803: editor.addUndo(SimpleEdit.MOVE);
0804: editor.updateDotLine();
0805: editor.setDot(line, offset);
0806: editor.updateDotLine();
0807: editor.moveCaretToDotCol();
0808: editor.updateDisplay();
0809: }
0810: }
0811: }
0812:
0813: public static void ensureCurrentNodeIsVisible(Editor editor,
0814: XmlTree tree) {
0815: if (tree == null)
0816: return;
0817: DefaultMutableTreeNode currentNode = tree.getNodeAtPos(editor
0818: .getDot());
0819: if (currentNode != null) {
0820: tree
0821: .scrollPathToVisible(new TreePath(currentNode
0822: .getPath()));
0823: tree.updatePosition();
0824: }
0825: }
0826:
0827: public static void copyXPath() {
0828: final Editor editor = Editor.currentEditor();
0829: if (editor.getModeId() == XML_MODE) {
0830: final Sidebar sidebar = editor.getSidebar();
0831: if (sidebar != null) {
0832: XmlTree tree = (XmlTree) sidebar.getBottomComponent();
0833: if (tree != null) {
0834: DefaultMutableTreeNode currentNode = tree
0835: .getNodeAtPos(editor.getDot());
0836: if (currentNode != null) {
0837: TreeNode[] array = currentNode.getPath();
0838: if (array != null) {
0839: FastStringBuffer sb = new FastStringBuffer();
0840: for (int i = 0; i < array.length; i++) {
0841: DefaultMutableTreeNode node = (DefaultMutableTreeNode) array[i];
0842: XmlTreeElement element = (XmlTreeElement) node
0843: .getUserObject();
0844: sb.append('/');
0845: sb.append(element.getName());
0846: }
0847: if (sb.length() > 0) {
0848: KillRing killRing = editor
0849: .getKillRing();
0850: killRing.appendNew(sb.toString());
0851: killRing
0852: .copyLastKillToSystemClipboard();
0853: editor
0854: .status("XPath copied to clipboard");
0855: }
0856: }
0857: }
0858: }
0859: }
0860: }
0861: }
0862:
0863: public static void xmlElectricEquals() {
0864: final Editor editor = Editor.currentEditor();
0865: if (!editor.checkReadOnly())
0866: return;
0867: boolean ok = false;
0868: if (editor.getModeId() == XML_MODE) {
0869: // Attributes always require quotes in XML mode.
0870: if (checkElectricEquals(editor))
0871: ok = true;
0872: }
0873: if (ok) {
0874: CompoundEdit compoundEdit = editor.beginCompoundEdit();
0875: editor.fillToCaret();
0876: editor.addUndo(SimpleEdit.INSERT_STRING);
0877: editor.insertStringInternal("=\"\"");
0878: editor.addUndo(SimpleEdit.MOVE);
0879: editor.getDot().moveLeft();
0880: editor.moveCaretToDotCol();
0881: editor.endCompoundEdit(compoundEdit);
0882: } else
0883: editor.insertNormalChar('=');
0884: }
0885:
0886: public static void xmlInsertTag() {
0887: final Editor editor = Editor.currentEditor();
0888: if (!editor.checkReadOnly())
0889: return;
0890: InsertTagDialog d = new InsertTagDialog(editor);
0891: editor.centerDialog(d);
0892: d.show();
0893: _xmlInsertTag(editor, d.getInput());
0894: }
0895:
0896: public static void xmlInsertTag(String input) {
0897: final Editor editor = Editor.currentEditor();
0898: if (!editor.checkReadOnly())
0899: return;
0900: _xmlInsertTag(editor, input);
0901: }
0902:
0903: private static void _xmlInsertTag(Editor editor, String input) {
0904: if (input != null) {
0905: final String tagName, extra;
0906: int index = input.indexOf(' ');
0907: if (index >= 0) {
0908: tagName = input.substring(0, index);
0909: extra = input.substring(index);
0910: } else {
0911: tagName = input;
0912: extra = "";
0913: }
0914: // We always want the end tag in XML mode.
0915: InsertTagDialog.insertTag(editor, tagName, extra, true);
0916: }
0917: }
0918:
0919: public static void xmlInsertEmptyElementTag() {
0920: final Editor editor = Editor.currentEditor();
0921: if (!editor.checkReadOnly())
0922: return;
0923: InputDialog d = new InputDialog(editor, "Tag:",
0924: "Insert Empty Element Tag", null);
0925: d.setHistory(new History("xmlInsertEmptyElementTag"));
0926: editor.centerDialog(d);
0927: d.show();
0928: String input = d.getInput();
0929: if (input == null)
0930: return;
0931: String tagName;
0932: String extra;
0933: int index = input.indexOf(' ');
0934: if (index >= 0) {
0935: tagName = input.substring(0, index);
0936: extra = input.substring(index);
0937: } else {
0938: tagName = input;
0939: extra = "";
0940: }
0941: final Buffer buffer = editor.getBuffer();
0942: if (buffer.getBooleanProperty(Property.FIX_CASE)) {
0943: if (buffer
0944: .getBooleanProperty(Property.UPPER_CASE_TAG_NAMES))
0945: tagName = tagName.toUpperCase();
0946: else
0947: tagName = tagName.toLowerCase();
0948: }
0949: CompoundEdit compoundEdit = editor.beginCompoundEdit();
0950: editor.fillToCaret();
0951: final int offset = editor.getDotOffset();
0952: editor.addUndo(SimpleEdit.INSERT_STRING);
0953: FastStringBuffer sb = new FastStringBuffer('<');
0954: sb.append(tagName);
0955: sb.append(extra);
0956: sb.append("/>");
0957: editor.insertStringInternal(sb.toString());
0958: Editor.updateInAllEditors(editor.getDotLine());
0959: editor.addUndo(SimpleEdit.MOVE);
0960: editor.getDot().setOffset(offset + 1 + input.length());
0961: editor.moveCaretToDotCol();
0962: editor.endCompoundEdit(compoundEdit);
0963: }
0964:
0965: public static void xmlFindMatch() {
0966: final Editor editor = Editor.currentEditor();
0967: final Position dot = editor.getDot();
0968: if (isInComment(dot)) {
0969: editor.status("In comment");
0970: return;
0971: }
0972: if (isInCDataSection(dot)) {
0973: editor.status("In CDATA section");
0974: return;
0975: }
0976: Position pos = findStartOfTag(dot);
0977: if (pos == null) {
0978: final Line dotLine = dot.getLine();
0979: int offset = dot.getOffset();
0980: if (dotLine.substring(0, offset).trim().length() == 0) {
0981: // We're in the whitespace to the left of the text on the line.
0982: // Skip to first non-whitespace char.
0983: while (Character.isWhitespace(dotLine.charAt(offset))
0984: && offset < dotLine.length())
0985: ++offset;
0986: if (dotLine.charAt(offset) == '<')
0987: pos = new Position(dotLine, offset);
0988: }
0989: if (pos == null) {
0990: offset = dotLine.getText().lastIndexOf(COMMENT_END,
0991: dot.getOffset());
0992: if (offset >= 0
0993: && dot.getOffset() >= offset
0994: && dot.getOffset() < offset
0995: + COMMENT_END.length())
0996: pos = new Position(dotLine, offset);
0997: else if (dotLine.trim().equals(COMMENT_END))
0998: pos = new Position(dotLine, dotLine.getText()
0999: .indexOf(COMMENT_END));
1000: }
1001: }
1002:
1003: if (pos == null) {
1004: editor.status("Nothing to match");
1005: return;
1006: }
1007:
1008: Position match = null;
1009: if (pos.lookingAt(COMMENT_START)) {
1010: match = findCommentEnd(pos);
1011: } else if (pos.lookingAt(COMMENT_END)) {
1012: match = findCommentStart(pos);
1013: } else if (pos.lookingAt("</")) {
1014: // End tag.
1015: String name = Utilities.getTagName(pos.getLine().substring(
1016: pos.getOffset()));
1017: name = name.substring(1); // Remove "/".
1018: match = findMatchingStartTag(name, pos);
1019: } else if (pos.lookingAt("<")) {
1020: // Start tag.
1021: String tag = getTag(pos.copy());
1022: if (tag.endsWith("/>")) {
1023: editor.status("Nothing to match (empty-element tag)");
1024: return;
1025: }
1026: String name = Utilities.getTagName(tag);
1027: match = findMatchingEndTag(name, pos);
1028: }
1029:
1030: if (match != null) {
1031: editor.updateDotLine();
1032: editor.addUndo(SimpleEdit.MOVE);
1033: dot.moveTo(match);
1034: editor.updateDotLine();
1035: editor.moveCaretToDotCol();
1036: } else
1037: editor.status("No match");
1038: }
1039:
1040: public static void xmlInsertMatchingEndTag() {
1041: final Editor editor = Editor.currentEditor();
1042: if (!editor.checkReadOnly())
1043: return;
1044: final Buffer buffer = editor.getBuffer();
1045: if (buffer.needsRenumbering())
1046: buffer.renumber();
1047: if (buffer.needsParsing())
1048: buffer.getFormatter().parseBuffer();
1049: final Position dot = editor.getDot();
1050: String tag = getUnmatchedStartTag(dot);
1051: if (tag != null) {
1052: final String name = Utilities.getTagName(tag);
1053: final String endTag = "</" + name + ">";
1054: try {
1055: buffer.lockWrite();
1056: } catch (InterruptedException e) {
1057: Log.error(e);
1058: return;
1059: }
1060: try {
1061: CompoundEdit compoundEdit = editor.beginCompoundEdit();
1062: editor.fillToCaret();
1063: editor.addUndo(SimpleEdit.INSERT_STRING);
1064: editor.insertStringInternal(endTag);
1065: buffer.modified();
1066: editor.addUndo(SimpleEdit.MOVE);
1067: editor.moveCaretToDotCol();
1068: editor.endCompoundEdit(compoundEdit);
1069: } finally {
1070: buffer.unlockWrite();
1071: }
1072: }
1073: }
1074:
1075: public static void xmlElectricSlash() {
1076: final Editor editor = Editor.currentEditor();
1077: if (!editor.checkReadOnly())
1078: return;
1079: final Buffer buffer = editor.getBuffer();
1080: if (buffer.needsRenumbering())
1081: buffer.renumber();
1082: final Position dot = editor.getDotCopy();
1083: final int offset = dot.getOffset();
1084: if (offset > 0 && dot.getLine().charAt(offset - 1) == '<') {
1085: dot.setOffset(offset - 1);
1086: String tag = getUnmatchedStartTag(dot);
1087: if (tag != null) {
1088: final String name = Utilities.getTagName(tag);
1089: final String endTag = "/" + name + ">";
1090: try {
1091: buffer.lockWrite();
1092: } catch (InterruptedException e) {
1093: Log.error(e);
1094: return;
1095: }
1096: try {
1097: // We don't need a compound edit here since all the
1098: // changes are line edits.
1099: editor.fillToCaret();
1100: editor.addUndo(SimpleEdit.LINE_EDIT);
1101: editor.insertStringInternal(endTag);
1102: buffer.modified();
1103: editor.moveCaretToDotCol();
1104: if (buffer.getBooleanProperty(Property.AUTO_INDENT))
1105: editor.indentLine();
1106: } finally {
1107: buffer.unlockWrite();
1108: }
1109: return;
1110: }
1111:
1112: }
1113: // Not electric.
1114: editor.insertNormalChar('/');
1115: }
1116:
1117: // Scan backward for "<!--".
1118: private static Position findCommentStart(Position start) {
1119: Position pos = start.copy();
1120: do {
1121: if (pos.lookingAt(COMMENT_START))
1122: return pos;
1123: } while (pos.prev());
1124: // Not found.
1125: return null;
1126: }
1127:
1128: // Scan forward for "-->".
1129: private static Position findCommentEnd(Position start) {
1130: Position pos = start.copy();
1131: do {
1132: if (pos.lookingAt(COMMENT_END))
1133: return pos;
1134: } while (pos.next());
1135: // Not found.
1136: return null;
1137: }
1138: }
|