Let's say you have XML documents like
<doc startnumber="15">
<sec counter="no">
<info/>
<title>Introduction</title>
</sec>
<sec>
<title>Section title</title>
<para>Content</para>
<sec>
<title>Section title</title>
<para>Content</para>
</sec>
</sec>
<sec>
<title>Section title</title>
<para>Content</para>
</sec>
</doc>
and
you want to display the XML content in a simplified Outline view
like:
doc "15"
sec Introduction
sec 15 Section title
sec 15.1 Section title
sec 16 Section title
A simple implementation using a Workspace Access plugin type could be something
like:
/**
* Simple Outline for the Text mode based on executing XPaths over the text content.
*/
public class CustomWorkspaceAccessPluginExtension implements WorkspaceAccessPluginExtension {
/**
* The custom outline list.
*/
private JList customOutlineList;
/**
* Maps outline nodes to ranges in document
*/
private WSXMLTextNodeRange[] currentOutlineRanges;
/**
* The current text page
*/
private WSXMLTextEditorPage currentTextPage;
/**
* Disable caret listener when we select from the caret listener.
*/
private boolean enableCaretListener = true;
/**
* @see ro.sync.exml.plugin.workspace.WorkspaceAccessPluginExtension#applicationStarted(ro.sync.exml.workspace.api.standalone.StandalonePluginWorkspace)
*/
@Override
public void applicationStarted(final StandalonePluginWorkspace pluginWorkspaceAccess) {
pluginWorkspaceAccess.addViewComponentCustomizer(new ViewComponentCustomizer() {
/**
* @see ro.sync.exml.workspace.api.standalone.ViewComponentCustomizer#customizeView(ro.sync.exml.workspace.api.standalone.ViewInfo)
*/
@Override
public void customizeView(ViewInfo viewInfo) {
if(
"SampleWorkspaceAccessID".equals(viewInfo.getViewID())) {
customOutlineList = new JList();
customOutlineList.setCellRenderer(new DefaultListCellRenderer() {
/**
* @see javax.swing.DefaultListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean, boolean)
*/
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
String val = null;
if(value instanceof Element) {
Element element = ((Element)value);
val = element.getNodeName();
if(!"".equals(element.getAttribute("startnumber"))) {
val += " " + "'" + element.getAttribute("startnumber") + "'";
}
NodeList titles = element.getElementsByTagName("title");
if(titles.getLength() > 0) {
val += " \"" + titles.item(0).getTextContent() + "\"";
}
}
label.setText(val);
return label;
}
});
customOutlineList.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if(SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
int sel = customOutlineList.getSelectedIndex();
enableCaretListener = false;
try {
currentTextPage.select(currentTextPage.getOffsetOfLineStart(currentOutlineRanges[sel].getStartLine()) + currentOutlineRanges[sel].getStartColumn() - 1,
currentTextPage.getOffsetOfLineStart(currentOutlineRanges[sel].getEndLine()) + currentOutlineRanges[sel].getEndColumn());
} catch (BadLocationException e1) {
e1.printStackTrace();
}
enableCaretListener = true;
}
}
});
viewInfo.setComponent(new JScrollPane(customOutlineList));
viewInfo.setTitle("Custom Outline");
}
}
});
pluginWorkspaceAccess.addEditorChangeListener(new WSEditorChangeListener() {
/**
* @see ro.sync.exml.workspace.api.listeners.WSEditorChangeListener#editorOpened(java.net.URL)
*/
@Override
public void editorOpened(URL editorLocation) {
WSEditor editorAccess = pluginWorkspaceAccess.getEditorAccess(editorLocation, StandalonePluginWorkspace.MAIN_EDITING_AREA);
if(editorAccess != null) {
WSEditorPage currentPage = editorAccess.getCurrentPage();
if(currentPage instanceof WSXMLTextEditorPage) {
final WSXMLTextEditorPage xmlTP = (WSXMLTextEditorPage) currentPage;
xmlTP.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
reconfigureOutline(xmlTP);
}
@Override
public void insertUpdate(DocumentEvent e) {
reconfigureOutline(xmlTP);
}
@Override
public void changedUpdate(DocumentEvent e) {
reconfigureOutline(xmlTP);
}
});
JTextArea textComponent = (JTextArea) xmlTP.getTextComponent();
textComponent.addCaretListener(new CaretListener() {
@Override
public void caretUpdate(CaretEvent e) {
if(currentOutlineRanges != null && currentTextPage != null && enableCaretListener) {
enableCaretListener = false;
try {
int line = xmlTP.getLineOfOffset(e.getDot());
for (int i = currentOutlineRanges.length - 1; i >= 0; i--) {
if(line > currentOutlineRanges[i].getStartLine() && line < currentOutlineRanges[i].getEndLine()) {
customOutlineList.setSelectedIndex(i);
break;
}
}
} catch (BadLocationException e1) {
e1.printStackTrace();
}
enableCaretListener = true;
}
}
});
}
}
}
/**
* @see ro.sync.exml.workspace.api.listeners.WSEditorChangeListener#editorActivated(java.net.URL)
*/
@Override
public void editorActivated(URL editorLocation) {
WSEditor editorAccess = pluginWorkspaceAccess.getEditorAccess(editorLocation, StandalonePluginWorkspace.MAIN_EDITING_AREA);
if(editorAccess != null) {
WSEditorPage currentPage = editorAccess.getCurrentPage();
if(currentPage instanceof WSXMLTextEditorPage) {
WSXMLTextEditorPage xmlTP = (WSXMLTextEditorPage) currentPage;
reconfigureOutline(xmlTP);
}
}
}
}, StandalonePluginWorkspace.MAIN_EDITING_AREA);
}
/**
* Reconfigure the outline
*
* @param xmlTP The XML Text page.
*/
protected void reconfigureOutline(final WSXMLTextEditorPage xmlTP) {
try {
Object[] evaluateXPath = xmlTP.evaluateXPath("//doc | //sec");
currentOutlineRanges = xmlTP.findElementsByXPath("//doc | //sec");
currentTextPage = xmlTP;
DefaultListModel listModel = new DefaultListModel();
if(evaluateXPath != null) {
for (int i = 0; i < evaluateXPath.length; i++) {
listModel.addElement(evaluateXPath[i]);
}
}
customOutlineList.setModel(listModel);
} catch(XPathException ex) {
ex.printStackTrace();
}
}
/**
* @see ro.sync.exml.plugin.workspace.WorkspaceAccessPluginExtension#applicationClosing()
*/
@Override
public boolean applicationClosing() {
return true;
}
}