/plugins/XML/tags/release-2-6-1/xml/XmlActions.java

# · Java · 1217 lines · 932 code · 151 blank · 134 comment · 200 complexity · 28fbe3b99908b69ae7ed9cee60956973 MD5 · raw file

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