0

I am writing a processor for the Citation Style Language (CSL) in JavaScript.

Before I explain what my problem is, I must give some context first:

CSL is an XML specification for describing how to generate citations and bibliographies in a particular style (e.g. Harvard, APA, etc.)

Let's say I wanted to create my own citation style, e.g. the "Stack Exchange" style

Given the following XML pseudo code (an XML complying with the Citation Style Language specification would look a bit different but hopefully you get the idea.)

<citation>
  <author>
    <lastname suffix=", "/>
    <firstname first-letter-only="true" suffix="."/>
  </author>
  <year prefix=" (" suffix="). "/>
  <title suffix=". "/>
  <source prefix="Retrieved from "/>
</citation>

And the following data:

{ "author_lastname": "Doe",
  "author_firstname": "John",
  "title": "My paper title",
  "year": "2009",
  "source": "http://example.com" }

Then I should expect the following citation:

Doe, J. (2019). My paper title. Retrieved from http://example.com

I use XSLT to generate a JavaScript module out of an XML document. I then use this module as any regular Node.js modules to produce citations.

$ xsltproc generate-module.xsl stackexchange.xml >stackexchange.js

Then in Node.js:

var citation = require('./stackexchange.js');

citation({ "author_lastname": "Doe",
           "author_firstname": "John",
           "title": "My paper title",
           "year": "2009",
           "source": "http://example.com" });

//=> "Doe, J. (2019). My paper title. Retrieved from http://example.com"

In a nutshell, each node in a XML document is converted into a function that receives as its parameters:

  1. its attributes (if any)
  2. its children (as functions; if any)
  3. the data it needs to process

Let's imagine the following contrived example:

<a>
  <b>
    <c>
      <d value="foo"/>
    </c>
  </b>
</a>

The generated module would something like that:

// dummy-style.js
module.exports = () => a(b(c(d("foo"))));

Where:

  • d will simply return its value attribute
  • c will append the string 'c'
  • b will append the string 'b'
  • a will append the string 'a'

Example of usage:

var citation = require('./dummy-style.js');

citation();
//=> "foocba"

Now here's the challenge that I am facing!

The specification could say things like: « if d produces the string 'foo' then b must not append the string 'b' to its output »

This is where I'm struggling as I need to somehow keep track of which functions have been called and where they've been called. It's becoming clear to me that my functions cannot simply return strings; they must be wrapped with some sort of metadata.

It feels like I need to inspect my functions call stack, inspect that stack and apply some other rules before I can replay it. But I really don't know how to approach this.

Questions

  1. Am I facing an issue that is common when designing "compilers" or "processors"?
  2. Are there any design principles that I'm lacking?
  3. How would you tackle this issue?
  • I'm afraid this question is offtopic because you are asking a question about a very specific problem. I don't know of a specific pattern i could point you to; but i'd recommend you don't generate code from the CSL. Instead, write a CSL interpreter and package it along with the CSL source. In that interpreter you can build whatever mechanism you need with much less complexity as when generating that code. – marstato Jun 4 at 11:16
  • According to the Help Center I believe this question is actually on topic. Feel free to disagree and cast a vote to close this question. Thank you for your answer though. – customcommander Jun 4 at 11:31
  • Didn't cast a vote as I am unsure myself – marstato Jun 4 at 11:43
  • Using the compiler analogy, you could pass in a context as a 4th argument which answers those questions. – BobDalgleish Jun 4 at 14:18
  • @BobDalgleish Thank you for your answer. Would you mind elaborating a bit please? – customcommander Jun 4 at 14:21
0

Don't return strings.

Formatting is not about strings, but about markup. At a minimum, the value you return has to be wrapped in appropriate markup. The usual markup, obviously, is HTML, but it could be XML, XHTML, SGML, whatever. For instance, if the title field of an article is "Care & Feeding Of Itinerant Programmers", the text must be properly escaped so that it renders properly in a web browser, let's say. Similarly, there may be other embedded markup such as in: "Applications of X2-Curve Fitting".

Instead, wrap the contained value with metadata that at least indicates whether it has already been formatted and all characters that should be escaped are properly escaped, or a wrapper that says that this is raw text.

To answer one of your questions, the method d would wrap its contents with metadata that says "produced by d". The result could then be inspected by method b.

Pass In Context

You need to pass in the context because it affects handling at lower levels. An author entry is handled differently when part of a list of editors of a book than as an author of an article in that publication. A context might be "handling list of editors".

Context provides metadata on the way down the call tree, wrappers provide metadata on the way up the call tree.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.