/ob-ml-marklogic

org-babel integration with MarkLogic XQuery and (server side) JavaScript code blocks

Primary LanguageEmacs Lisp

README

This little hack allows you to place XQuery, JavaScript, or SPARQL code blocks in org-mode files and evaluate them with babel using MarkLogic server.

Setup

I’m working on getting this package into MELPA.

  1. Put these files on your load path.
  2. (require 'ob-ml-marklogic) to load all of the language features. (Alternatively, require only the languages you need.)
  3. Add some or all of ml-xquery, ml-javascript, and ml-xquery to the org-babel-load-languages:
    (org-babel-do-load-languages
     'org-babel-load-languages
     ; load all language marked with (lang . t).
     '((ml-xquery . t)
       (ml-javascript . t)
       (ml-sparql . t)
       …))
        
  4. Add the language/editing mode mappings to org-src-lang-modes:
    (add-to-list 'org-src-lang-modes '("ml-xquery" . xquery))
    (add-to-list 'org-src-lang-modes '("ml-javascript" . javascript))
    (add-to-list 'org-src-lang-modes '("ml-sparql" . sparql))
        
  5. Happy editing.

Configuration

Most of the configuration is done with header arguments. These can be specified at any level. The following header arguments are supported:

:ml-curl
The curl executable
:ml-host
The MarkLogic hostname
:ml-scheme
The URI scheme for requests
:ml-port
The port for requests
:ml-eval-path
The eval path
:ml-graphs-path
The SPARQL eval path
:ml-auth
Type of auth
:ml-username
Username
:ml-password
Password
:ml-output
Output buffer
:ml-save-output
Keep output buffer?

System defaults are stored in ob-ml-common-default-header-args in ob-ml-common.el. You’ll probably need to change some of them. The request URI is constructed by concatenation:

:ml-scheme "://" :ml-host ":" :ml-port :ml-*-path

You’ll probably never need to change the :ml-eval-path or :ml-graphs-path.

If you don’t specify :ml-auth, then the requests will be made without authentication. Setting :ml-save-output will prevent the temporary buffer that’s used to hold results from being deleted. That can be useful if something goes wrong.

Query parameters

You can also pass query parameters and variables to the query using the standard :var header. Variable names that start with “&” are passed to the eval endpoint. All other variable names are passed through to the underlying query.

For example:

:var startDate="2017-04-19T12:34:57"

Specifying the variable startDate passes it to the query (where it can be accessed by declaring it external). Alternatively:

:var &database="Documents"

Specifying the variable &database passes it to the eval endpoint.

You can specify as many variables as you wish. You’ll no doubt get errors if you pass things that the endpoint or query aren’t expecting. I have no idea how well my code plays with advanced org-mode features like reference to other named code blocks. If you see something weird, please open an issue.

Examples

Note: The GitHub rendering of this section is a bit misleading as it elides the begin_src/end_src lines. Check the README.org file for a more accurate picture.

XQuery:

xquery version "1.0-ml";

declare default function namespace "http://www.w3.org/2005/xpath-functions";

declare option xdmp:mapping "false";

declare variable $startDate external;

let $date   := $startDate cast as xs:dateTime
let $diff   := current-dateTime() - $date
return
  current-dateTime() - $date
#+RESULTS:
: -P347DT22H38.695693S

JavaScript:

var jsearch     = require('/MarkLogic/jsearch.sjs'),
    collection  = jsearch.collections,
    qbe         = jsearch.byExample;

collection('muppets')
  .documents()
  .where(qbe({ name: 'waldorf'}))
  .result();
#+RESULTS:
: {
:     "results": null,
:     "estimate": 0
: }

SPARQL:

PREFIX rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX c:     <http://nwalsh.com/rdf/contacts#>
PREFIX v:     <http://nwalsh.com/rdf/vCard#>

SELECT ?rdf
WHERE
{
  ?rdf rdf:type c:Thing
}
#+RESULTS:
{
    "head": {
        "vars": [
            "rdf"
        ]
    },
    "results": {
        "bindings": [
            {
                "rdf": {
                    "type": "uri",
                    "value": "http:\/\/norman.walsh.name\/knows\/what\/DOM"
                }
            }
        ]
    }
}

If the result is JSON or XML (and consists of a single part, it will be reformatted for legibility.)

let $_ := <doc><foo><bar><baz></baz></bar></foo></doc>
return
  $_
#+RESULTS:
: <doc>
:   <foo>
:     <bar>
:       <baz/>
:     </bar>
:   </foo>
: </doc>

Feedback welcome

Kudos, complaints, bug reports, etc. most welcome. Please open an issue for bugs or observations of failure in my Emacs lisp style.