Example Files Listings

 The Simple Documentation Framework Files

 XML Schema files

  sdf.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.oxygenxml.com/sample/documentation"
    xmlns:doc="http://www.oxygenxml.com/sample/documentation"
    xmlns:abs="http://www.oxygenxml.com/sample/documentation/abstracts"
    elementFormDefault="qualified">

    <xs:import
        namespace="http://www.oxygenxml.com/sample/documentation/abstracts"
        schemaLocation="abs.xsd"/>

    <xs:element name="book" type="doc:sectionType"/>
    <xs:element name="article" type="doc:sectionType"/>
    <xs:element name="section" type="doc:sectionType"/>

    <xs:complexType name="sectionType">
        <xs:sequence>
            <xs:element name="title" type="xs:string"/>
            <xs:element ref="abs:def" minOccurs="0"/>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="doc:section"
                        maxOccurs="unbounded"/>
                </xs:sequence>
                <xs:choice maxOccurs="unbounded">
                    <xs:element ref="doc:para"/>
                    <xs:element ref="doc:ref"/>
                    <xs:element ref="doc:image"/>
                    <xs:element ref="doc:table"/>
                </xs:choice>
            </xs:choice>
        </xs:sequence>
    </xs:complexType>

    <xs:element name="para" type="doc:paragraphType"/>

    <xs:complexType name="paragraphType" mixed="true">
        <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element name="b"/>
            <xs:element name="i"/>
            <xs:element name="link"/>
        </xs:choice>
    </xs:complexType>
    
    <xs:element name="ref">
        <xs:complexType>
            <xs:attribute name="location" type="xs:anyURI"
                use="required"/>
        </xs:complexType>
    </xs:element>

    <xs:element name="image">
        <xs:complexType>
            <xs:attribute name="href" type="xs:anyURI"
                use="required"/>
        </xs:complexType>
    </xs:element>

    <xs:element name="table">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="header">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="td"
                                maxOccurs="unbounded"
                                type="doc:paragraphType"/>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
                <xs:element name="tr" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="td"
                                type="doc:tdType"
                                maxOccurs="unbounded"/>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>


    <xs:complexType name="tdType">
        <xs:complexContent>
            <xs:extension base="doc:paragraphType">
                <xs:attribute name="row_span"
                    type="xs:integer"/>
                <xs:attribute name="column_span"
                    type="xs:integer"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

</xs:schema>
  abs.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    targetNamespace=
 "http://www.oxygenxml.com/sample/documentation/abstracts">
    <xs:element name="def" type="xs:string"/>
</xs:schema>

 CSS Files

  sdf.css
/* Element from another namespace */
@namespace abs "http://www.oxygenxml.com/sample/documentation/abstracts";

abs|def{
    font-family:monospace;
    font-size:smaller;    
}
abs|def:before{
    content:"Definition:";
    color:gray;
}

/* Vertical flow */
book,
section,
para,
title,
image,
ref {
    display:block;
}

/* Horizontal flow */
b,i {
    display:inline;
}

section{
    margin-left:1em;
    margin-top:1em;
}

section{
    foldable:true;
    not-foldable-child: title;
}

link[href]:before{
    display:inline;
    link:attr(href);
    content: "Click to open: " attr(href);
}

/* Title rendering*/
title{
    font-size: 2.4em;
    font-weight:bold;    
}

* * title{
    font-size: 2.0em;
}
* * * title{
    font-size: 1.6em;
}
* * * * title{
    font-size: 1.2em;
}

book, 
article{
    counter-reset:sect;
}
book > section,
article > section{
    counter-increment:sect;
}
book > section > title:before,
article > section > title:before{
    content: "Section: " counter(sect) " ";
}

/* Inlines rendering*/
b {
    font-weight:bold;
}

i {
    font-style:italic;
}

/*Table rendering */
table{
    display:table;
    border:1px solid navy;
    margin:1em;
}

tr, header{
    display:table-row;
}

header{
    background-color: silver;
    color:inherit
}

td{
  display:table-cell;
  border:1px solid navy;
  padding:1em;
}

