PageRenderTime 45ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/bundles/plugins-trunk/XML/xml/XmlActions.java

#
Java | 1202 lines | 989 code | 100 blank | 113 comment | 168 complexity | 80aadaefdb7f8c0b52423116372a0f56 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * XmlActions.java - Action implementations
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 2000, 2003 Slava Pestov
  7. * 1998, 2000 Ollie Rutherfurd
  8. * 2000, 2001 Andre Kaplan
  9. * 1999 Romain Guy
  10. * 2007 Alan Ezust
  11. *
  12. * The XML plugin is licensed under the GNU General Public License, with
  13. * the following exception:
  14. *
  15. * "Permission is granted to link this code with software released under
  16. * the Apache license version 1.1, for example used by the Xerces XML
  17. * parser package."
  18. */
  19. package xml;
  20. //{{{ Imports
  21. import java.awt.Toolkit;
  22. import java.awt.datatransfer.StringSelection;
  23. import java.io.IOException;
  24. import java.io.StreamTokenizer;
  25. import java.io.StringReader;
  26. import java.lang.reflect.InvocationTargetException;
  27. import java.lang.reflect.Method;
  28. import java.util.HashMap;
  29. import java.util.Iterator;
  30. import java.util.List;
  31. import java.util.Map;
  32. import javax.swing.text.Segment;
  33. import javax.swing.tree.DefaultMutableTreeNode;
  34. import javax.swing.tree.TreePath;
  35. import javax.swing.SwingUtilities;
  36. import org.gjt.sp.jedit.BeanShell;
  37. import org.gjt.sp.jedit.Buffer;
  38. import org.gjt.sp.jedit.GUIUtilities;
  39. import org.gjt.sp.jedit.gui.StatusBar;
  40. import org.gjt.sp.jedit.msg.PositionChanging;
  41. import org.gjt.sp.jedit.EditBus;
  42. import org.gjt.sp.jedit.Macros;
  43. import org.gjt.sp.jedit.View;
  44. import org.gjt.sp.jedit.jEdit;
  45. import org.gjt.sp.jedit.Registers;
  46. import org.gjt.sp.jedit.buffer.JEditBuffer;
  47. import org.gjt.sp.jedit.textarea.JEditTextArea;
  48. import org.gjt.sp.jedit.textarea.Selection;
  49. import org.gjt.sp.util.Log;
  50. import sidekick.SideKickParsedData;
  51. import xml.completion.ElementDecl;
  52. import xml.parser.TagParser;
  53. import xml.parser.XmlTag;
  54. import xml.parser.TagParser.Tag;
  55. import xml.parser.TagParser.Attr;
  56. import sidekick.html.parser.html.HtmlDocument;
  57. import sidekick.util.SideKickElement;
  58. import sidekick.util.SideKickAsset;
  59. //}}}
  60. // {{{ class XMLActions
  61. public class XmlActions
  62. {
  63. //{{{ Static variables
  64. private static Segment seg = new Segment();
  65. private static boolean closeCompletion;
  66. private static boolean closeCompletionOpen;
  67. private static boolean standaloneExtraSpace;
  68. static final String brackets = "[](){}";
  69. static final String xmlchars = "<>";
  70. //}}}
  71. //{{{ showEditTagDialog() methods
  72. public static void showEditTagDialog(View view)
  73. {
  74. JEditTextArea textArea = view.getTextArea();
  75. if(XmlPlugin.isDelegated(textArea))
  76. {
  77. view.getToolkit().beep();
  78. return;
  79. }
  80. Buffer buffer = view.getBuffer();
  81. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  82. if(data == null)return;
  83. CharSequence text = buffer.getSegment(0,buffer.getLength());
  84. int caret = textArea.getCaretPosition();
  85. TagParser.Tag tag = TagParser.getTagAtOffset(text,caret);
  86. if(tag == null || tag.type == TagParser.T_END_TAG)
  87. {
  88. view.getToolkit().beep();
  89. return;
  90. }
  91. // use a StringTokenizer to parse the tag - WTF?!?? Why not find data?
  92. HashMap<String, Object> attributes = new HashMap<String, Object>();
  93. String attributeName = null;
  94. boolean seenEquals = false;
  95. boolean empty = false;
  96. /* StringTokenizer does not support disabling or changing
  97. * the escape character, so we have to work around it here. */
  98. char backslashSub = 127;
  99. StreamTokenizer st = new StreamTokenizer(new StringReader(
  100. text.subSequence(tag.start + tag.tag.length() + 1,
  101. tag.end - 1).toString()
  102. .replace('\\',backslashSub)));
  103. st.resetSyntax();
  104. st.wordChars('!',255);
  105. st.whitespaceChars(0,' ');
  106. st.quoteChar('"');
  107. st.quoteChar('\'');
  108. st.ordinaryChar('/');
  109. st.ordinaryChar('=');
  110. Map entityHash = data.entityHash;
  111. //{{{ parse tag
  112. try
  113. {
  114. loop: for(;;)
  115. {
  116. switch(st.nextToken())
  117. {
  118. case StreamTokenizer.TT_EOF:
  119. if(attributeName != null)
  120. {
  121. // in HTML, can have attributes
  122. // without values.
  123. attributes.put(attributeName,
  124. attributeName);
  125. }
  126. break loop;
  127. case '=':
  128. seenEquals = true;
  129. break;
  130. case StreamTokenizer.TT_WORD:
  131. if(attributeName == null)
  132. {
  133. attributeName = (data.html
  134. ? st.sval.toLowerCase()
  135. : st.sval);
  136. break;
  137. }
  138. else
  139. /* fall thru */;
  140. case '"':
  141. case '\'':
  142. if(attributeName != null)
  143. {
  144. if(seenEquals)
  145. {
  146. attributes.put(attributeName,
  147. entitiesToCharacters(
  148. st.sval.replace(backslashSub,'\\'),
  149. entityHash));
  150. seenEquals = false;
  151. }
  152. else if(data.html)
  153. {
  154. attributes.put(attributeName,
  155. Boolean.TRUE);
  156. }
  157. attributeName = null;
  158. }
  159. break;
  160. case '/':
  161. empty = true;
  162. break;
  163. }
  164. }
  165. }
  166. catch(IOException io)
  167. {
  168. Log.log(Log.ERROR, XmlActions.class, "this shouldn't happen:", io);
  169. } //}}}
  170. // must really go inside the open tag to have it returned (hence tag.end+1)
  171. ElementDecl elementDecl = data.getElementDecl(tag.tag,tag.end+1);
  172. if(elementDecl == null)
  173. {
  174. String[] pp = { tag.tag };
  175. GUIUtilities.error(view,"xml-edit-tag.undefined-element",pp);
  176. return;
  177. }
  178. // tag.start+1 because don't want the locally declared namespaces
  179. Map<String,String> namespaces = data.getNamespaceBindings(tag.start+1);
  180. Map<String,String> namespacesToInsert = new HashMap<String,String>();
  181. // grab namespaces declared in this tag
  182. for(String attrname: attributes.keySet()){
  183. if(attrname.startsWith("xmlns:")){
  184. namespacesToInsert.put((String)attributes.get(attrname), attrname.substring(6));
  185. }
  186. }
  187. EditTagDialog dialog = new EditTagDialog(view,tag.tag,
  188. elementDecl,attributes,empty,
  189. elementDecl.completionInfo.entityHash,
  190. data.getSortedIds(),data.html, namespaces, namespacesToInsert);
  191. String newTag = dialog.getNewTag();
  192. if(newTag != null)
  193. {
  194. try
  195. {
  196. buffer.beginCompoundEdit();
  197. buffer.remove(tag.start,tag.end - tag.start);
  198. buffer.insert(tag.start,newTag);
  199. }
  200. finally
  201. {
  202. buffer.endCompoundEdit();
  203. }
  204. }
  205. }
  206. /**
  207. * show EditTagDialog in order to insert open and close tags or edit current tag
  208. * @param view current view
  209. * @param elementName qualified element name to insert
  210. * @param elementDecl element to insert (may not be null)
  211. * @param insideTag Selection of the current start tag (or part of, for instance inf XmlCompletion, what has been already typed)
  212. * @param namespaces namespace bindings in scope for current location (not those declared inside the start tag itself)
  213. * @param namespacesToInsert namespace bindings that will have to be inserted at the end of the start tag
  214. * @param reallyShowEditTagDialog false to disable showing the dialog at all, but insert the start and end tags nonetheless
  215. */
  216. public static void showEditTagDialog(View view, String elementName, ElementDecl elementDecl, Selection insideTag, Map<String, String> namespaces, Map<String, String> namespacesToInsert, boolean reallyShowEditTagDialog)
  217. {
  218. Buffer buffer = view.getBuffer();
  219. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  220. if(data == null)return;
  221. String newTag;
  222. String closingTag;
  223. // compute content to insert
  224. if(!reallyShowEditTagDialog)
  225. {
  226. StringBuilder[] openclose = EditTagDialog.composeTag(data, elementDecl, namespaces, namespacesToInsert, true);
  227. // composeTag doesn't insert the <
  228. openclose[0].insert(0, '<');
  229. newTag = openclose[0].toString();
  230. closingTag = openclose[1].toString();
  231. }
  232. else
  233. {
  234. EditTagDialog dialog = new EditTagDialog(view,elementName,elementDecl,
  235. new HashMap<String, Object>(),elementDecl.empty,
  236. elementDecl.completionInfo.entityHash,
  237. data.getSortedIds(),data.html, namespaces, namespacesToInsert);
  238. newTag = dialog.getNewTag();
  239. if(dialog.isEmpty())
  240. closingTag = "";
  241. else
  242. closingTag = "</" + elementName + ">";
  243. }
  244. // really insert now
  245. // TODO: there is no support for Recorder, unlike in XmlCompletion
  246. if(newTag != null)
  247. {
  248. JEditTextArea textArea = view.getTextArea();
  249. if (insideTag != null) textArea.setSelectedText(insideTag, "");
  250. Selection[] selection = textArea.getSelection();
  251. if(selection.length > 0)
  252. {
  253. try
  254. {
  255. buffer.beginCompoundEdit();
  256. for(int i = 0; i < selection.length; i++)
  257. {
  258. buffer.insert(selection[i].getStart(),
  259. newTag);
  260. buffer.insert(selection[i].getEnd(),
  261. closingTag);
  262. }
  263. }
  264. finally
  265. {
  266. buffer.endCompoundEdit();
  267. }
  268. }
  269. else
  270. {
  271. textArea.setSelectedText(newTag);
  272. int caret = textArea.getCaretPosition();
  273. textArea.setSelectedText(closingTag);
  274. textArea.setCaretPosition(caret);
  275. }
  276. textArea.selectNone();
  277. textArea.requestFocus();
  278. }
  279. } //}}}
  280. //{{{ insertClosingTag() method
  281. public static void insertClosingTag(View view)
  282. {
  283. JEditTextArea textArea = view.getTextArea();
  284. Buffer buffer = view.getBuffer();
  285. if(XmlPlugin.isDelegated(textArea) || !buffer.isEditable())
  286. {
  287. view.getToolkit().beep();
  288. return;
  289. }
  290. XmlParsedData data = XmlParsedData.getParsedData(view,false);
  291. TagParser.Tag tag = TagParser.findLastOpenTag(
  292. buffer.getText(0,textArea.getCaretPosition()),
  293. textArea.getCaretPosition(),data);
  294. if(tag != null)
  295. textArea.setSelectedText("</" + tag.tag + ">");
  296. else
  297. {
  298. view.getToolkit().beep();
  299. }
  300. } //}}}
  301. // {{{ splitTag() method
  302. /**
  303. * Splits tag at caret, so that attributes are on separate lines.
  304. */
  305. public static void splitTag(Tag tag, JEditTextArea textArea, CharSequence text) {
  306. View view = textArea.getView();
  307. textArea.setSelection(new Selection.Range(tag.start, tag.end));
  308. XmlParsedData data = XmlParsedData.getParsedData(view,true);
  309. Selection[] s = textArea.getSelection();
  310. if (s.length != 1) return;
  311. Selection sel = s[0];
  312. if (sel.getEnd() - sel.getStart() < 2) return;
  313. int line = textArea.getLineOfOffset(tag.start);
  314. int lineStartOffset = textArea.getLineStartOffset(line);
  315. int indentChars = 2 + sel.getStart() - lineStartOffset;
  316. StringBuffer indent = new StringBuffer("\n");
  317. for (int i=indentChars; i>=0; --i) {
  318. indent.append(" ");
  319. }
  320. TreePath path = data.getTreePathForPosition(textArea.getCaretPosition());
  321. int count = path.getPathCount();
  322. DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPathComponent(count-1);
  323. StringBuffer result = new StringBuffer();
  324. Object user_object = node.getUserObject();
  325. if (user_object instanceof XmlTag) {
  326. result.append('<');
  327. result.append(tag.tag);
  328. List<Attr> attrs = TagParser.getAttrs(text,tag);
  329. count = attrs.size();
  330. if(count>0)result.append(' ');
  331. for (int i=0; i<count; ++i) {
  332. Attr a = attrs.get(i);
  333. result.append(a.name).append(" = ").append(a.val);
  334. if (i < count ) result.append(indent.toString());
  335. }
  336. result.append('>');
  337. }
  338. else if (user_object instanceof SideKickAsset) {
  339. SideKickElement element = ((SideKickAsset)user_object).getElement();
  340. if (element instanceof HtmlDocument.Tag) {
  341. HtmlDocument.Tag htmlTag = (HtmlDocument.Tag)element;
  342. result.append(htmlTag.tagStart);
  343. result.append(htmlTag.tagName);
  344. List attrs = ((HtmlDocument.Tag)element).attributeList.attributes;
  345. if(attrs.size()>0)result.append(" ");
  346. for (Iterator it = attrs.iterator(); it.hasNext(); ) {
  347. HtmlDocument.Attribute attr = (HtmlDocument.Attribute)it.next();
  348. result.append(attr.name);
  349. if (attr.hasValue) {
  350. String value = attr.value;
  351. if (!value.startsWith("\"")) {
  352. value = "\"" + value;
  353. }
  354. if (!value.endsWith("\"")) {
  355. value += "\"";
  356. }
  357. result.append(" = ").append(value);
  358. }
  359. if (it.hasNext()) {
  360. result.append(indent.toString());
  361. }
  362. }
  363. result.append(htmlTag.tagEnd);
  364. }
  365. }
  366. else {
  367. return;
  368. }
  369. textArea.replaceSelection(result.toString());
  370. }// }}}
  371. // {{{ join() method
  372. /**
  373. * If inside a HTML or XML, join attributes and tagname all on one line.
  374. * Otherwise do nothing.
  375. */
  376. static public void join (View view) {
  377. JEditTextArea textArea = view.getTextArea();
  378. Buffer buffer = view.getBuffer();
  379. int pos = textArea.getCaretPosition();
  380. CharSequence text = buffer.getSegment(0,buffer.getLength());
  381. Tag tag = TagParser.getTagAtOffset(text, pos);
  382. if (tag == null) return; // we're not in a tag;
  383. // select it
  384. textArea.setSelection(new Selection.Range(tag.start, tag.end));
  385. XmlParsedData data = XmlParsedData.getParsedData(view,true);
  386. if(data==null)return;
  387. Selection[] s = textArea.getSelection();
  388. if (s.length != 1) return;
  389. Selection sel = s[0];
  390. if (sel.getEnd() - sel.getStart() < 2) return;
  391. int line = textArea.getLineOfOffset(tag.start);
  392. TreePath path = data.getTreePathForPosition(textArea.getCaretPosition());
  393. int count = path.getPathCount();
  394. DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPathComponent(count-1);
  395. StringBuffer result = new StringBuffer();
  396. Object user_object = node.getUserObject();
  397. if (user_object instanceof XmlTag) {
  398. result.append('<');
  399. result.append(tag.tag);
  400. List<Attr> attrs = TagParser.getAttrs(text,tag);
  401. for(Attr a: attrs)
  402. {
  403. result.append(' ').append(a.name).append(" = ").append(a.val);
  404. }
  405. if(tag.type == TagParser.T_STANDALONE_TAG)
  406. {
  407. result.append('/');
  408. }
  409. result.append('>');
  410. }
  411. else if (user_object instanceof SideKickAsset) {
  412. SideKickElement element = ((SideKickAsset)user_object).getElement();
  413. if (element instanceof HtmlDocument.Tag) {
  414. HtmlDocument.Tag htmlTag = (HtmlDocument.Tag)element;
  415. result.append(htmlTag.tagStart);
  416. result.append(htmlTag.tagName).append(" ");
  417. List attrs = ((HtmlDocument.Tag)element).attributeList.attributes;
  418. for (Iterator it = attrs.iterator(); it.hasNext(); ) {
  419. HtmlDocument.Attribute attr = (HtmlDocument.Attribute)it.next();
  420. result.append(attr.name);
  421. if (attr.hasValue) {
  422. String value = attr.value;
  423. if (!value.startsWith("\"")) {
  424. value = "\"" + value;
  425. }
  426. if (!value.endsWith("\"")) {
  427. value += "\"";
  428. }
  429. result.append(" = ").append(value);
  430. }
  431. }
  432. result.append(htmlTag.tagEnd.replaceAll("\\s", ""));
  433. }
  434. }
  435. else {
  436. return;
  437. }
  438. textArea.replaceSelection(result.toString());
  439. }// }}}
  440. //{{{ split() method
  441. /**
  442. * If inside a tag, calls splitTagAtCaret.
  443. *
  444. * If the DTD allows this tag to be split, split at the cursor.
  445. *
  446. * Note that this can be used to do a kind of 'fast-editing', eg when
  447. * editing an HTML &lt;p&gt; this will insert an end tag (if necessary)
  448. * and then place the cursor inside a new &lt;p&gt;.
  449. *
  450. * TODO: Syntax Checking
  451. */
  452. public static void split(View view)
  453. {
  454. JEditTextArea textArea = view.getTextArea();
  455. Buffer buffer = view.getBuffer();
  456. int pos = textArea.getCaretPosition();
  457. CharSequence text = buffer.getSegment(0,buffer.getLength());
  458. Tag t = TagParser.getTagAtOffset(text, pos);
  459. if (t != null && t.end != pos) { // getTagAtOffset will return a tag if you are just after it
  460. splitTag(t, textArea, text);
  461. return;
  462. }
  463. if(XmlPlugin.isDelegated(textArea) || !buffer.isEditable())
  464. {
  465. view.getToolkit().beep();
  466. return;
  467. }
  468. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  469. if(data == null)return;
  470. TagParser.Tag tag = TagParser.findLastOpenTag(
  471. buffer.getText(0,textArea.getCaretPosition()),
  472. textArea.getCaretPosition(),data);
  473. if(tag != null)
  474. {
  475. Segment wsBefore = new Segment();
  476. pos = getPrevNonWhitespaceChar( buffer, tag.start - 1 ) + 1;
  477. buffer.getText( pos, tag.start-pos, wsBefore );
  478. //System.err.println( "wsBefore: [" + wsBefore + "]" );
  479. Segment wsAfter = new Segment();
  480. pos = getNextNonWhitespaceChar( buffer, tag.end );
  481. //Need to do this otherwise the ws in empty tags
  482. //just gets bigger and bigger and bigger...
  483. pos = Math.min( pos, textArea.getCaretPosition() );
  484. buffer.getText( tag.end, pos - tag.end, wsAfter );
  485. //System.err.println( "wsAfter: [" + wsAfter + "]" );
  486. int lineStart = buffer.getLineStartOffset(
  487. buffer.getLineOfOffset( tag.start ) );
  488. String tagIndent = buffer.getText( lineStart, tag.start-lineStart );
  489. //Note that the number of blank lines BEFORE the end tag will
  490. //be the number AFTER the start tag, for symmetry's sake.
  491. int crBeforeEndTag = countNewLines( wsAfter );
  492. int crAfterEndTag = countNewLines( wsBefore );
  493. StringBuffer insert = new StringBuffer();
  494. if ( crBeforeEndTag>0 ) {
  495. for ( int i=0; i<crBeforeEndTag; i++ ) {
  496. insert.append( "\n" );
  497. }
  498. insert.append(tagIndent);
  499. }
  500. insert.append("</" + tag.tag + ">");
  501. insert.append( wsBefore );
  502. insert.append("<" + tag.tag + ">");
  503. insert.append( wsAfter );
  504. //Move the insertion point to here
  505. textArea.setSelectedText(insert.toString());
  506. }
  507. }
  508. //}}}
  509. //{{{ removeTags() method
  510. public static void removeTags(Buffer buffer)
  511. {
  512. if(!buffer.isEditable())
  513. {
  514. Toolkit.getDefaultToolkit().beep();
  515. return;
  516. }
  517. int off = 0;
  518. int len = buffer.getLength();
  519. long startTime = System.currentTimeMillis();
  520. int total = 0;
  521. try
  522. {
  523. buffer.beginCompoundEdit();
  524. CharSequence text = buffer.getSegment(off,len);
  525. for (int i = TagParser.indexOf(text, '<',0);
  526. i != -1; i = TagParser.indexOf(text,'<', ++i))
  527. {
  528. TagParser.Tag tag = TagParser.getTagAtOffset(text,i + 1);
  529. if (tag == null)
  530. continue;
  531. else
  532. {
  533. int length = tag.end - tag.start;
  534. buffer.remove(tag.start - total,length);
  535. total += length;
  536. }
  537. }
  538. }
  539. finally
  540. {
  541. buffer.endCompoundEdit();
  542. }
  543. long endTime = System.currentTimeMillis();
  544. Log.log(Log.DEBUG, XmlActions.class,
  545. "removeTags time: " + (endTime - startTime) + " ms");
  546. } //}}}
  547. //{{{ matchTag() method
  548. public static void matchTag(JEditTextArea textArea) {
  549. int caretPos = textArea.getCaretPosition();
  550. // Check if I am near one of the regular brackets
  551. for (int i=caretPos-1; i<caretPos+3; ++i) try
  552. {
  553. String s = textArea.getText(i,1);
  554. if (brackets.indexOf(s) > -1)
  555. {
  556. textArea.goToMatchingBracket();
  557. return;
  558. }
  559. }
  560. catch (ArrayIndexOutOfBoundsException aiobe) {}
  561. xmlMatchTag(textArea);
  562. }
  563. // }}}
  564. //{{{ xmlMatchTag() method
  565. public static void xmlMatchTag(JEditTextArea textArea)
  566. {
  567. CharSequence text = textArea.getBuffer().getSegment(0,textArea.getBufferLength());
  568. int caret = textArea.getCaretPosition();
  569. // De-Select previous selection
  570. // textArea.select(caret, caret);
  571. textArea.setSelection(new Selection.Range(caret, caret));
  572. // Move cursor inside tag, to help with matching
  573. try { if (text.charAt(caret) == '<')
  574. textArea.goToNextCharacter(false);
  575. } catch (Exception e ) {}
  576. TagParser.Tag tag = TagParser.getTagAtOffset(text,textArea.getCaretPosition());
  577. if (tag != null)
  578. {
  579. TagParser.Tag matchingTag = TagParser.getMatchingTag(text, tag);
  580. if (matchingTag != null)
  581. {
  582. EditBus.send(new PositionChanging(textArea));
  583. textArea.setSelection(new Selection.Range(
  584. matchingTag.start, matchingTag.end
  585. ));
  586. textArea.moveCaretPosition(matchingTag.end-1);
  587. }
  588. else
  589. textArea.getToolkit().beep();
  590. }
  591. } //}}}
  592. //{{{ selectElement() method
  593. /**
  594. * Selects whole element, can be called repeatedly to select
  595. * parent element of selected element. If no element found, calls
  596. * "Select Code Block" action -- analogy to the
  597. * "Select Matching Tag or Bracket" action
  598. */
  599. public static void selectElement(JEditTextArea textArea)
  600. {
  601. final int step = 2;
  602. CharSequence text = textArea.getBuffer().getSegment(0, textArea.getBufferLength());
  603. boolean isSel = textArea.getSelectionCount() == 1;
  604. int caret, pos;
  605. if (isSel)
  606. caret = pos = textArea.getSelection(0).getEnd();
  607. else
  608. caret = pos = textArea.getCaretPosition();
  609. while (pos >= 0)
  610. {
  611. TagParser.Tag tag = TagParser.getTagAtOffset(text, pos);
  612. if (tag != null)
  613. {
  614. TagParser.Tag matchingTag = TagParser.getMatchingTag(text, tag);
  615. if (matchingTag != null
  616. && ((tag.type == TagParser.T_START_TAG && matchingTag.end >= caret)
  617. || (!isSel && tag.type == TagParser.T_END_TAG && tag.end >= caret)))
  618. {
  619. if (tag.start < matchingTag.end)
  620. {
  621. textArea.setSelection(
  622. new Selection.Range(tag.start, matchingTag.end));
  623. textArea.moveCaretPosition(
  624. matchingTag.end);
  625. }
  626. else
  627. {
  628. textArea.setSelection(
  629. new Selection.Range(matchingTag.start,tag.end));
  630. textArea.moveCaretPosition(
  631. matchingTag.start);
  632. }
  633. break;
  634. }
  635. else if (!isSel && tag.type == TagParser.T_STANDALONE_TAG)
  636. {
  637. textArea.setSelection(
  638. new Selection.Range(tag.start, tag.end));
  639. textArea.moveCaretPosition(tag.end);
  640. break;
  641. }
  642. else
  643. {
  644. // No tag found - skip as much as posible
  645. // NOTE: checking if matchingTag.start < tag.start
  646. // shouldn't be necesary, but TagParser.getMatchingTag method
  647. // sometimes finds matching tag only for start,
  648. // tag, e.g.: "<x> => </x>"
  649. pos = (matchingTag != null && matchingTag.start < tag.start)
  650. ? matchingTag.start
  651. : tag.start;
  652. }
  653. }
  654. pos -= step;
  655. }
  656. if (pos <= 0) {
  657. textArea.selectBlock();
  658. }
  659. } //}}}
  660. //{{{ selectTag() method
  661. /**
  662. * Selects tag at caret. Also returns it. Returns null if there is no tag.
  663. * */
  664. public static Tag selectTag(JEditTextArea textArea) {
  665. CharSequence text = textArea.getBuffer().getSegment(0, textArea.getBufferLength());
  666. int pos = textArea.getCaretPosition();
  667. Tag t = TagParser.getTagAtOffset(text, pos);
  668. if (t == null) return null;
  669. textArea.setSelection(new Selection.Range(t.start, t.end));
  670. return t;
  671. } // }}}
  672. //{{{ selectBetweenTags() method
  673. /**
  674. * Selects content of an element, can be called repeatedly
  675. */
  676. public static void selectBetweenTags(JEditTextArea textArea)
  677. {
  678. final int step = 2;
  679. CharSequence text = textArea.getBuffer().getSegment(0,textArea.getBufferLength());
  680. boolean isSel = textArea.getSelectionCount() == 1;
  681. int caret, pos;
  682. if (isSel)
  683. caret = pos = textArea.getSelection(0).getEnd();
  684. else
  685. caret = pos = textArea.getCaretPosition();
  686. while (pos >= 0)
  687. {
  688. TagParser.Tag tag = TagParser.getTagAtOffset(text, pos);
  689. if (tag != null)
  690. {
  691. TagParser.Tag matchingTag = TagParser.getMatchingTag(text, tag);
  692. if (tag.type == TagParser.T_START_TAG)
  693. {
  694. if (matchingTag != null
  695. && (matchingTag.start > caret
  696. || (!isSel && matchingTag.start == caret)))
  697. {
  698. if (tag.start < matchingTag.end)
  699. {
  700. textArea.setSelection(
  701. new Selection.Range(tag.end, matchingTag.start));
  702. textArea.moveCaretPosition(
  703. matchingTag.start);
  704. }
  705. else
  706. {
  707. textArea.setSelection(
  708. new Selection.Range(matchingTag.end,tag.start));
  709. textArea.moveCaretPosition(
  710. matchingTag.end);
  711. }
  712. break;
  713. }
  714. else
  715. {
  716. pos = tag.start - step;
  717. continue;
  718. }
  719. }
  720. else
  721. {
  722. // No tag found - skip as much as posible
  723. // NOTE: checking if matchingTag.start < tag.start
  724. // shouldn't be necesary, but TagParser.getMatchingTag method
  725. // sometimes finds matching tag only for start,
  726. // tag, e.g.: "<x> => </x>"
  727. pos = (matchingTag != null && matchingTag.start < tag.start)
  728. ? matchingTag.start
  729. : tag.start;
  730. }
  731. }
  732. pos -= step;
  733. }
  734. if (pos <= 0) {
  735. textArea.getToolkit().beep();
  736. }
  737. }
  738. // }}}
  739. //{{{ insertClosingTagKeyTyped() method
  740. public static void insertClosingTagKeyTyped(View view)
  741. {
  742. JEditTextArea textArea = view.getTextArea();
  743. Macros.Recorder recorder = view.getMacroRecorder();
  744. textArea.userInput('>');
  745. Buffer buffer = view.getBuffer();
  746. if(XmlPlugin.isDelegated(textArea) || !buffer.isEditable()
  747. || !closeCompletionOpen)
  748. return;
  749. XmlParsedData data = XmlParsedData.getParsedData(view,false);
  750. if(data==null)return;
  751. int caret = textArea.getCaretPosition();
  752. CharSequence text = buffer.getSegment(0,caret);
  753. TagParser.Tag tag = TagParser.getTagAtOffset(text,caret - 1);
  754. if(tag == null)
  755. return;
  756. ElementDecl decl = data.getElementDecl(tag.tag,tag.start+1);
  757. if(tag.type == TagParser.T_STANDALONE_TAG
  758. || (decl != null && decl.empty))
  759. return;
  760. tag = TagParser.findLastOpenTag(text,caret,data);
  761. if(tag != null)
  762. {
  763. String insert = "</" + tag.tag + ">";
  764. if(recorder != null)
  765. recorder.recordInput(insert,false);
  766. textArea.setSelectedText(insert);
  767. String code = "textArea.setCaretPosition("
  768. + "textArea.getCaretPosition() - "
  769. + insert.length() + ");";
  770. if(recorder != null)
  771. recorder.record(code);
  772. BeanShell.eval(view,BeanShell.getNameSpace(),code);
  773. }
  774. } //}}}
  775. //{{{ completeClosingTag() method
  776. public static void completeClosingTag(View view, boolean insertSlash)
  777. {
  778. JEditTextArea textArea = view.getTextArea();
  779. Macros.Recorder recorder = view.getMacroRecorder();
  780. if(insertSlash)
  781. {
  782. if(recorder != null)
  783. recorder.recordInput(1,'/',false);
  784. textArea.userInput('/');
  785. }
  786. JEditBuffer buffer = textArea.getBuffer();
  787. if(XmlPlugin.isDelegated(textArea))
  788. return;
  789. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  790. if(data==null)return;
  791. if(!buffer.isEditable() || !closeCompletion)
  792. {
  793. return;
  794. }
  795. int caret = textArea.getCaretPosition();
  796. if(caret == 1)
  797. return;
  798. CharSequence text = buffer.getSegment(0,buffer.getLength());
  799. if(text.charAt(caret - 2) != '<')
  800. return;
  801. // check if caret is inside a tag
  802. if(TagParser.getTagAtOffset(text,caret) != null)
  803. return;
  804. TagParser.Tag tag = TagParser.findLastOpenTag(text,caret - 2,data);
  805. if(tag != null)
  806. {
  807. String insert = tag.tag + ">";
  808. if(recorder != null)
  809. recorder.recordInput(insert,false);
  810. textArea.setSelectedText(insert);
  811. }
  812. } //}}}
  813. //{{{ charactersToEntities() methods
  814. public static String charactersToEntities(String s, Map hash)
  815. {
  816. final String specialChars = "<>&\"\\";
  817. StringBuffer buf = new StringBuffer();
  818. for(int i = 0; i < s.length(); i++)
  819. {
  820. char ch = s.charAt(i);
  821. if(ch >= 0x7f || specialChars.indexOf(ch) > -1)
  822. {
  823. Character c = new Character(ch);
  824. String entity = (String)hash.get(c);
  825. if(entity != null)
  826. {
  827. buf.append('&');
  828. buf.append(entity);
  829. buf.append(';');
  830. continue;
  831. }
  832. }
  833. buf.append(ch);
  834. }
  835. return buf.toString();
  836. }
  837. public static void charactersToEntities(View view)
  838. {
  839. Buffer buffer = view.getBuffer();
  840. JEditTextArea textArea = view.getTextArea();
  841. if(XmlPlugin.isDelegated(textArea) || !buffer.isEditable())
  842. {
  843. view.getToolkit().beep();
  844. return;
  845. }
  846. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  847. if(data == null)return;
  848. Map entityHash = data.entityHash;
  849. Selection[] selection = textArea.getSelection();
  850. for(int i = 0; i < selection.length; i++)
  851. {
  852. String old = textArea.getSelectedText(selection[i]);
  853. textArea.setSelectedText(selection[i], charactersToEntities(old, entityHash));
  854. }
  855. } //}}}
  856. //{{{ entitiesToCharacters() methods
  857. public static String entitiesToCharacters(String s, Map hash)
  858. {
  859. StringBuffer buf = new StringBuffer();
  860. for(int i = 0; i < s.length(); i++)
  861. {
  862. char ch = s.charAt(i);
  863. if(ch == '&')
  864. {
  865. int index = s.indexOf(';',i);
  866. if(index != -1)
  867. {
  868. String entityName = s.substring(i + 1,index);
  869. Character c = (Character)hash.get(entityName);
  870. if(c != null)
  871. {
  872. buf.append(c.charValue());
  873. i = index;
  874. continue;
  875. }
  876. }
  877. }
  878. buf.append(ch);
  879. }
  880. return buf.toString();
  881. }
  882. public static void entitiesToCharacters(View view)
  883. {
  884. Buffer buffer = view.getBuffer();
  885. JEditTextArea textArea = view.getTextArea();
  886. if(XmlPlugin.isDelegated(textArea) || !buffer.isEditable())
  887. {
  888. view.getToolkit().beep();
  889. return;
  890. }
  891. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  892. if(data == null)return;
  893. Map entityHash = data.entityHash;
  894. Selection[] selection = textArea.getSelection();
  895. for(int i = 0; i < selection.length; i++)
  896. {
  897. textArea.setSelectedText(selection[i],
  898. entitiesToCharacters(textArea.getSelectedText(
  899. selection[i]),entityHash));
  900. }
  901. } //}}}
  902. //{{{ getStandaloneEnd() method
  903. public static String getStandaloneEnd()
  904. {
  905. return (standaloneExtraSpace ? " />" : "/>");
  906. } //}}}
  907. //{{{ generateDTD() method
  908. public static void generateDTD(View view)
  909. {
  910. JEditTextArea textArea = view.getTextArea();
  911. Buffer buffer = view.getBuffer();
  912. String text = buffer.getText(0,buffer.getLength());
  913. String encoding = buffer.getStringProperty(Buffer.ENCODING);
  914. // String declaration = "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n";
  915. String dtd = DTDGenerator.write(view, text);
  916. StatusBar status = view.getStatus();
  917. if (dtd.trim().equals(""))
  918. status.setMessageAndClear("Document produced an empty DTD");
  919. else
  920. {
  921. Buffer newbuffer = jEdit.newFile(view);
  922. newbuffer.setMode("sgml");
  923. newbuffer.setStringProperty(Buffer.ENCODING, encoding);
  924. // newbuffer.insert(0, declaration + dtd);
  925. newbuffer.insert(0, dtd);
  926. status.updateBufferStatus();
  927. }
  928. }
  929. //}}}
  930. //{{{ openSchema() method
  931. public static void openSchema(View view)
  932. {
  933. JEditTextArea textArea = view.getTextArea();
  934. Buffer buffer = view.getBuffer();
  935. String schemaURL = buffer.getStringProperty(SchemaMappingManager.BUFFER_SCHEMA_PROP);
  936. if(schemaURL == null)
  937. {
  938. schemaURL = buffer.getStringProperty(SchemaMappingManager.BUFFER_AUTO_SCHEMA_PROP);
  939. }
  940. if(schemaURL != null){
  941. Buffer newbuffer = jEdit.openFile(view,schemaURL);
  942. }
  943. }
  944. //}}}
  945. //{{{ copyXPath() method
  946. public static void copyXPath(View view)
  947. {
  948. XmlParsedData data = XmlParsedData.getParsedData(view, true);
  949. if(data == null)
  950. {
  951. view.getToolkit().beep();
  952. return;
  953. }
  954. JEditTextArea textArea = view.getTextArea();
  955. int pos = textArea.getCaretPosition();
  956. String xpath = data.getXPathForPosition(pos);
  957. if(xpath!=null)
  958. {
  959. Registers.getRegister('$').setTransferable(new StringSelection(xpath));
  960. }
  961. }
  962. //}}}
  963. // {{{ non-public methods
  964. //{{{ propertiesChanged() method
  965. static void propertiesChanged()
  966. {
  967. closeCompletion = jEdit.getBooleanProperty(
  968. "xml.close-complete");
  969. if(closeCompletionOpen != jEdit.getBooleanProperty(
  970. "xml.close-complete-open"))
  971. {
  972. closeCompletionOpen = !closeCompletionOpen;
  973. SwingUtilities.invokeLater(new Runnable(){
  974. public void run(){
  975. /* with the jEdit 5 Keymap API, it's no more possible
  976. * to define a shortcut with action-name.shortcut=X in the XML.props
  977. * => so set it programatically !
  978. * */
  979. try {
  980. // test the presence of the jEdit 5 KeyMap API
  981. Class.forName("org.jedit.keymap.Keymap");
  982. // OK, the KeyMap API exists
  983. String shortcut = ">";
  984. // shortcut will be undefined when closeCompletionOpen is not active
  985. String actionName = closeCompletionOpen? "xml-insert-closing-tag" : null;
  986. Class c = Class.forName("xml.JEdit5Support");
  987. Method m = c.getDeclaredMethod("setShortcut", new Class[]{String.class,String.class});
  988. m.invoke(null, actionName, shortcut);
  989. } catch (ClassNotFoundException e) {
  990. // NOPMD: don't log an error when org.jedit.keymap.Keymap is not found
  991. } catch (Exception e) {
  992. Log.log(Log.WARNING, XmlActions.class, "error setting shortcut to implement 'insert closing tag when opening tag is typed'",e);
  993. }
  994. }});
  995. }
  996. standaloneExtraSpace = jEdit.getBooleanProperty(
  997. "xml.standalone-extra-space");
  998. } //}}}
  999. //{{{ getPrevNonWhitespaceChar() method
  1000. /**
  1001. * Find the offset of the previous non whitespace character.
  1002. */
  1003. private static int getPrevNonWhitespaceChar( Buffer buf, int start )
  1004. {
  1005. //It might be more efficient if there were a getCharAt() method on the buffer?
  1006. //This is trying to conserve memory by not creating strings all the time
  1007. Segment seg = new Segment( new char[1], 0, 1 );
  1008. int pos = start;
  1009. while ( pos>0 )
  1010. {
  1011. buf.getText( pos, 1, seg );
  1012. if ( ! Character.isWhitespace( seg.first() ) )
  1013. break;
  1014. pos--;
  1015. }
  1016. return pos;
  1017. }
  1018. //}}}
  1019. //{{{ getNextNonWhitespaceChar() method
  1020. /**
  1021. * Find the offset of the next non-whitespace character.
  1022. */
  1023. private static int getNextNonWhitespaceChar( Buffer buf, int start )
  1024. {
  1025. //It might be more efficient if there were a getCharAt() method on the buffer?
  1026. //This is trying to conserve memory by not creating strings all the time
  1027. Segment seg = new Segment( new char[1], 0, 1 );
  1028. int pos = start;
  1029. while ( pos < buf.getLength() )
  1030. {
  1031. buf.getText( pos, 1, seg );
  1032. //System.err.println( "NNWS Testing: " + seg.first() + " at " + pos );
  1033. if ( ! Character.isWhitespace( seg.first() ) )
  1034. break;
  1035. pos++;
  1036. }
  1037. return pos;
  1038. }
  1039. //}}}
  1040. //{{{ countNewLines() method
  1041. /**
  1042. * Count the number of newlines in the given segment.
  1043. */
  1044. private static int countNewLines( Segment seg )
  1045. {
  1046. //It might help if there were a getCharAt() method on the buffer
  1047. //or the buffer itself implemented CharaterIterator?
  1048. //This is trying to conserve memory by not creating strings all the time
  1049. int count = 0;
  1050. for ( int pos = seg.getBeginIndex(); pos<seg.getEndIndex(); pos++ ) {
  1051. if ( seg.array[pos] == '\n' ) {
  1052. count++;
  1053. }
  1054. }
  1055. return count;
  1056. }
  1057. //}}}
  1058. //}}}
  1059. } // }}}