// browseAndLink.bsh - a BeanShell macro script for the jEdit text editor // Version 20-Mar-2005 // This version was completely rewriten, by Pavel Stetina, from previous version // Added features: // - If selection starts just before opening of any tag and tag type is known // and selection contains at least full opening or standalone tag, then first // tag in this selection will be edited. // - If caret is inside of any tag, then this tag will be edited. // - If dot "." is selected, then textual representation of newly created tag // will be same as URL. // - If string containing relative URL is selected, then this file is offered // in the file dialog window, instead of buffer directory. // - Anything else, or nothing, is selected (and caret isn't at any tag), then // this selection will be textual representation of newly created tag. // - Editing of tag will offer the actual link path at the file dialog window // instead of buffer directory. // - Improved searching for anchor labels. Now not only names, but ids are // listed too. // - Searching for document title in HTML files added. // - Multiple selected files can be used, for creation or editing of tags. // Try to insert caret at standalone tag, or opening tag, // or select tag yourself (selection must starts just before // opening or standalone tag). Then you can select multiple files. Result // will be repeatly edited selection with different URLs in first tag. // // Pavel Stetina // E-mail: stetina_at_af_dot_czu_dot_cz // // Version 19-Oct-2004. // This macro opens a file browser in order to insert links with relative // path in an HTML file. It can be used for linking html, pdf, images, shortcut // icons, style sheet files and Javascript files. // - For images, it adds "width" an "height" in pixels. // - If only one html file is selected in the file browser, the anchor labels // of this file are listed in a dialog window (the linked page can be the // active buffer itself). // - If several html and/or pdf files are selected in the file browser and if // a single dot is selected in the buffer, the link text is set to the file // name of each considered files (try it to understand). If more text is // selected in the buffer, it is used as link text for all considered files. // // Copyright (C) 2004 Jean-Francois Magni // E-mail: jfmagni_at_free_dot_fr // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with the jEdit application; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. String htmlExt = "php|html?"; // File extensions for anchor or document title search if (System.getProperty("file.separator").equals("\\")) // File separator for use in regexp { slash = "\\\\"; } else { slash = System.getProperty("file.separator"); } void browseAndLink() { String imgExt = "gif|jpe?g|png"; // Known image file extensions String editablePairedTags = "a|script"; // Known paired tags with href or src atribute String editableTag = editablePairedTags + "|area|img|link"; // Known tags with href or src atribute bufferPath = textArea.buffer.getPath(); bufferDir = bufferPath.substring(0, bufferPath.lastIndexOf(System.getProperty("file.separator")) + 1); lookForAnchorLabels = true; // After clicking on cancel button in label select this will be false // Don't known filename -> error if (bufferPath.toLowerCase().matches(".*untitled- *\\d*")) { Macros.error(view,"You must save the active buffer\n before using this macro."); return; }; // Saving of selected text tagAtCaret = selected = textArea.getSelectedText(); // If selected doesn't contain opening tag at first position, than it check if carret is inside of any tag if (selected == null || (selected != null && !selected.matches("^<([a-zA-Z0-9]+)(\\s[^>]*)?(>.*(?s).*$"))) { tagBeforeCaret = buffer.getText(0, textArea.getCaretPosition()); tagAfterCaret = buffer.getText(textArea.getCaretPosition(), buffer.getLength() - textArea.getCaretPosition()); // If caret is inside of any tag, than this tag will be selected tagBeforeCaret = tagBeforeCaret.substring(tagBeforeCaret.lastIndexOf("<")); if (tagBeforeCaret.indexOf(">") == -1 && tagAfterCaret.indexOf(">") < tagAfterCaret.indexOf("<")) { tagAtCaret = tagBeforeCaret + tagAfterCaret.substring(0, tagAfterCaret.indexOf(">") + 1); // Tag start position tagOffset = textArea.getCaretPosition() - tagBeforeCaret.length(); // If tag is opening section of paired tag if (tagAtCaret.matches("^<(" + editablePairedTags + ")[\\s>].*")) { tagPair = "].*", "$1"); // Searching for matching closing tag tagPairOffset = tagAfterCaret.indexOf(">", tagAfterCaret.indexOf(tagPair)) + 1; if (tagPairOffset > 0) { tagAtCaret += tagAfterCaret.substring(tagAtCaret.length() - tagBeforeCaret.length(), tagPairOffset); } } textArea.resizeSelection(tagOffset, tagOffset + tagAtCaret.length(), 0, false); } // Caret is outside of any tag else { tagAtCaret = null; } } // Path to file if editing tag if (tagAtCaret != null) { // Get tag type tagType = tagAtCaret.replaceFirst("^<([a-zA-Z0-9]+)(\\s[^>]*)?(>.*(?s).*$", "$1").toLowerCase(); if (!tagType.matches(editableTag)) { Macros.error(view, "Selected tag \"" + tagType + "\" can't have href or src attribute."); return; } // Href is set from edited tag href = tagAtCaret.replaceFirst("^<[^>]+\\s(href|src)\\s*=\\s*\"?([^\"\\s>]+)\"?[^>]*>.*$", "$2"); } else { // Href is set from selected text href = selected; if (href == null) { href = ""; } } // Relative path from href attribute String[] dirsBuffer = bufferDir.split(slash); String[] dirsHref = href.split("/"); String[] dirsOutput = new String[20]; // All dirs from buffer are copied to output dirs for (dirP=0; dirP < dirsBuffer.length; dirP++) { dirsOutput[dirP] = dirsBuffer[dirP]; } // Parse all parts of href for (i=0; i < dirsHref.length; i++) { // Go up one directory if (dirsHref[i].equals("..")) { dirsOutput[dirP--] = null; } // Go into specified directory else { dirsOutput[dirP++] = dirsHref[i]; } } // Output merge into string for (href = dirsOutput[0], i=1; i < dirP; i++) { href += "/" + dirsOutput[i]; } // Dialog for selecting files File fileToOpen = new File(href.replaceFirst("^([^#]+)(#.*)?$", "$1")); if (fileToOpen.exists()) { browseFile = fileToOpen.getPath(); } else { browseFile = bufferDir; } VFSFileChooserDialog fileDialog = new VFSFileChooserDialog(view, browseFile, VFSBrowser.OPEN_DIALOG, true); // Files and paths String[] files = fileDialog.getSelectedFiles(); // Operation canceled if (files == null) { return; } String filePath = files[0]; String fileDir = filePath.substring(0, filePath.lastIndexOf(System.getProperty("file.separator"))); // Relative path from buffer directory String[] dirsBuffer = bufferDir.split(slash); String[] dirsFile = fileDir.split(slash); int dirLevels = Math.min(dirsBuffer.length, dirsFile.length); int sameDirs = 0; for (i=0; i < dirLevels; i++) { if (dirsBuffer[i].equals(dirsFile[i])) { sameDirs++; } } // From now href contains new relative URL String href = ""; // Backward in directories tree for (i=sameDirs; i < dirsBuffer.length; i++) { href += "../"; } // Forward in directories tree for (i=sameDirs; i < dirsFile.length; i++) { href += dirsFile[i] + "/"; } // Final output string String output = ""; // Tag(s) editing if (tagAtCaret != null) { // Loop for multiply selected files for (i=0; i < files.length; i++) { // Paste a filename into relative path filename = files[i].substring(fileDir.length() + 1); newTag = tagAtCaret; // Switch type of linked file switch (tagType) { case "img": // Only files with known image file extension will be recalculated if (files[i].matches("^.*" + slash + "?[^." + slash + "]+\\.(" + imgExt + ")$")) { image = new ImageIcon(files[i]); atrWidth = image.getIconWidth(); atrHeight = image.getIconHeight(); newTag = newTag.replaceFirst("(width\\s*=\\s*)\"?[^\" >]+\"?", "$1\"" + atrWidth + "\""); newTag = newTag.replaceFirst("(height\\s*=\\s*)\"?[^\" >]+\"?", "$1\"" + atrHeight + "\""); } default: // Offering of anchor label selection window filename += anchorLabel(files[i]); // Tag with href or src attribute set if (tagAtCaret.matches("^<[a-zA-Z0-9]+(\\s[^>]+)?\\s(src|href)\\s*=\\s*\"?[^\"\\s>]+\"?.*>(?s).*$")) { output += newTag.replaceFirst("((src|href)\\s*=\\s*)\"?[^\"\\s>]+\"?", "$1\"" + href + filename + "\""); } else { output += newTag.replaceFirst("<(" + editableTag + ")", "<$1 href=\"" + href + filename + "\""); } break; } } } // Tag(s) inserting else { // Loop for multiply selected files for (i=0; i < files.length; i++) { // Paste a filename into relative path filename= files[i].substring(fileDir.length() + 1); fileExt = files[i].replaceFirst("^(?:.*" + slash + ")?(?:[^." + slash + "]+)(?:\\.(\\S*))?$", "$1"); // Switch type of linked file switch (fileExt.toLowerCase()) { case "js": output += ""; break; case "css": output += ""; break; case "ico": output += ""; break; case "gif": case "jpg": case "jpeg": case "png": image = new ImageIcon(files[i]); atrWidth = image.getIconWidth(); atrHeight = image.getIconHeight(); // Some text was selected if (selected != null) { // If dot is selected, than text will be same as href if (selected.equals(".")) { atrAlt = filename; } else { atrAlt = selected; } } else { atrAlt = documentTitle(files[i]); } output += "\"""; break; default: // After clicking on cancel button in label select this will be false if (lookForAnchorLabels) { try { filename += anchorLabel(files[i]); } // Cancel button presed catch (Exception e) { // No more looking for anchor labels lookForAnchorLabels = false; } } // Some text was selected if (selected != null) { // If dot is selected, than text will be same as href if (selected.equals(".")) { atrAlt = filename; } else { atrAlt = selected; } } else { atrAlt = documentTitle(files[i]); } output += "" + atrAlt + ""; break; } } } // Macro's output textArea.setSelectedText(output); } // Parse HTML file, offer found labels and return selected label public String anchorLabel(String filePath) throws Exception { // Get filename extension if(filePath.matches("^.*\\.(" + htmlExt + ")$")) { // File reading FileReader fr = new FileReader(filePath); BufferedReader br = new BufferedReader(fr); sourceLine = new String(); //Looking for ids and names String[] labels = new String[100]; labelsP = 0; labels[labelsP++] = filePath.replaceFirst("^(?:.*" + slash + ")?([^." + slash + "]+)(\\.(\\S*))?$", "$1$2"); // Prvni v seznamu je nazev souboru openedTag = false; while ((sourceLine = br.readLine()) != null) { // If multiline tag is opened if (openedTag != false) { // If this line contains closing of previous opened tag if (sourceLine.indexOf(">") != -1) { sourceLine = openedTag + "\n" + sourceLine; // Tag was closed openedTag = false; } else { // Tag is'n closed yet openedTag += "\n" + sourceLine; // Whole line contains body of opened tag, than continue to next line continue; } } openedTagP = sourceLine.lastIndexOf("<"); // Line ends by opening of multiline tag if (sourceLine.lastIndexOf("<") != -1 && sourceLine.lastIndexOf(">") < openedTagP) { openedTag = sourceLine.substring(openedTagP); } sourceLine = sourceLine.replaceAll("[^>]*<([a-zA-Z0-9]+)(?:[^>]*\\s(id|name)=(?:\\s*\"([^\"]+)\"|([^\">\\s]+))[^>]*)?>[^<]*", "<$1 $2=\"$3$4\">\n"); // preLabels = sourceLine.split("\n"); for (i=0; i < preLabels.length; i++) { // All ids and tag names added into list if (preLabels[i] != "" && preLabels[i].matches("^(?:<[aA]\\sname|<[a-zA-Z0-9]+\\sid)=\"[^\"]+\">$")) // Neprazdny nazev pridam do vystupu { labels[labelsP++] = preLabels[i].replaceFirst("^<[a-zA-Z0-9]+\\s(?:id|name)=\"([^\"]+)\">$", "#$1"); } } } fr.close(); // Select window with list of labels label = JOptionPane.showInputDialog(view, "Select an anchor label", "Select an anchor label", JOptionPane.QUESTION_MESSAGE, null, labels, labels[0]); // Option dialog was closed by cancel button if (label == null) { throw new Exception(); } else { // First label is only for showing of filename if (label != labels[0]) { return label.toString(); } } } return ""; } public String documentTitle(String filePath) { // Get filename extension if(filePath.matches("^.*\\.(" + htmlExt + ")$")) { // File reading FileReader fr = new FileReader(filePath); BufferedReader br = new BufferedReader(fr); sourceLine = new String(); //Looking for title openedTag = false; while ((sourceLine = br.readLine()) != null) { // If multiline tag is opened if (openedTag != false) { // If this line contains closing of previous opened tag if (sourceLine.indexOf(">") != -1) { sourceLine = openedTag + "\n" + sourceLine; // Tag was closed openedTag = false; } else { // Tag is'n closed yet openedTag += "\n" + sourceLine; // Whole line contains body of opened tag, than continue to next line continue; } } openedTagP = sourceLine.lastIndexOf("<"); // Line ends by opening of multiline tag if (sourceLine.lastIndexOf("<") != -1 && sourceLine.lastIndexOf(">") < openedTagP) { openedTag = sourceLine.substring(openedTagP); } // If line with tag content found, return content if (sourceLine.matches("^.*]*>([^<]+)]*>.*$")) { return sourceLine.replaceFirst("^.*]*>([^<]+)]*>.*$", "$1"); } } } return ""; } browseAndLink(); // Start macro