// 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[^>]*)?(>.*\\1\\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 = "" + tagAtCaret.replaceFirst("^<(" + editablePairedTags + ")[\\s>].*", "$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[^>]*)?(>.*\\1\\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