Extending JCite

You can extend JCite to support new citation sources by means of citelets (as I did, for example, to support citing Excel spreadsheet ranges).

Plain Citelets

Plain citelets do not make assumptions about the format of citable sources. You can use anything, like the Excel spreadsheets mentioned above. If your sources are plain text files, however, please also read the next section. In this demo, we are going to use a simple map to look up the citation for a given reference.

So here’s our basic citelet, to be fleshed out further below:

public static class MyCitelet extends JCitelet{

  private final Map<String, String> fragments = new HashMap<String, String>();

  public MyCitelet(JCite _jcite) {
    super(_jcite);
    this.fragments.put("exA", "Example A\nAnd a little more of example A.");
    this.fragments.put("exB", "Example B");
  }

Since we can have multiple citelets, JCite needs to know which of the citelets to ask to resolve a reference. It does this via the prefix of a reference, whose general format is [citelet:reference]. Here we use demo, so references to our citelet will have to look like [demo:foo]:

@Override protected String referencePrefix() {
  return "demo";
}

If there is a reference directed at our citelet in the text, JCite asks us for the corresponding citation. A Citation must return a canonical textual representation of the cited fragment. When JCite is used with tripwires, this text is what gets recorded in the tripwire database. It should be kept as raw as possible, so that changes in how a citelet formats citations (see below) do not invalidate the tripwires.

In the simplest case, the reference can be used directly as the key to look up the cited fragment. Often, though, you will want to parse it. Another special case arises when the reference contains not only the key, but also formatting options. The latter can be passed to the formatter by subclassing Citation, or using the predefined AnnotatedCitation:

@Override public Citation cite(String _reference) throws JCiteError, IOException {
  final String[] parts = _reference.split(" as ");
  final String key = parts[0];
  final String fragment = this.fragments.get(key);
  if (null == fragment)
    throw new JCiteError("Cannot find source for " + key);
  if (parts.length == 1)
    return new Citation(fragment);
  final String formattingOptions = parts[1];
  return new AnnotatedCitation(fragment, formattingOptions);
}

Finally, prior to inserting the cited fragment into the resulting HTML document, JCite asks us to format it. Here we can make use of any additional information we passed to subclasses of Citation:

@Override protected String format(Insertion _insertion) throws JCiteError, IOException {
  final String[] lines = _insertion.text().split("\n");
  final StringBuilder fmt = new StringBuilder();
  String prefix = "DEMO:\t";
  if (_insertion instanceof AnnotatedCitation)
    prefix = ((AnnotatedCitation) _insertion).annotation() + ":\t";
  for (String line : lines) {
    fmt.append(prefix).append(line).append("\n");
    prefix = "\t";
  }
  return fmt.toString();
}

Finally, here’s an example of the demo citelet in action:

String cited = citer.process("A test string\n" + //
    "with a citation:\n" + //
    "<stripped>[demo:exA]</stripped>\n"+ //
    "and another one as in info block:\n" + //
    "<stripped>[demo:exB as INFO]</stripped>");
assertEquals("A test string\n" + //
    "with a citation:\n" + //
    "DEMO:  Example A\n" + //
    "  And a little more of example A.\n" + //
    "\n"+ //
    "and another one as in info block:\n" + //
    "INFO:  Example B\n", //
    cited);

Text-Based Citelets

Text-based citelets extract referenced fragments from plain text files. They do this by means of fragment markers, which denote the start and end of a fragment for a given reference. The Java citelet is a prominent example, where the markers are Java comments.

So again, here’s our basic citelet, to be fleshed out further below:

public static class MyCitelet extends TextBasedCitelet {

  public MyCitelet(JCite _jcite) {
    super(_jcite);
  }

  @Override protected String referencePrefix() {
    return "demo";
  }

The base class, TextBasedCitelet, already takes care of parsing the source file name, fragment name, and options from the reference, looking for the source file in the search path, and extracting fragments from it. For the latter, however, it needs to know the fragment markers the citelet supports. For every reference into a source file, it asks us for the appropriate markers, giving us the name of the fragment to look for:

@Override protected FragmentMarker[] markersFor(String _fragmentName) {
  return new FragmentMarker[] { new BlockMarker(
      "// begin " + _fragmentName + "\n", //
      "// end " + _fragmentName + "\n") };
}

In this demo, we use a simple block marker (which means the fragment markers appear on separate lines from the fragment content), with different start and end markers. For example:

// begin exA
Example A
And a little more of example A.
// end exA

The formatter is similar to the one above, as TextBasedCitelet automatically passes options to us with an AnnotatedCitation:

@Override protected String format(Insertion _insertion) throws JCiteError, IOException {
  final String[] lines = stripIndentation( _insertion.text() ).split("\n");
  final StringBuilder fmt = new StringBuilder();
  String prefix = "DEMO:\t";
  if (_insertion instanceof AnnotatedCitation)
    prefix = ((AnnotatedCitation) _insertion).annotation() + ":\t";
  for (String line: lines) {
    fmt.append(prefix).append(line).append("\n");
    prefix = "\t";
  }
  return fmt.toString();
}

We do get a bit of help from some predefined helper methods, such as:

protected final String stripIndentation( String _fragment )
protected final String trimEmptyLines( String _fragment )
protected final String escapeXML( String _fragment )

Finally, here’s an example of the text-based demo citelet in action:

String cited = citer.process("A test string\n"
    + "with a citation:\n"
    + "<stripped>[demo:simplefile.txt:exA]</stripped>\n"
    + "and another one as in info block:\n"
    + "<stripped>[demo:simplefile.txt:exB;INFO]</stripped>");
assertEquals("A test string\n"
    + "with a citation:\n"
    + "DEMO:  Example A\n"
    + "  And a little more of example A.\n"
    + "\n"
    + "and another one as in info block:\n"
    + "INFO:  Example B\n"
    , cited);

Registering Citelets

JCite can auto-detect custom citelets from the .jar files on the classpath via a ServiceLoader for the JCiteletProvider interface, like so:

public static class MyProvider implements JCiteletProvider {
  public JCitelet getCitelet(JCite _jcite) {
    return new MyCitelet(_jcite);
  }
}

and a file called:

META-INF/services/ch.arrenbrecht.jcite.JCiteletProvider

containing a line like:

ch.arrenbrecht.jcite.JCitePlainExtensionTest$MyProvider