InsertImageOperation.java
package simple.documentation.framework;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.net.MalformedURLException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.filechooser.FileFilter;
import ro.sync.ecss.extensions.api.ArgumentDescriptor;
import ro.sync.ecss.extensions.api.ArgumentsMap;
import ro.sync.ecss.extensions.api.AuthorAccess;
import ro.sync.ecss.extensions.api.AuthorOperation;
import ro.sync.ecss.extensions.api.AuthorOperationException;
public class InsertImageOperation implements AuthorOperation {
//
// Implementing the Author Operation Interface.
//
/**
* Performs the operation.
*/
public void doOperation(AuthorAccess authorAccess,
ArgumentsMap arguments)
throws IllegalArgumentException,
AuthorOperationException {
JFrame oxygenFrame = (JFrame) authorAccess.getParentFrame();
String href = displayURLDialog(oxygenFrame);
if (href.length() != 0) {
// Creates the image XML fragment.
String imageFragment =
"<image xmlns='http://www.oxygenxml.com/sample/documentation'" +
" href='" + href + "'/>";
// Inserts this fragment at the caret position.
int caretPosition = authorAccess.getCaretOffset();
authorAccess.insertXMLFragment(imageFragment, caretPosition);
}
}
/**
* Has no arguments.
*
* @return null.
*/
public ArgumentDescriptor[] getArguments() {
return null;
}
/**
* @return A description of the operation.
*/
public String getDescription() {
return "Inserts an image element. Asks the" +
" user for a URL reference.";
}
//
// End of interface implementation.
//
//
// Auxiliary methods.
//
/**
* Displays the URL dialog.
*
* @param parentFrame The parent frame for
* the dialog.
* @return The selected URL string value,
* or the empty string if the user canceled
* the URL selection.
*/
private String displayURLDialog(JFrame parentFrame) {
final JDialog dlg = new JDialog(parentFrame,
"Enter the value for the href attribute", true);
JPanel mainContent = new JPanel(new GridBagLayout());
// The text field.
GridBagConstraints cstr = new GridBagConstraints();
cstr.gridx = 0;
cstr.gridy = 0;
cstr.weightx = 0;
cstr.gridwidth = 1;
cstr.fill = GridBagConstraints.HORIZONTAL;
mainContent.add(new JLabel("Image URI:"), cstr);
cstr.gridx = 1;
cstr.weightx = 1;
final JTextField urlField = new JTextField();
urlField.setColumns(15);
mainContent.add(urlField, cstr);
// Add the "Browse button."
cstr.gridx = 2;
cstr.weightx = 0;
JButton browseButton = new JButton("Browse");
browseButton.addActionListener(new ActionListener() {
/**
* Shows a file chooser dialog.
*/
public void actionPerformed(ActionEvent e) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setMultiSelectionEnabled(false);
// Accepts only the image files.
fileChooser.setFileFilter(new FileFilter() {
public String getDescription() {
return "Image files";
}
public boolean accept(File f) {
String fileName = f.getName();
return f.isFile() &&
( fileName.endsWith(".jpeg")
|| fileName.endsWith(".jpg")
|| fileName.endsWith(".gif")
|| fileName.endsWith(".png")
|| fileName.endsWith(".svg"));
}
});
if (fileChooser.showOpenDialog(dlg)
== JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try {
// Set the file into the text field.
urlField.setText(file.toURL().toString());
} catch (MalformedURLException ex) {
// This should not happen.
ex.printStackTrace();
}
}
}
});
mainContent.add(browseButton, cstr);
// Add the "Ok" button to the layout.
cstr.gridx = 0;
cstr.gridy = 1;
cstr.weightx = 0;
JButton okButton = new JButton("Ok");
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dlg.setVisible(false);
}
});
mainContent.add(okButton, cstr);
mainContent.setBorder(
BorderFactory.createEmptyBorder(10, 5, 10, 5));
// Add the "Cancel" button to the layout.
cstr.gridx = 2;
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
urlField.setText("");
dlg.setVisible(false);
}
});
mainContent.add(cancelButton, cstr);
// When the user closes the dialog
// from the window decoration,
// assume "Cancel" action.
dlg.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
urlField.setText("");
}
});
dlg.getContentPane().add(mainContent);
dlg.pack();
dlg.setLocationRelativeTo(parentFrame);
dlg.setVisible(true);
return urlField.getText();
}
/**
* Test method.
*
* @param args The arguments are ignored.
*/
public static void main(String[] args) {
InsertImageOperation operation =
new InsertImageOperation();
System.out.println("Choosen URL: " +
operation.displayURLDialog(new JFrame()));
}
}
QueryDatabaseOperation.java
package simple.documentation.framework;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Properties;
import ro.sync.ecss.extensions.api.ArgumentDescriptor;
import ro.sync.ecss.extensions.api.ArgumentsMap;
import ro.sync.ecss.extensions.api.AuthorAccess;
import ro.sync.ecss.extensions.api.AuthorOperation;
import ro.sync.ecss.extensions.api.AuthorOperationException;
public class QueryDatabaseOperation implements AuthorOperation{
private static String ARG_JDBC_DRIVER ="jdbc_driver";
private static String ARG_USER ="user";
private static String ARG_PASSWORD ="password";
private static String ARG_SQL ="sql";
private static String ARG_CONNECTION ="connection";
/**
* @return The array of arguments the developer must specify when
* configuring the action.
*/
public ArgumentDescriptor[] getArguments() {
ArgumentDescriptor args[] = new ArgumentDescriptor[] {
new ArgumentDescriptor(
ARG_JDBC_DRIVER,
ArgumentDescriptor.TYPE_STRING,
"The name of the Java class that is the JDBC driver."),
new ArgumentDescriptor(
ARG_CONNECTION,
ArgumentDescriptor.TYPE_STRING,
"The database URL connection string."),
new ArgumentDescriptor(
ARG_USER,
ArgumentDescriptor.TYPE_STRING,
"The name of the database user."),
new ArgumentDescriptor(
ARG_PASSWORD,
ArgumentDescriptor.TYPE_STRING,
"The database password."),
new ArgumentDescriptor(
ARG_SQL,
ArgumentDescriptor.TYPE_STRING,
"The SQL statement to be executed.")
};
return args;
}
/**
* @return The operation description.
*/
public String getDescription() {
return "Executes a database query and puts the result in a table.";
}
public void doOperation(AuthorAccess authorAccess, ArgumentsMap map)
throws IllegalArgumentException, AuthorOperationException {
// Collects the arguments.
String jdbcDriver =
(String)map.getArgumentValue(ARG_JDBC_DRIVER);
String connection =
(String)map.getArgumentValue(ARG_CONNECTION);
String user =
(String)map.getArgumentValue(ARG_USER);
String password =
(String)map.getArgumentValue(ARG_PASSWORD);
String sql =
(String)map.getArgumentValue(ARG_SQL);
int caretPosition = authorAccess.getCaretOffset();
try {
authorAccess.insertXMLFragment(
getFragment(jdbcDriver, connection, user, password, sql),
caretPosition);
} catch (SQLException e) {
throw new AuthorOperationException(
"The operation failed due to the following database error: " +
e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new AuthorOperationException(
"The JDBC database driver was not found. Tried to load ' " +
jdbcDriver + "'", e);
}
}
/**
* Creates a connection to the database, executes
* the SQL statement and creates an XML fragment
* containing a table element that wraps the data
* from the result set.
*
*
* @param jdbcDriver The class name of the JDBC driver.
* @param connectionURL The connection URL.
* @param user The database user.
* @param password The password.
* @param sql The SQL statement.
* @return The string containing the XML fragment.
*
* @throws SQLException thrown when there is a
* problem accessing the database or there are
* erors in the SQL expression.
* @throws ClassNotFoundException when the JDBC
* driver class could not be loaded.
*/
private String getFragment(
String jdbcDriver,
String connectionURL,
String user,
String password,
String sql) throws
SQLException,
ClassNotFoundException {
Properties pr = new Properties();
pr.put("characterEncoding", "UTF8");
pr.put("useUnicode", "TRUE");
pr.put("user", user);
pr.put("password", password);
// Loads the database driver.
Class.forName(jdbcDriver);
// Opens the connection
Connection connection =
DriverManager.getConnection(connectionURL, pr);
java.sql.Statement statement =
connection.createStatement();
ResultSet resultSet =
statement.executeQuery(sql);
StringBuffer fragmentBuffer = new StringBuffer();
fragmentBuffer.append(
"<table xmlns='http://www.oxygenxml.com/sample/documentation'>");
//
// Creates the table header.
//
fragmentBuffer.append("<header>");
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
fragmentBuffer.append("<td>");
fragmentBuffer.append(
xmlEscape(metaData.getColumnName(i)));
fragmentBuffer.append("</td>");
}
fragmentBuffer.append("</header>");
//
// Creates the table content.
//
while (resultSet.next()) {
fragmentBuffer.append("<tr>");
for (int i = 1; i <= columnCount; i++) {
fragmentBuffer.append("<td>");
fragmentBuffer.append(
xmlEscape(resultSet.getObject(i)));
fragmentBuffer.append("</td>");
}
fragmentBuffer.append("</tr>");
}
fragmentBuffer.append("</table>");
// Cleanup
resultSet.close();
statement.close();
connection.close();
return fragmentBuffer.toString();
}
/**
* Some of the values from the database table
* may contain characters that must be escaped
* in XML, to ensure the fragment is well formed.
*
* @param object The object from the database.
* @return The escaped string representation.
*/
private String xmlEscape(Object object) {
String str = String.valueOf(object);
return str.
replaceAll("&", "&").
replaceAll("<", "<");
}
}
package simple.documentation.framework;
import ro.sync.contentcompletion.xml.SchemaManagerFilter;
import ro.sync.ecss.extensions.api.AuthorExtensionStateListener;
import ro.sync.ecss.extensions.api.AuthorReferenceResolver;
import ro.sync.ecss.extensions.api.AuthorTableCellSpanProvider;
import ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider;
import ro.sync.ecss.extensions.api.ExtensionsBundle;
import ro.sync.ecss.extensions.api.StylesFilter;
import ro.sync.ecss.extensions.api.link.ElementLocatorProvider;
import ro.sync.ecss.extensions.commons.DefaultElementLocatorProvider;
import ro.sync.exml.editor.xmleditor.pageauthor.AuthorDnDListener;
public class SDFExtensionsBundle extends ExtensionsBundle {
public String getDocumentTypeID() {
return "Simple.Document.Framework.document.type";
}
public String getDescription() {
return "A custom extensions bundle used for the " +
"Simple Document Framework document type";
}
public AuthorExtensionStateListener createAuthorExtensionStateListener() {
return new SDFAuthorExtensionStateListener();
}
public SchemaManagerFilter createSchemaManagerFilter() {
return new SDFSchemaManagerFilter();
}
public ElementLocatorProvider createElementLocatorProvider() {
return new DefaultElementLocatorProvider();
}
public AuthorDnDListener createAuthorAWTDndListener() {
return new SDFAuthorDndListener();
}
public AuthorReferenceResolver createAuthorReferenceResolver() {
return new ReferencesResolver();
}
public StylesFilter createAuthorStylesFilter() {
return new SDFStylesFilter();
}
public AuthorTableCellSpanProvider createAuthorTableCellSpanProvider() {
return new TableCellSpanProvider();
}
public AuthorTableColumnWidthProvider createAuthorTableColumnWidthProvider() {
return new TableColumnWidthProvider();
}
}
SDFSchemaManagerFilter.java
package simple.documentation.framework;
import java.util.Iterator;
import java.util.List;
import ro.sync.contentcompletion.xml.CIAttribute;
import ro.sync.contentcompletion.xml.CIElement;
import ro.sync.contentcompletion.xml.CIValue;
import ro.sync.contentcompletion.xml.Context;
import ro.sync.contentcompletion.xml.ContextElement;
import ro.sync.contentcompletion.xml.SchemaManagerFilter;
import ro.sync.contentcompletion.xml.WhatAttributesCanGoHereContext;
import ro.sync.contentcompletion.xml.WhatElementsCanGoHereContext;
import ro.sync.contentcompletion.xml.WhatPossibleValuesHasAttributeContext;
public class SDFSchemaManagerFilter implements SchemaManagerFilter {
@Override
public List<CIValue> filterAttributeValues(List<CIValue> attributeValues,
WhatPossibleValuesHasAttributeContext context) {
return attributeValues;
}
@Override
public List<CIAttribute> filterAttributes(List<CIAttribute> attributes,
WhatAttributesCanGoHereContext context) {
// If the element from the current context is the 'table' element add the
// attribute named 'frame' to the list of default content
// completion proposals
ContextElement contextElement = context.getParentElement();
if ("table".equals(contextElement.getQName())) {
CIAttribute frameAttribute = new CIAttribute();
frameAttribute.setName("frame");
frameAttribute.setRequired(false);
frameAttribute.setFixed(false);
frameAttribute.setDefaultValue("void");
attributes.add(frameAttribute);
}
return attributes;
}
@Override
public List<CIValue> filterElementValues(List<CIValue> elementValues,
Context context) {
return elementValues;
}
@Override
public List<CIElement> filterElements(List<CIElement> elements,
WhatElementsCanGoHereContext context) {
// If the element from the current context is the 'header'
// element remove the 'td' element from the list of content
// completion proposals and add the 'th' element.
ContextElement contextElement = context.getElementStack().peek();
if ("header".equals(contextElement.getQName())) {
for (Iterator<CIElement> iterator = elements.iterator();
iterator.hasNext();) {
CIElement element = iterator.next();
// Remove the 'td' element
if ("td".equals(element.getQName())) {
elements.remove(element);
break;
}
}
// Insert the 'th' element in the list of content completion proposals
CIElement thElement = new SDFElement();
thElement.setName("th");
elements.add(thElement);
}
return elements;
}
@Override
public String getDescription() {
return null;
}
}
TableCellSpanProvider.java
package simple.documentation.framework;
public class TableCellSpanProvider
implements AuthorTableCellSpanProvider {
/**
* Extracts the integer specifing what is the width
* (in columns) of the cell
* representing in the table layout the cell element.
*/
public Integer getColSpan(AuthorElement cell) {
Integer colSpan = null;
AttrValue attrValue = cell.getAttribute("column_span");
if(attrValue != null) {
// The attribute was found.
String cs = attrValue.getValue();
if(cs != null) {
try {
colSpan = new Integer(cs);
} catch (NumberFormatException ex) {
// The attribute value was not a number.
}
}
}
return colSpan;
}
/**
* Extracts the integer specifing what is the
* height (in rows) of the cell
* representing in the table layout the cell element.
*/
public Integer getRowSpan(AuthorElement cell) {
Integer rowSpan = null;
AttrValue attrValue = cell.getAttribute("row_span");
if(attrValue != null) {
// The attribute was found.
String rs = attrValue.getValue();
if(rs != null) {
try {
rowSpan = new Integer(rs);
} catch (NumberFormatException ex) {
// The attribute value was not a number.
}
}
}
return rowSpan;
}
/**
* @return true considering the column specifications always available.
*/
public boolean hasColumnSpecifications(AuthorElement tableElement) {
return true;
}
/**
* Ignored. We do not extract data from the
* <code>table</code> element.
*/
public void init(AuthorElement table) {
}
public String getDescription() {
return
"Implementation for the Simple Documentation Framework table layout.";
}
}
TableColumnWidthProvider.java
package simple.documentation.framework.extensions;
import java.util.ArrayList;
import java.util.List;
import ro.sync.ecss.extensions.api.AuthorDocumentController;
import ro.sync.ecss.extensions.api.AuthorOperationException;
import ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider;
import ro.sync.ecss.extensions.api.WidthRepresentation;
import ro.sync.ecss.extensions.api.node.AttrValue;
import ro.sync.ecss.extensions.api.node.AuthorElement;
/**
*
* Simple Documentation Framework table column width provider.
*
*/
public class TableColumnWidthProvider implements AuthorTableColumnWidthProvider {
/**
* Cols start offset
*/
private int colsStartOffset;
/**
* Cols end offset
*/
private int colsEndOffset;
/**
* Column widths specifications
*/
private List<WidthRepresentation> colWidthSpecs = new ArrayList<WidthRepresentation>();
/**
* The table element
*/
private AuthorElement tableElement;
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#commitColumnWidthModifications(ro.sync.ecss.extensions.api.AuthorDocumentController, ro.sync.ecss.extensions.api.WidthRepresentation[], java.lang.String)
*/
public void commitColumnWidthModifications(AuthorDocumentController authorDocumentController,
WidthRepresentation[] colWidths, String tableCellsTagName) throws AuthorOperationException {
if ("td".equals(tableCellsTagName)) {
if (colWidths != null && tableElement != null) {
if (colsStartOffset >= 0 && colsEndOffset >= 0 && colsStartOffset < colsEndOffset) {
authorDocumentController.delete(colsStartOffset,
colsEndOffset);
}
String xmlFragment = createXMLFragment(colWidths);
int offset = -1;
AuthorElement[] header = tableElement.getElementsByLocalName("header");
if (header != null && header.length > 0) {
// Insert the cols elements before the 'header' element
offset = header[0].getStartOffset();
}
if (offset == -1) {
throw new AuthorOperationException("No valid offset to insert the columns width specification.");
}
authorDocumentController.insertXMLFragment(xmlFragment, offset);
}
}
}
/**
* Creates the XML fragment representing the column specifications.
*
* @param widthRepresentations
* @return The XML fragment as a string.
*/
private String createXMLFragment(WidthRepresentation[] widthRepresentations) {
StringBuffer fragment = new StringBuffer();
String ns = tableElement.getNamespace();
for (int i = 0; i < widthRepresentations.length; i++) {
WidthRepresentation width = widthRepresentations[i];
fragment.append("<customcol");
String strRepresentation = width.getWidthRepresentation();
if (strRepresentation != null) {
fragment.append(" width=\"" + width.getWidthRepresentation() + "\"");
}
if (ns != null && ns.length() > 0) {
fragment.append(" xmlns=\"" + ns + "\"");
}
fragment.append("/>");
}
return fragment.toString();
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#commitTableWidthModification(ro.sync.ecss.extensions.api.AuthorDocumentController, int, java.lang.String)
*/
public void commitTableWidthModification(AuthorDocumentController authorDocumentController,
int newTableWidth, String tableCellsTagName) throws AuthorOperationException {
if ("td".equals(tableCellsTagName)) {
if (newTableWidth > 0) {
if (tableElement != null) {
String newWidth = String.valueOf(newTableWidth);
authorDocumentController.setAttribute(
"width",
new AttrValue(newWidth),
tableElement);
} else {
throw new AuthorOperationException("Cannot find the element representing the table.");
}
}
}
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#getCellWidth(ro.sync.ecss.extensions.api.node.AuthorElement, int, int)
*/
public List<WidthRepresentation> getCellWidth(AuthorElement cellElement, int colNumberStart,
int colSpan) {
List<WidthRepresentation> toReturn = null;
int size = colWidthSpecs.size();
if (size >= colNumberStart && size >= colNumberStart + colSpan) {
toReturn = new ArrayList<WidthRepresentation>(colSpan);
for (int i = colNumberStart; i < colNumberStart + colSpan; i ++) {
// Add the column widths
toReturn.add(colWidthSpecs.get(i));
}
}
return toReturn;
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#getTableWidth(java.lang.String)
*/
public WidthRepresentation getTableWidth(String tableCellsTagName) {
WidthRepresentation toReturn = null;
if (tableElement != null && "td".equals(tableCellsTagName)) {
AttrValue widthAttr = tableElement.getAttribute("width");
if (widthAttr != null) {
String width = widthAttr.getValue();
if (width != null) {
toReturn = new WidthRepresentation(width, true);
}
}
}
return toReturn;
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#init(ro.sync.ecss.extensions.api.node.AuthorElement)
*/
public void init(AuthorElement tableElement) {
this.tableElement = tableElement;
AuthorElement[] colChildren = tableElement.getElementsByLocalName("customcol");
if (colChildren != null && colChildren.length > 0) {
for (int i = 0; i < colChildren.length; i++) {
AuthorElement colChild = colChildren[i];
if (i == 0) {
colsStartOffset = colChild.getStartOffset();
}
if (i == colChildren.length - 1) {
colsEndOffset = colChild.getEndOffset();
}
// Determine the 'width' for this col.
AttrValue colWidthAttribute = colChild.getAttribute("width");
String colWidth = null;
if (colWidthAttribute != null) {
colWidth = colWidthAttribute.getValue();
// Add WidthRepresentation objects for the columns this 'customcol' specification
// spans over.
colWidthSpecs.add(new WidthRepresentation(colWidth, true));
}
}
}
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isAcceptingFixedColumnWidths(java.lang.String)
*/
public boolean isAcceptingFixedColumnWidths(String tableCellsTagName) {
return true;
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isAcceptingPercentageColumnWidths(java.lang.String)
*/
public boolean isAcceptingPercentageColumnWidths(String tableCellsTagName) {
return true;
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isAcceptingProportionalColumnWidths(java.lang.String)
*/
public boolean isAcceptingProportionalColumnWidths(String tableCellsTagName) {
return true;
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isTableAcceptingWidth(java.lang.String)
*/
public boolean isTableAcceptingWidth(String tableCellsTagName) {
return "td".equals(tableCellsTagName);
}
/**
* @see ro.sync.ecss.extensions.api.AuthorTableColumnWidthProvider#isTableAndColumnsResizable(java.lang.String)
*/
public boolean isTableAndColumnsResizable(String tableCellsTagName) {
return "td".equals(tableCellsTagName);
}
/**
* @see ro.sync.ecss.extensions.api.Extension#getDescription()
*/
public String getDescription() {
return "Implementation for the Simple Documentation Framework table layout.";
}
}
package simple.documentation.framework;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.transform.sax.SAXSource;
import org.apache.log4j.Logger;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import ro.sync.ecss.extensions.api.AuthorAccess;
import ro.sync.ecss.extensions.api.AuthorReferenceResolver;
import ro.sync.ecss.extensions.api.node.AttrValue;
import ro.sync.ecss.extensions.api.node.AuthorElement;
import ro.sync.ecss.extensions.api.node.AuthorNode;
/**
* Resolver for content referred by elements named 'ref' with a
* 'location' attribute.
*/
public class ReferencesResolver implements AuthorReferenceResolver {
/**
* Logger for logging.
*/
private static Logger logger = Logger.getLogger(
ReferencesResolver.class.getName());
/**
* Verifies if the handler considers the node to have references.
*
* @param node The node to be analyzed.
* @return <code>true</code> if it is has references.
*/
public boolean hasReferences(AuthorNode node) {
boolean hasReferences = false;
if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) {
AuthorElement element = (AuthorElement) node;
if ("ref".equals(element.getLocalName())) {
AttrValue attrValue = element.getAttribute("location");
hasReferences = attrValue != null;
}
}
return hasReferences;
}
/**
* Returns the name of the node that contains the expanded referred content.
*
* @param node The node that contains references.
* @return The display name of the node.
*/
public String getDisplayName(AuthorNode node) {
String displayName = "ref-fragment";
if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) {
AuthorElement element = (AuthorElement) node;
if ("ref".equals(element.getLocalName())) {
AttrValue attrValue = element.getAttribute("location");
if (attrValue != null) {
displayName = attrValue.getValue();
}
}
}
return displayName;
}
/**
* Resolve the references of the node.
*
* The returning SAXSource will be used for creating the referred content
* using the parser and source inside it.
*
* @param node The clone of the node.
* @param systemID The system ID of the node with references.
* @param authorAccess The author access implementation.
* @param entityResolver The entity resolver that can be used to resolve:
*
* <ul>
* <li>Resources that are already opened in editor.
* For this case the InputSource will contains the editor content.</li>
* <li>Resources resolved through XML catalog.</li>
* </ul>
*
* @return The SAX source including the parser and the parser's input source.
*/
public SAXSource resolveReference(
AuthorNode node,
String systemID,
AuthorAccess authorAccess,
EntityResolver entityResolver) {
SAXSource saxSource = null;
if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) {
AuthorElement element = (AuthorElement) node;
if ("ref".equals(element.getLocalName())) {
AttrValue attrValue = element.getAttribute("location");
if (attrValue != null) {
String attrStringVal = attrValue.getValue();
try {
URL absoluteUrl = new URL(new URL(systemID),
authorAccess.correctURL(attrStringVal));
InputSource inputSource = entityResolver.resolveEntity(null,
absoluteUrl.toString());
if(inputSource == null) {
inputSource = new InputSource(absoluteUrl.toString());
}
XMLReader xmlReader = authorAccess.newNonValidatingXMLReader();
xmlReader.setEntityResolver(entityResolver);
saxSource = new SAXSource(xmlReader, inputSource);
} catch (MalformedURLException e) {
logger.error(e, e);
} catch (SAXException e) {
logger.error(e, e);
} catch (IOException e) {
logger.error(e, e);
}
}
}
}
return saxSource;
}
/**
* Get an unique identifier for the node reference.
*
* The unique identifier is used to avoid resolving the references
* recursively.
*
* @param node The node that has reference.
* @return An unique identifier for the reference node.
*/
public String getReferenceUniqueID(AuthorNode node) {
String id = null;
if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) {
AuthorElement element = (AuthorElement) node;
if ("ref".equals(element.getLocalName())) {
AttrValue attrValue = element.getAttribute("location");
if (attrValue != null) {
id = attrValue.getValue();
}
}
}
return id;
}
/**
* Return the systemID of the referred content.
*
* @param node The reference node.
* @param authorAccess The author access.
*
* @return The systemID of the referred content.
*/
public String getReferenceSystemID(AuthorNode node,
AuthorAccess authorAccess) {
String systemID = null;
if (node.getType() == AuthorNode.NODE_TYPE_ELEMENT) {
AuthorElement element = (AuthorElement) node;
if ("ref".equals(element.getLocalName())) {
AttrValue attrValue = element.getAttribute("location");
if (attrValue != null) {
String attrStringVal = attrValue.getValue();
try {
URL absoluteUrl = new URL(node.getXMLBaseURL(),
authorAccess.correctURL(attrStringVal));
systemID = absoluteUrl.toString();
} catch (MalformedURLException e) {
logger.error(e, e);
}
}
}
}
return systemID;
}
/**
* Verifies if the references of the given node must be refreshed
* when the attribute with the specified name has changed.
*
* @param node The node with the references.
* @param attributeName The name of the changed attribute.
* @return <code>true</code> if the references must be refreshed.
*/
public boolean isReferenceChanged(AuthorNode node, String attributeName) {
return "location".equals(attributeName);
}
/**
* @return The description of the author extension.
*/
public String getDescription() {
return "Resolves the 'ref' references";
}
}
package simple.documentation.framework;
import org.xml.sax.Attributes;
import ro.sync.ecss.extensions.api.DocumentTypeCustomRuleMatcher;
public class CustomRule implements
DocumentTypeCustomRuleMatcher {
/**
* Checks if the root namespace is the one
* of our documentation framework.
*/
public boolean matches(
String systemID,
String rootNamespace,
String rootLocalName,
String doctypePublicID,
Attributes rootAttributes) {
return
"http://www.oxygenxml.com/sample/documentation".equals(rootNamespace);
}
public String getDescription() {
return
"Checks if the current Document Type Association is matching the document.";
}
}
DefaultElementLocatorProvider.java
package ro.sync.ecss.extensions.commons;
import org.apache.log4j.Logger;
import ro.sync.ecss.extensions.api.link.ElementLocator;
import ro.sync.ecss.extensions.api.link.ElementLocatorException;
import ro.sync.ecss.extensions.api.link.ElementLocatorProvider;
import ro.sync.ecss.extensions.api.link.IDTypeVerifier;
/**
* Default implementation for locating elements based on a given link.
* Depending on the link structure the following cases are covered:
* - xinclude element scheme : element(/1/2) see
* http://www.w3.org/TR/2003/REC-xptr-element-20030325/
* - ID based links : the link represents the value of an attribute of type ID
*/
public class DefaultElementLocatorProvider implements ElementLocatorProvider {
/** * Logger for logging. */
private static Logger logger = Logger.getLogger(
DefaultElementLocatorProvider.class.getName());
/**
* @see ro.sync.ecss.extensions.api.link.ElementLocatorProvider#
* getElementLocator(ro.sync.ecss.extensions.api.link.IDTypeVerifier,
* java.lang.String)
*/
public ElementLocator getElementLocator(IDTypeVerifier idVerifier,
String link) {
ElementLocator elementLocator = null;
try {
if(link.startsWith("element(")){
// xpointer element() scheme
elementLocator = new XPointerElementLocator(idVerifier, link);
} else {
// Locate link element by ID
elementLocator = new IDElementLocator(idVerifier, link);
}
} catch (ElementLocatorException e) {
logger.warn("Exception when create element locator for link: "
+ link + ". Cause: " + e, e);
}
return elementLocator;
}
/**
* @see ro.sync.ecss.extensions.api.Extension#getDescription()
*/
public String getDescription() {
return
"Default implementation for locating elements based on a given link. \n" +
"The following cases are covered: xinclude element scheme "
+ "and ID based links.";
}
}
XPointerElementLocator.java
package ro.sync.ecss.extensions.commons;
import java.util.Stack;
import java.util.StringTokenizer;
import org.apache.log4j.Logger;
import ro.sync.ecss.extensions.api.link.Attr;
import ro.sync.ecss.extensions.api.link.ElementLocator;
import ro.sync.ecss.extensions.api.link.ElementLocatorException;
import ro.sync.ecss.extensions.api.link.IDTypeVerifier;
/**
* Element locator for links that have the one of the following pattern:
* <ul>
* <li>element(elementID) - locate the element with the same id</li>
* <li>element(/1/2/5) - A child sequence appearing alone identifies an
* element by means of stepwise navigation, which is directed by a
* sequence of integers separated by slashes (/); each integer n locates
* the nth child element of the previously located element. </li>
* <li>element(elementID/3/4) - A child sequence appearing after an
* NCName identifies an element by means of stepwise navigation,
* starting from the element located by the given name.</li>
* </ul>
*
*/
public class XPointerElementLocator extends ElementLocator {
/**
* Logger for logging.
*/
private static Logger logger = Logger.getLogger(
XPointerElementLocator.class.getName());
/**
* Verifies if a given attribute is of a type ID.
*/
private IDTypeVerifier idVerifier;
/**
* XPointer path, the path to locate the linked element.
*/
private String[] xpointerPath;
/**
* The stack with indexes in parent of the current iterated elements.
*/
private Stack currentElementIndexStack = new Stack();
/**
* The number of elements in xpointer path.
*/
private int xpointerPathDepth;
/**
* If true then the XPointer path starts with an element ID.
*/
private boolean startWithElementID = false;
/**
* The depth of the current element in document, incremented in startElement.
*/
private int startElementDepth = 0;
/**
* Depth in document in the last endElement event.
*/
private int endElementDepth = 0;
/**
* The index in parent of the previous iterated element. Set in endElement().
*/
private int lastIndexInParent;
/**
* Constructor.
*
* @param idVerifier Verifies if an given attribute is of type ID.
* @param link The link that gives the element position.
* @throws ElementLocatorException When the link format is not supported.
**/
public XPointerElementLocator(IDTypeVerifier idVerifier, String link)
throws ElementLocatorException {
super(link);
this.idVerifier = idVerifier;
link = link.substring("element(".length(), link.length() - 1);
StringTokenizer stringTokenizer = new StringTokenizer(link, "/", false);
xpointerPath = new String[stringTokenizer.countTokens()];
int i = 0;
while (stringTokenizer.hasMoreTokens()) {
xpointerPath[i] = stringTokenizer.nextToken();
boolean invalidFormat = false;
// Empty xpointer component is not supported
if(xpointerPath[i].length() == 0){
invalidFormat = true;
}
if(i > 0){
try {
Integer.parseInt(xpointerPath[i]);
} catch (NumberFormatException e) {
invalidFormat = true;
}
}
if(invalidFormat){
throw new ElementLocatorException(
"Only the element() scheme is supported when locating XPointer links."
+ "Supported formats: element(elementID), element(/1/2/3),
element(elemID/2/3/4).");
}
i++;
}
if(Character.isDigit(xpointerPath[0].charAt(0))){
// This is the case when xpointer have the following pattern /1/5/7
xpointerPathDepth = xpointerPath.length;
} else {
// This is the case when xpointer starts with an element ID
xpointerPathDepth = -1;
startWithElementID = true;
}
}
/**
* @see ro.sync.ecss.extensions.api.link.ElementLocator#endElement(
* java.lang.String, java.lang.String, java.lang.String)
*/
public void endElement(String uri, String localName, String name) {
endElementDepth = startElementDepth;
startElementDepth --;
lastIndexInParent = ((Integer)currentElementIndexStack.pop()).intValue();
}
/**
* @see ro.sync.ecss.extensions.api.link.ElementLocator#startElement(
* java.lang.String, java.lang.String, java.lang.String,
* ro.sync.ecss.extensions.api.link.Attr[])
*/
public boolean startElement(String uri, String localName,
String name, Attr[] atts) {
boolean linkLocated = false;
// Increase current element document depth
startElementDepth ++;
if (endElementDepth != startElementDepth) {
// The current element is the first child of the parent
currentElementIndexStack.push(new Integer(1));
} else {
// Another element in the parent element
currentElementIndexStack.push(new Integer(lastIndexInParent + 1));
}
if (startWithElementID) {
// This the case when xpointer path starts with an element ID.
String xpointerElement = xpointerPath[0];
for (int i = 0; i < atts.length; i++) {
if(xpointerElement.equals(atts[i].getValue())){
if(idVerifier.hasIDType(
localName, uri, atts[i].getQName(), atts[i].getNamespace())){
xpointerPathDepth = startElementDepth + xpointerPath.length - 1;
break;
}
}
}
}
if(xpointerPathDepth == startElementDepth){
// check if xpointer path matches with the current element path
linkLocated = true;
try {
int xpointerIdx = xpointerPath.length - 1;
int stackIdx = currentElementIndexStack.size() - 1;
int stopIdx = startWithElementID ? 1 : 0;
while (xpointerIdx >= stopIdx && stackIdx >= 0) {
int xpointerIndex = Integer.parseInt(xpointerPath[xpointerIdx]);
int currentElementIndex = ((Integer)currentElementIndexStack.
get(stackIdx)).intValue();
if(xpointerIndex != currentElementIndex) {
linkLocated = false;
break;
}
xpointerIdx--;
stackIdx--;
}
} catch (NumberFormatException e) {
logger.warn(e,e);
}
}
return linkLocated;
}
}
package ro.sync.ecss.extensions.commons;
import ro.sync.ecss.extensions.api.link.Attr;
import ro.sync.ecss.extensions.api.link.ElementLocator;
import ro.sync.ecss.extensions.api.link.ExtensionUtil;
import ro.sync.ecss.extensions.api.link.IDTypeVerifier;
/**
* Implementation of an ElementLocator that treats the link as the value of an
* attribute with the type ID.
*/
public class IDElementLocator extends ElementLocator {
/**
* Class able to tell if a given attribute is of type ID.
*/
private IDTypeVerifier idVerifier;
/**
* Constructor.
*
* @param idVerifier It tells us if an attribute is of type ID.
* @param link The link used to identify an element.
*/
public IDElementLocator(IDTypeVerifier idVerifier, String link) {
super(link);
this.idVerifier = idVerifier;
}
/**
* @see ro.sync.ecss.extensions.api.link.ElementLocator#endElement(
* java.lang.String, java.lang.String, java.lang.String)
*/
public void endElement(String uri, String localName, String name) {
// Nothing to do.
}
/**
* @see ro.sync.ecss.extensions.api.link.ElementLocator#startElement(
* java.lang.String, java.lang.String, java.lang.String,
* ro.sync.ecss.extensions.api.link.Attr[])
*/
public boolean startElement(String uri, String localName,
String name, Attr[] atts) {
boolean elementFound = false;
for (int i = 0; i < atts.length; i++) {
if (link.equals(atts[i].getValue())) {
if("xml:id".equals(atts[i].getQName())) {
// xml:id attribute
elementFound = true;
} else {
// check if attribute has ID type
String attrLocalName =
ExtensionUtil.getLocalName(atts[i].getQName());
String attrUri = atts[i].getNamespace();
if (idVerifier.hasIDType(localName, uri, attrLocalName, attrUri)) {
elementFound = true;
}
}
}
}
return elementFound;
}
}