org.fife.ui.rtextarea.Macro Maven / Gradle / Ivy
/*
* 09/16/2004
*
* Macro.java - A macro as recorded/played back by an RTextArea.
*
* This library is distributed under a modified BSD license. See the included
* LICENSE file for details.
*/
package org.fife.ui.rtextarea;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.fife.io.UnicodeReader;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* A macro as recorded/played back by an {@link RTextArea}.
*
* Macro
s are static; when a Macro is loaded, it can be run by any
* instance of RTextArea
in the application. To activate and play
* back a macro, use the following methods:
*
*
* - {@link RTextArea#loadMacro(Macro)}
*
- {@link RTextArea#playbackLastMacro()}
*
*
* To record and save a new macro, you'd use the following methods:
*
*
* - {@link RTextArea#beginRecordingMacro()} (this discards the previous
* "current" macro, if any)
*
- {@link RTextArea#endRecordingMacro()} (at this point, you could call
*
playbackLastMacro()
to play this macro immediately if
* desired)
* - {@link RTextArea#getCurrentMacro()}.{@link #saveToFile(File)}
*
*
* As Macro
s save themselves as XML files, a common technique is
* to save all macros in files named "{@link #getName()}.xml
", and
* place them all in a common directory.
*
* @author Robert Futrell
* @version 0.1
*/
public class Macro {
private String name;
private ArrayList macroRecords;
private static final String ROOT_ELEMENT = "macro";
private static final String MACRO_NAME = "macroName";
private static final String ACTION = "action";
private static final String ID = "id";
private static final String UNTITLED_MACRO_NAME = "";
private static final String FILE_ENCODING = "UTF-8";
/**
* Constructor.
*/
public Macro() {
this(UNTITLED_MACRO_NAME);
}
/**
* Loads a macro from a file on disk.
*
* @param file The file from which to load the macro.
* @throws IOException If the file does not exist or an I/O exception occurs
* while reading the file.
* @see #saveToFile(String)
* @see #saveToFile(File)
*/
public Macro(File file) throws IOException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
try {
db = dbf.newDocumentBuilder();
//InputSource is = new InputSource(new FileReader(file));
InputSource is = new InputSource(new UnicodeReader(
new FileInputStream(file), FILE_ENCODING));
is.setEncoding(FILE_ENCODING);
doc = db.parse(is);//db.parse(file);
} catch (Exception e) {
e.printStackTrace();
String desc = e.getMessage();
if (desc==null) {
desc = e.toString();
}
throw new IOException("Error parsing XML: " + desc);
}
macroRecords = new ArrayList<>();
// Traverse the XML tree.
boolean parsedOK = initializeFromXMLFile(doc.getDocumentElement());
if (!parsedOK) {
name = null;
macroRecords.clear();
macroRecords = null;
throw new IOException("Error parsing XML!");
}
}
/**
* Constructor.
*
* @param name The name of the macro.
*/
public Macro(String name) {
this(name, null);
}
/**
* Constructor.
*
* @param name The name of the macro.
* @param records The initial records of the macro.
*/
public Macro(String name, List records) {
this.name = name;
if (records!=null) {
macroRecords = new ArrayList<>(records.size());
macroRecords.addAll(records);
}
else {
macroRecords = new ArrayList<>(10);
}
}
/**
* Adds a macro record to this macro.
*
* @param record The record to add. If null
, nothing happens.
* @see #getMacroRecords
*/
public void addMacroRecord(MacroRecord record) {
if (record!=null) {
macroRecords.add(record);
}
}
/**
* Returns the macro records that make up this macro.
*
* @return The macro records.
* @see #addMacroRecord
*/
public List getMacroRecords() {
return macroRecords;
}
/**
* Returns the name of this macro. A macro's name is simply something to
* identify it with in a UI; it has nothing to do with the name of the file
* to save the macro to.
*
* @return The macro's name.
* @see #setName(String)
*/
public String getName() {
return name;
}
/**
* Used in parsing an XML document containing a macro. This method
* initializes this macro with the data contained in the passed-in node.
*
* @param root The root node of the parsed XML document.
* @return true
if the macro initialization went okay;
* false
if an error occurred.
*/
private boolean initializeFromXMLFile(Element root) {
/*
* This method expects the XML document to be in the following format:
*
*
*
* test
* abcdefg
* [... ]
* ...
*
*
*/
NodeList childNodes = root.getChildNodes();
int count = childNodes.getLength();
for (int i=0; i0) {
node = childNodes2.item(0);
int type2 = node.getNodeType();
if (type2!=Node.CDATA_SECTION_NODE &&
type2!=Node.TEXT_NODE) {
return false;
}
name = node.getNodeValue().trim();
}
//System.err.println("Macro name==" + name);
}
else if (nodeName.equals(ACTION)) {
NamedNodeMap attributes = node.getAttributes();
if (attributes==null || attributes.getLength()!=1) {
return false;
}
Node node2 = attributes.item(0);
MacroRecord macroRecord = new MacroRecord();
if (!node2.getNodeName().equals(ID)) {
return false;
}
macroRecord.id = node2.getNodeValue();
NodeList childNodes2 = node.getChildNodes();
int length = childNodes2.getLength();
if (length==0) { // Could be empty "" command.
//System.err.println("... empty actionCommand");
macroRecord.actionCommand = "";
//System.err.println("... adding action: " + macroRecord);
macroRecords.add(macroRecord);
break;
}
else {
node = childNodes2.item(0);
int type2 = node.getNodeType();
if (type2!=Node.CDATA_SECTION_NODE &&
type2!=Node.TEXT_NODE) {
return false;
}
macroRecord.actionCommand = node.getNodeValue();
macroRecords.add(macroRecord);
}
}
break;
default:
break; // Skip whitespace nodes, etc.
}
}
// Everything went okay.
return true;
}
/**
* Saves this macro to an XML file. This file can later be read in by the
* constructor taking a File
parameter; this is the mechanism
* for saving macros.
*
* @param file The file in which to save the macro.
* @throws IOException If an error occurs while generating the XML for
* the output file.
* @see #saveToFile(String)
*/
public void saveToFile(File file) throws IOException {
saveToFile(file.getAbsolutePath());
}
/**
* Saves this macro to a file. This file can later be read in by the
* constructor taking a File
parameter; this is the mechanism
* for saving macros.
*
* @param fileName The name of the file in which to save the macro.
* @throws IOException If an error occurs while generating the XML for
* the output file.
* @see #saveToFile(File)
*/
public void saveToFile(String fileName) throws IOException {
/*
* This method writes the XML document in the following format:
*
*
*
* test
* abcdefg
* [... ]
* ...
*
*
*/
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().
newDocumentBuilder();
DOMImplementation impl = db.getDOMImplementation();
Document doc = impl.createDocument(null, ROOT_ELEMENT, null);
Element rootElement = doc.getDocumentElement();
// Write the name of the macro.
Element nameElement = doc.createElement(MACRO_NAME);
nameElement.appendChild(doc.createCDATASection(name));
rootElement.appendChild(nameElement);
// Write all actions (the meat) in the macro.
for (MacroRecord record : macroRecords) {
Element actionElement = doc.createElement(ACTION);
actionElement.setAttribute(ID, record.id);
if (record.actionCommand!=null &&
record.actionCommand.length()>0) {
// Remove illegal characters. I'm no XML expert, but
// I'm not sure what I'm doing wrong. If we don't
// strip out chars with Unicode value < 32, our
// generator will insert '', which will cause
// our parser to barf when reading the macro back in
// (it says "Invalid XML character"). But why doesn't
// our generator tell us the character is invalid too?
String command = record.actionCommand;
for (int j=0; j