image{
    display:block;
    content: attr(href, url);
    margin-left:2em;
}

 XML Files

  sdf_sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://www.oxygenxml.com/sample/documentation"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:abs="http://www.oxygenxml.com/sample/documentation/abstracts">
    <title>My Technical Book</title>
    <section>
        <title>XML</title>
        <abs:def>Extensible Markup Language</abs:def>
        <para>In this section of the book I will explain
            different XML applications.</para>
    </section>
    <section>
        <title>Accessing XML data.</title>
        <section>
            <title>XSLT</title>
            <abs:def>Extensible stylesheet language
                transformation (XSLT) is a language for
                transforming XML documents into other XML
                documents.</abs:def>
            <para>A list of XSL elements and what they do..</para>
            <table>
                <header>
                    <td>XSLT Elements</td>
                    <td>Description</td>
                </header>
                <tr>
                    <td>
                        <b>xsl:stylesheet</b>
                    </td>
                    <td>The <i>xsl:stylesheet</i> element is
                        always the top-level element of an
                        XSL stylesheet. The name
                            <i>xsl:transform</i> may be used
                        as a synonym.</td>
                </tr>
                <tr>
                    <td>
                        <b>xsl:template</b>
                    </td>
                    <td>The <i>xsl:template</i> element has
                        an optional mode attribute. If this
                        is present, the template will only
                        be matched when the same mode is
                        used in the invoking
                            <i>xsl:apply-templates</i>
                        element.</td>
                </tr>
                <tr>
                    <td>
                        <b>for-each</b>
                    </td>
                    <td>The xsl:for-each element causes
                        iteration over the nodes selected by
                        a node-set expression.</td>
                </tr>
                <tr>
                    <td column_span="2">End of the list</td>
                </tr>
            </table>
        </section>
        <section>
            <title>XPath</title>
            <abs:def>XPath (XML Path Language) is a terse
                (non-XML) syntax for addressing portions of
                an XML document. </abs:def>
            <para>Some of the XPath functions.</para>
            <table>
                <header>
                    <td>Function</td>
                    <td>Description</td>
                </header>
                <tr>
                    <td>format-number</td>
                    <td>The <i>format-number</i> function
                        converts its first argument to a
                        string using the format pattern
                        string specified by the second
                        argument and the decimal-format
                        named by the third argument, or the
                        default decimal-format, if there is
                        no third argument</td>
                </tr>
                <tr>
                    <td>current</td>
                    <td>The <i>current</i> function returns
                        a node-set that has the current node
                        as its only member.</td>
                </tr>
                <tr>
                    <td>generate-id</td>
                    <td>The <i>generate-id</i> function
                        returns a string that uniquely
                        identifies the node in the argument
                        node-set that is first in document
                        order.</td>
                </tr>
            </table>
        </section>
    </section>
    <section>
        <title>Documentation frameworks</title>
        <para>One of the most important documentation
            frameworks is Docbook.</para>
        <image
            href="http://www.xmlhack.com/images/docbook.gif"/>
        <para>The other is the topic oriented DITA, promoted
            by OASIS.</para>
        <image
href="http://www.oasis-open.org/images/standards/oasis_standard.jpg"
        />
    </section>
</book>

 XSL Files

  sdf.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
    xpath-default-namespace=
    "http://www.oxygenxml.com/sample/documentation">
    
    <xsl:template match="/">
        <html><xsl:apply-templates/></html>
    </xsl:template>
    
    <xsl:template match="section">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="image">
        <img src="{@href}"/>
    </xsl:template>
    
    <xsl:template match="para">
        <p>
            <xsl:apply-templates/>
        </p>
    </xsl:template>
    
    <xsl:template match="abs:def"
        xmlns:abs=
        "http://www.oxygenxml.com/sample/documentation/abstracts">
        <p>
            <u><xsl:apply-templates/></u>
        </p>
    </xsl:template>
    
    <xsl:template match="title">
        <h1><xsl:apply-templates/></h1>
    </xsl:template>
    
    <xsl:template match="b">
        <b><xsl:apply-templates/></b>
    </xsl:template>
    
    <xsl:template match="i">
        <i><xsl:apply-templates/></i>
    </xsl:template>
    
    <xsl:template match="table">
        <table frame="box" border="1px">
            <xsl:apply-templates/>
        </table>
    </xsl:template>
    
    <xsl:template match="header">
        <tr>
            <xsl:apply-templates/>
        </tr>
    </xsl:template>
    
    <xsl:template match="tr">
        <tr>
            <xsl:apply-templates/>
        </tr>
    </xsl:template>
    
    <xsl:template match="td">
        <td>
            <xsl:apply-templates/>
        </td>
    </xsl:template>
    
    <xsl:template match="header/header/td">
        <th>
            <xsl:apply-templates/>
        </th>
    </xsl:template>

</xsl:stylesheet>

 Java Files

  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("&", "&amp;").
      replaceAll("<", "&lt;");
  }
}
  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;
  }

  /**
   * 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.";
  }
}
  ReferencesResolver.java
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";
  }
}
 CustomRule.java
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;
  }
}
 IDElementLocator.java
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;
  }
}