Loading TOC...
REST Application Developer's Guide (PDF)

REST Application Developer's Guide — Chapter 8

Working With Content Transformations

The REST Client API allows you to create custom content transformations and apply them during operations such as document ingestion and search. Transforms can be implemented as XQuery library modules, XSLT stylesheets, or server-side JavaScript modules, and can accept transform-specific parameters. Transforms can be applied on any REST Client API service that accepts a transform parameter.

This chapter covers the following topics:

Writing Transformations

This section covers the following topics related to authoring transform functions:

Guidelines for Writing Transforms

Keep the following guidelines in mind when authoring a transform:

  • A transform is one step in a document processing pipeline controlled by the REST Client API framework. As such, transforms typically should not have side-effects.
  • A transform operates on an in-memory input document. MarkLogic Server builtin functions that operate on in-database documents, such as xdmp:node-replace or xdmp.nodeReplace, cannot be applied directly to the input document. To modify the structure of the input document, perform the modifications during construction of a copy of the input content.
  • A transform executes in the transaction context of the HTTP request on whose behalf it is called.
  • A transform should not alter the HTTP request context. For example, you should not set the response code or headers directly. The REST Client API framework manages the request context.
  • Report errors by throwing one of the exceptions provided by the REST Client API. For details, see Reporting Errors.

Writing JavaScript Transformations

To create a JavaScript transformation, implement a server-side JavaScript module that implements function with the following interface and exports it with the name transform:

function yourTransformName(context, params, content) 
{
    ...
};

exports.transform = yourTransformName;

Your implementation must use the MarkLogic server-side JavaScript dialect. For details, see the JavaScript Reference Guide.

Resource service extensions, transforms, row mappers and reducers, and other hooks on the REST API cannot be implemented as JavaScript MJS modules.

The table below describes the parameters:

Parameter Description
context
An object containing service request context information such as input document types and URIs, and output types accepted by the caller. For details, see Context Map Keys.
params
An object containing the extension-specific parameters, if any. The object contains an object property for each request parameter with a trans: prefix. If you specify the same parameter name more than once, the property value is an array. You can optionally define parameter name and type metadata when installing the extension. For details, see Installing Transformations.
content
The input document to which to apply the transformation. For example, the document being inserted into the database if the transform is applied during ingestion, or a search:response if the transform is applied during a search. For details, see Expected Input and Output.

Note that content is document node, not a JavaScript object. If content is a JSON document, you must call toObject on it before you can manipulate the content as a JavaScript object or modify it. For example:

const mutableDoc = content.toObject();

The expected output from your transform function depends upon the situation in which it is used. For details, see Expected Input and Output.

For a complete example, see JavaScript Example: Adding a JSON Property During Ingestion.

Writing XQuery Transformations

To create an XQuery transformation, implement an XQuery library module in the following namespace:

http://marklogic.com/rest-api/transform/yourTransformName

Where yourTransformName is the name used to refer to the transform in subsequent REST requests, such as installing and applying the transform.

The module must implement a public function called transform with the following interface:

declare function yourNS:transform(
  $context as map:map,
  $params as map:map,
  $content as document-node())
as document-node()

The table below describes the parameters:

Parameter Description
$context
Service request context information such as input document type and URI, and output types accepted by the caller. For details, see Context Map Keys.
$params
Transformation specific parameters. The map contains a key for each transform parameter passed to the request for which the transform is applied. Define parameters when installing the transformation. For details, see Installing Transformations.
$content
The input document to which to apply the transformation. For example, the document being inserted into the database if the transform is applied during ingestion, or a search:response if the transform is applied during a search. For details, see Expected Input and Output.

The map:map type is a MarkLogic Server extended XQuery type representing a key-value map. For details, see Using the map Functions to Create Name-Value Maps in the Application Developer's Guide.

The expected output from your transform function depends upon the situation in which it is used. For details, see Expected Input and Output.

For a complete example, see XQuery Example: Adding an Attribute During Ingestion.

Writing XSLT Transformations

To create an XSLT transformation, implement an XSLT stylesheet that accepts the following parameters:

Parameter Type Description
$context map:map Service request context information such as input document type and URI, and output types accepted by the caller. For details, see Context Map Keys.
$params map:map Transformation specific parameters. The map contains a key for each transform parameter passed to the request for which the transform is applied. Define any parameters when installing the transformation. For details, see Installing Transformations.

The map:map type is a MarkLogic Server extended XQuery type representing a key-value map. For details, see Using the map Functions to Create Name-Value Maps in the Application Developer's Guide.

To use the map type in your stylesheet, include the namespace http://marklogic.com/xdmp/map. Reference the map contents using the map:get XQuery function.

The following example illustrates declaring the map namespace and $context and $params parameters, and referencing values in the $params map:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:example="http://marklogic.com/rest-api/example/transform"
    xmlns:map="http://marklogic.com/xdmp/map">
<xsl:param name="context" as="map:map"/>
<xsl:param name="params"  as="map:map"/>
<xsl:template match="/*">
  <xsl:copy>
    <xsl:attribute name='{(map:get($params,"name"),"transformed")[1]}'
      select='(map:get($params,"value"),"UNDEFINED")[1]'/>
...

The expected output from your transform function depends upon the situation in which it is used. For details, see Expected Input and Output.

For a complete example, see XSLT Example: Adding an Attribute During Ingestion.

Expected Input and Output

The input document ($content) to a transform is always a document. The format and contents of the document depend on where the transform is used. The following table summarizes the expected input and output for a transform function for the supported use cases.

Transform Use Case Input Output
Document Ingestion The input document, as received from the client application. In JavaScript, this is an immutable document node. Use toObject to create a mutable copy. The document as it is to be stored in the database.
Document Retrieval The document as stored in the database. The document as it is to be returned to the client application.
Search An XML or JSON document node representing either the search response or matching documents, depending on the operation. The document to put in the response body. The data is returned to the client as-is.
Alert Match An XML document with a <rapi:rules/> root element. The document to put in the response body. The data is returned to the client as-is.

A transform applied to a search operation should expect to receive either a search results summary, such as a <search:response/>, or a document matched by the query. A search request can return a search result summary, a set of matching documents, or both, depending on the request parameters, Accept headers, and query options. For details, see What to Expect as Input Content.

Reporting Errors

This section discusses proper error reporting in your transform or service resource extension implementation. Use the approach described here for both XQuery and XSLT implementations. For more information about error reporting conventions, see Error Reporting.

To report an error from your implementation, use the fn:error XQuery function with the RESTAPI-SRVEXERR error code, and provide the HTTP response code and additional error information in the 3rd parameter to fn:error.

The 3rd parameter to fn:error should be a sequence of the form ("status-code", "status-message", "response-payload"). That is, when using fn:error to raise RESTAPI-SRVEXERR, the $data parameter to fn:error is a sequence with the following members, all optional:

  • HTTP status code. Default: 400.
  • HTTP status message. Default: Bad Request.
  • Response payload (optional). This should either be plain text or data in a format compatible with the default error format configured for the REST API instance.

    Best practice is to use RESTAPI-SRVEXERR. If you report any other error or raise any other exception, it is reported to the calling application as a 500 Server Internal Error.

For example, this resource extension function raises RESTAPI-SRVEXERR if the input content type is not as expected:

declare function example:transform(
    $context as map:map,
    $params  as map:map,
    $input   as document-node()
) as document-node()
{
    (: get 'input-types' to use in content negotiation :)
    let $input-types := map:get($context,"input-types")
    let $negotiate :=
        if ($input-types = "application/xml")
        then () (: process, insert/update :)
        else fn:error((),"RESTAPI-SRVEXERR",
          ("415","tsk tsk","whoops"))
    return document { "Done"}  (: may return a document node :)
};

If a PUT request is made to the transform with an unexpected content type, the fn:error call causes the request to fail with a status 415 and to include the additional error description in the response body:

HTTP/1.1 415 tsk tsk
Content-type: application/xml
Server: MarkLogic
Set-Cookie: SessionID=714070bdf4076536; path=/
Content-Length: 62
Connection: close
{
  "message": "js-example: response with invalid 400 status",
  "statusCode": 415,
  "body": {
    "errorResponse": {
      "statusCode": 415,
      "status": "tsk tsk",
      "messageCode": "RESTAPI-SRVEXERR",
      "message": "whoops"
    }
  }
}

Context Map Keys

The context map parameter made available to transformations can contain the following keys:

XQuery Map Key JavaScript Property Name Description
input-type
inputType
The MIME type of the input document (the content parameter).
uri
uri
The URI of the input document, in a read or write transform.
accept-types
acceptTypes
On a read transform, the MIME types accepted by the requestor. Multiple MIME types are separated by whitespace. If there are multiple Accept types, the value is a JavaScript array or an XQuery sequence.
output-type
outputType
The expected output document MIME type. It is set for you by the REST API, but if your XQuery or XSLT read or write transform supports content negotiation, the transform function can set/modify this value.

Controlling Transaction Mode

This section discusses the default transaction mode under which your content transformation is evaluated, and how you can override the default behavior when using an XQuery transform. You cannot control transaction context for a JavaScript transform.

This discussion assumes you are familiar with the MarkLogic Server transaction mode concept; for details, see Transaction Mode in Application Developer's Guide.

The default transaction mode for a transformation function depends on the request on whose behalf it is applied:

  • PUT or POST to /documents: update
  • GET /documents: query
  • GET or POST for a query service (/search, /qbe, /values): query

Choosing the appropriate transaction mode can improve the resource utilization of your implementation. For example, an update transaction acquires exclusive locks on documents, as described in Update Transactions: Readers/Writers Locks in Application Developer's Guide. If you know that your transformation is actually a read-only operation, you can override the default transaction mode so that the function is evaluated in query transaction mode and does not acquire exclusive locks.

To override the default behavior, include a transaction mode annotation in the declaration of your function:

declare %rapi:transaction-mode("the-mode") function yourNS:transform(...)

The value for %rapi:transaction-mode can be either update or query.

For example, to specify query transaction mode for your transform function, use the following declaration:

declare %rapi:transaction-mode("query") function example:transform(
  $context as map:map,
  $params as map:map,
  $content as document-node())
as document-node()
{...};

Installing Transformations

This section covers the following topics:

Basic Installation

To install or update a transformation, use PUT /v1/config/transforms/{name}. Send a PUT request with a URL of the following form, with the transform code in the request body:

http://host:port/version/config/transforms/yourTransformName

Where yourTransformName is the name used when administering and applying the transformation. You can optionally include informational metadata about your transform during installation; for details, see Including Transform Metadata.

If you are installing an XQuery transform, yourTransformName must match the leaf name in the transform module namespace declaration. For example:

xquery version "1.0-ml";
module namespace example = 
  "http://marklogic.com/rest-api/transform/yourTransformName";
...

Set the Content-type of the request to one of the following, depending on the language in which you implement your transformation:

  • XQuery: application/xquery
  • JavaScript: application/vnd.marklogic-javascript
  • XSLT: application/xslt+xml

For example, the following command installs a JavaScript transform under the name js-example:

# Windows users, see Modifying the Example Commands for Windows 
$ curl --anyauth --user user:password -X PUT -i \
    --data-binary @"./trans-ex.sjs" \
    -H "Content-type: application/vnd.marklogic-javascript" \
    'http://localhost:8000/LATEST/config/transforms/js-example'

If your transform requires additional assets such as user-defined XQuery or JavaScript library modules, you must install them separately before installing your transform. For details, see Using a Resource Service Extension.

For a complete example, see one of the following topics:

Including Transform Metadata

You can optionally include metadata for provider, version, title, description and caller-supplied parameters as request parameters during installation. Metadata can be retrieved using GET /v1/config/transforms. See Discovering Transformations.

For example, the following transform installation includes metadata about the provider:

http://host:port/version/config/transforms/example?provider=MarkLogic%20Corporation

The optional parameter metadata identifies the name, type, and cardinality of parameters the client can supply when apply the transform to a read, write or search. Include the parameter metadata as request parameters of the following form:

trans:paramName=paramTypeAndCardinality

Where paramName is the name of the parameter and paramTypeAndCardinality reflects the type and cardinality the parameter would have in an XQuery function declaration.

For example, string? means a string type parameter which may appear 0 or 1 time. The following URL illustrates a transform called example with an optional string parameter named reviewer:

http://host:port/version/config/transforms/example?trans:reviewer=string\?

Applying Transformations

All REST Client API services that support transformations use the following mechanism for specifying application of a transformation:

  1. Specify the name of an installed transform as the value of the transform request parameter:
    transform=transformName
  2. If the transform expects parameters, specify each parameter with a request parameter of the form:
    trans:paramName=value

For example, to apply a transform during document insertion, you can send a PUT request to the /documents service with a transform request parameter. Assuming the transform called example expects one parameter named reviewer, the PUT request URL might look like the following:

http://host:port/version/documents?uri=/doc/theDoc.xml&transform=example&trans:reviewer=me

You could apply the same transformation during document retrieval by sending a GET request to the /documents service with the same URL.

For a complete example, see one of the following topics:

Discovering Transformations

Use GET /v1/config/transforms to discover the name, interface, and other metadata about installed transformations. Send a GET request to the /config/transforms service of the form:

http://host:port/version/config/transforms

MarkLogic Server returns a summary of the installed transforms in XML or JSON. The default summary format is XML. Use the Accept header or format request parameter to select the output content type. For details, see Controlling Input and Output Content Type.

By default, this request rebuilds the transform metadata each time it is called to ensure the metadata is up to date. If you find this refresh makes discovery take too long, you can disable the refresh by setting the refresh request parameter to false. Disabling the refresh can result in this request returning inaccurate information, but it does not affect the freshness or availability of the implementation of any transforms.

The following example requests an XML report:

# Windows users, see Modifying the Example Commands for Windows 
$ curl --anyauth --user user:password -X GET \
    -H "Accept: application/xml" \
    http://localhost:8000/LATEST/config/transforms
...
HTTP/1.1 200 Transform List Retrieved
Content-type: application/xml
Server: MarkLogic
Content-Length: 348
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<rapi:transforms xmlns:rapi="http://marklogic.com/rest-api">
  <rapi:transform>
    <rapi:name>add-attr-xsl</rapi:name>
    <rapi:title/>
    <rapi:version/>
    <rapi:provider-name>MarkLogic Corporation</rapi:provider-name>
    <rapi:description/>
    <rapi:transform-parameters>
      <rapi:parameter>
        <rapi:parameter-name>value</rapi:parameter-name>
        <rapi:parameter-type>string?</rapi:parameter-type>
      </rapi:parameter>
      <rapi:parameter>
        <rapi:parameter-name>name</rapi:parameter-name>
        <rapi:parameter-type>string?</rapi:parameter-type>
      </rapi:parameter>
    </rapi:transform-parameters>
    <rapi:transform-source>
      /v1/transforms/add-attr
    </rapi:transform-source>
  </rapi:transform>
</rapi:transforms>

Retrieving the Implementation of a Transformation

To retrieve the XQuery code or XSLT stylesheet that implements a transformation, send a GET request to the /config/transforms/{name} service of the form:

http://host:port/version/config/transforms/transformName

Where transformName is the name under which the extension is installed.

Set the request Accept header to one of the following MIME types. The type must match the implementation language of the transform.

  • XQuery: application/xquery
  • JavaScript: application/vnd.marklogic-javascript
  • XSLT: application/xslt+xml

MarkLogic Server returns the XQuery library module or XSLT stylesheet that implements the transform in the response body. For example:

# Windows users, see Modifying the Example Commands for Windows 
$ curl --anyauth --user user:password -X GET \
    -H "Accept: application/xquery" \
    http://localhost:8000/LATEST/config/transforms/example
...
xquery version "1.0-ml";
module namespace example="http://marklogic.com/rest-api/transform/example";
declare function example:transform( ...

JavaScript Example: Adding a JSON Property During Ingestion

This example demonstrates a transformation that adds a property to a JSON document. The first example in this section adds a property with a fixed name and a value corresponding to the current dateTime. The second example adds a property corresponding to a name and value passed in as transform parameters.

When used as a read transform, the following transform adds a property named readTimestamp to the content returned to the client. When used as a write transform, it adds a property named writeTimestamp to the document stored in the database. In both cases, the value of the property is the dateTime at the time of the operation. If the input document is not JSON, the content is unchanged on both read and write.

Follow these steps to exercise the transform:

  1. Copy the following code into a file called trans-ex.sjs.
    function insertTimestamp(context, params, content)
    {
      if (context.inputType.search('json') >= 0) {
        const result = content.toObject();
        if (context.acceptTypes) {                 /* read */
          result.readTimestamp = fn.currentDateTime();
        } else {                                   /* write */
          result.writeTimestamp = fn.currentDateTime();
        }
        return result;
      } else {
        /* Pass thru for non-JSON documents */
        return content;
      }
    };
    
    exports.transform = insertTimestamp;
  2. Send a PUT /v1/config/transforms/{name} request to install the transform with the name js-example. For example:
    # Windows users, see Modifying the Example Commands for Windows 
    $ curl --anyauth --user user:password -X PUT -i \
        --data-binary @"./trans-ex.sjs" \
        -H "Content-type: application/vnd.marklogic-javascript" \
        'http://localhost:8000/LATEST/config/transforms/js-example'
  3. Optionally, verify installation of the transformation using GET /v1/config/transforms. For example:
    $ curl --anyauth --user user:password -X GET \
        -H "Accept: application/json" \
        http://localhost:8000/LATEST/config/transforms
    { "transforms": {
        "transform": [
          {
            "name": "js-example",
            "source-format": "javascript",
            "transform-parameters": "",
            "transform-source": "/v1/config/transforms/example"
          }
    ] }
  4. Send a PUT /v1/documents request to insert a document into the database, using the transform to add the writeTimestamp property. For example:
    $ curl --anyauth --user user:password -X PUT -d'{"name":"value"}' \
        -H "Content-type: application/json" \
        'http://localhost:8000/LATEST/documents?uri=/transforms/example.json&transform=js-example'
  5. Send a GET /v1/documents request to retrieve the inserted document. Note the added writeTimestamp attribute. For example:
    $ curl --anyauth --user user:password -X GET \
        -H "Accept: application/json" \
        http://localhost:8000/LATEST/documents?uri=/transforms/example.json
    { "key":"value", 
      "writeTimestamp":"2014-11-25T16:59:00.459241-08:00"
    }
  6. Send a second GET /v1/documents request to retrieve the inserted documents and apply the transform as a read transform. Note the added readTimestamp property. For example:
    $ curl --anyauth --user user:password -X GET \
        -H "Accept: application/json" \
        'http://localhost:8000/LATEST/documents?uri=/transforms/example.json&transform=js-example'
    { "key":"value", 
      "writeTimestamp":"2014-11-25T16:59:00.459241-08:00",
      "readTimestamp":"2014-11-25T17:38:28.417881-08:00"
    }

    The writeTimestamp is unchanged since it is part of the document. The readTimestamp is an artifact of the read, rather than part of the document. It will change each time you apply the transform during the read.

The following function is an alternative transform that demonstrates passing in transform parameters. For each parameter passed in via trans:name=value, the transform inserts a JSON property with the same name and value. The transform is applicable to both read and write operations. As before, non-JSON documents are unchanged.

function insertProperty(context, params, content)
{
  if (context.inputType.search('json') >= 0 && params) {
    const result = content.toObject();
    Object.keys(params).forEach(function(key) {
      result[key] = params[key];
    });
    return result;
  } else {
    // Pass thru unchanged
    return content;
  }
};

exports.transform = insertProperty;

The following example commands demonstrate using this transform on a read operation, assuming the transform is installed with the name ex-params:

$ curl --anyauth --user user:password -X GET \
    -H "Accept: application/json" -i \
    'http://localhost:8000/LATEST/documents?uri=/transforms/example.json&transform=ex-params&trans:prop1=42&trans:prop2=dog'
...
{ "key":"value", 
  "writeTimestamp":"2014-11-25T16:59:00.459241-08:00", 
  "prop1":"42", 
  "prop2":"dog" 
}

You can specify the same parameter more than once. For example, if the request parameter prop1 is supplied more than once, the value of prop1 is an array.

http://localhost:8000/LATEST/documents?...&trans:prop1=42&trans:prop1=43
==>
{ ..., 
  "prop1":["42", "43"]
}

XQuery Example: Adding an Attribute During Ingestion

This example demonstrates using a transformation to add an attribute to the root node of an XML document during ingestion. The name and value of the added attribute are passed in as parameters to the transform function. You can use the same transform for document retrieval by applying it to a GET request instead of a PUT.

The transformation XQuery module shown below reads the attribute name and value from parameters, and then makes a copy of the input document, adding the requested attribute to the root node. The transformation cannot perform direct transformations on the input XML, such as calling xdmp:node-replace, because the document has not yet been inserted into the database when the transform function runs.

xquery version "1.0-ml";
module namespace example =
  "http://marklogic.com/rest-api/transform/add-attr";

declare function example:transform(
  $context as map:map,
  $params as map:map,
  $content as document-node()
) as document-node()
{
  if (fn:empty($content/*)) then $content
  else
    let $value := (map:get($params,"value"),"UNDEFINED")[1]
    let $name := (map:get($params, "name"), "transformed")[1]
    let $root  := $content/*
    return document {
      $root/preceding-sibling::node(),
      element {fn:name($root)} {
        attribute { fn:QName("", $name) } {$value},
        $root/@*,
        $root/node()
      },
      $root/following-sibling::node()
    }
};

To exercise this example:

  1. Cut and paste the code above into a file called add-attr.xqy.
  2. Make a PUT request to the /config/transforms/{name} service to install the transform as add-attr, accepting 2 string parameters called name and value. For example:
    # Windows users, see Modifying the Example Commands for Windows 
    $ curl --anyauth --user user:password -X PUT -d@"./add-attr.xqy" \
        -H "Content-type: application/xquery" \
        'http://localhost:8000/LATEST/config/transforms/add-attr?trans:name=string\?&trans:value=string\?'
  3. Optionally, verify installation of the transformation by making a GET request to the /config/transforms service. For example:
    $ curl --anyauth --user user:password -X GET -i \
        -H "Accept: application/xml" \
        http://localhost:8000/LATEST/config/transforms
    ...
    HTTP/1.1 200 Transform List Retrieved
    Content-type: application/xml
    Server: MarkLogic
    Content-Length: 348
    Connection: close
    
    <?xml version="1.0" encoding="UTF-8"?>
    <rapi:transforms xmlns:rapi="http://marklogic.com/rest-api">
      <rapi:transform>
        <rapi:name>add-attr</rapi:name>
        <rapi:source-format>xquery</rapi:source-format>
        <rapi:title/>
        <rapi:version/>
        <rapi:provider-name/>
        <rapi:description/>
        <rapi:transform-parameters>
          <rapi:parameter>
            <rapi:parameter-name>value</rapi:parameter-name>
            <rapi:parameter-type>string?</rapi:parameter-type>
          </rapi:parameter>
          <rapi:parameter>
            <rapi:parameter-name>name</rapi:parameter-name>
            <rapi:parameter-type>string?</rapi:parameter-type>
          </rapi:parameter>
        </rapi:transform-parameters>
        <rapi:transform-source>
          /v1/transforms/add-attr
        </rapi:transform-source>
      </rapi:transform>
    </rapi:transforms>
  4. Make a PUT request to the /documents service to insert a document into the database, using the add-attr transform to add the attribute example with a value of new. For example:
    $ curl --anyauth --user user:password -X PUT -d"<data/>" \
        'http://localhost:8000/LATEST/documents?uri=/doc/transformed.xml&transform=add-attr&trans:name=example&trans:value=new'
  5. Make a GET request to the /documents service to retrieve the inserted document. Note the added example attribute. For example:
    $ curl --anyauth --user user:password -X GET \
        http://localhost:8000/LATEST/documents?uri=/doc/transformed.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <data example="new"/>

XSLT Example: Adding an Attribute During Ingestion

This example demonstrates using a transformation to add an attribute to the root node of an XML document during ingestion. The name and value of the added attribute are passed in as parameters to the stylesheet. You can use the same transform for document retrieval by applying it to a GET request instead of a PUT.

The transformation stylesheet shown below reads the attribute name and value from parameters, and then makes a copy of the input document, adding the requested attribute to the root node.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:example="http://marklogic.com/rest-api/example/transform"
    xmlns:map="http://marklogic.com/xdmp/map">
<xsl:param name="context" as="map:map"/>
<xsl:param name="params"  as="map:map"/>
<xsl:template match="/*">
    <xsl:copy>
        <xsl:attribute name='{(map:get($params,"name"),"transformed")[1]}'
            select='(map:get($params,"value"),"UNDEFINED")[1]'/>
        <xsl:copy-of select="@*"/>
        <xsl:copy-of select="node()"/>
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

To exercise this example:

  1. Create a REST Client API instance on port 8003. For instructions, see Creating an Instance.
  2. Cut and paste the stylesheet above into a file called add-attr.xsl.
  3. Make a PUT request to the /config/transforms/{name} service to install the transform as add-attr-xsl, accepting 2 string parameters called name and value. For example:
    # Windows users, see Modifying the Example Commands for Windows 
    $ curl --anyauth --user user:password -X PUT -d@"./add-attr.xsl" \
        -H "Content-type: application/xslt+xml" \
        'http://localhost:8000/LATEST/config/transforms/add-attr-xsl?trans:name=string\?&trans:value=string\?'
  4. Optionally, verify installation of the transformation by sending a GET /v1/config/transforms request. For example:
    $ curl --anyauth --user user:password -X GET \
        -H "Accept: application/xml" \
        http://localhost:8000/LATEST/config/transforms
    ...
    HTTP/1.1 200 Transform List Retrieved
    Content-type: application/xml
    Server: MarkLogic
    Content-Length: 348
    Connection: close
    
    <?xml version="1.0" encoding="UTF-8"?>
    <rapi:transforms xmlns:rapi="http://marklogic.com/rest-api">
      <rapi:transform>
        <rapi:name>add-attr-xsl</rapi:name>
        <rapi:source-format>xslt</rapi:source-format>
        <rapi:title/>
        <rapi:version/>
        <rapi:provider-name/>
        <rapi:description/>
        <rapi:transform-parameters>
          <rapi:parameter>
            <rapi:parameter-name>value</rapi:parameter-name>
            <rapi:parameter-type>string?</rapi:parameter-type>
          </rapi:parameter>
          <rapi:parameter>
            <rapi:parameter-name>name</rapi:parameter-name>
            <rapi:parameter-type>string?</rapi:parameter-type>
          </rapi:parameter>
        </rapi:transform-parameters>
        <rapi:transform-source>
          /v1/transforms/add-attr
        </rapi:transform-source>
      </rapi:transform>
    </rapi:transforms>
  5. Send a PUT:/v1/documents request to insert a document into the database, using the add-attr-xsl transform to add the attribute example with a value of new. For example:
    $ curl --anyauth --user user:password -X PUT -d"<data/>" \
        'http://localhost:8000/LATEST/documents?uri=/doc/transformed.xml&transform=add-attr-xsl&trans:name=example&trans:value=new'
  6. Send a GET /v1/documents request to retrieve the inserted document. Note the added example attribute. For example:
    $ curl --anyauth --user user:password -X GET \
        http://localhost:8000/LATEST/documents?uri=/doc/transformed.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <data example="new"/>

XQuery Example: Modifying Document Type During Ingestion

This example is a write transform that converts JSON input into XML, inserting an XML document into the database. It also demonstrates modifying the context map to change the document URI.

When you insert a document with a PUT or POST request to the /documents service and specify a transform function name, the MIME type in the Content-type request header takes precedence over the URI extension in determining the format of the data in the request body.

The following transform takes advantage of this behavior to accept JSON input and convert it to XML. The transform also modifies the URI to match, if necessary: If the URI has a .json URI suffix, the URI is modified to have a .xml suffix by modifying the value of the uri key in the $context map.

xquery version "1.0-ml";
module namespace xqjson2xml =
  "http://marklogic.com/rest-api/transform/xqjson2xml";

import module namespace json="http://marklogic.com/xdmp/json"
 at "/MarkLogic/json/json.xqy";


declare function xqjson2xml:transform(
  $context as map:map,
  $params as map:map,
  $content as document-node()
) as document-node()
{
  if (fn:contains(map:get($context, "input-type"), "json"))
  then (
    map:put($context, "output-type", "application/xml"),
    let $uri := map:get($context, "uri")
    return
      if (fn:ends-with($uri, ".json")) then 
        map:put($context, "uri", 
                fn:concat(fn:substring-before($uri, ".json"), ".xml"))
      else (),
    document {json:transform-from-json($content)}
  )
  else $content
};

To exercise this example:

  1. Cut and paste the code above into a file called json2xml.xqy.
  2. Make a PUT request to the /config/transforms/{name} service to install the transform as xqjson2xm. For example:
    # Windows users, see Modifying the Example Commands for Windows 
    $ curl --anyauth --user user:password -X PUT -d@"./json2xml.xqy" \
        -H "Content-type: application/xquery" \
        'http://localhost:8000/LATEST/config/transforms/xqjson2xml'
  3. Optionally, verify installation of the transformation by making a GET request to the /config/transforms service. For example:
    $ curl --anyauth --user user:password -X GET -i \
        -H "Accept: application/xml" \
        http://localhost:8000/LATEST/config/transforms
  4. Make a PUT request to the /documents service to insert a document into the database with a .xml extension, using the xqjson2xml transform. Note the request payload is JSON, but an XML document is inserted into the database.
    $ curl --anyauth --user user:password -i -X PUT \
        -d'{"a": "b"}' -H "Content-type: application/json" \
        'http://localhost:8000/LATEST/documents?uri=/doc/j2x.xml&transform=xqjson2xml'
  5. Make a GET request to the /documents service to retrieve the inserted XML document. For example:
    $ curl --anyauth --user user:password -X GET \
        'http://localhost:8000/LATEST/documents?uri=/doc/j2x.xml'
    ...
    HTTP/1.1 200 OK
    vnd.marklogic.document-format: xml
    Content-type: application/xml; charset=utf-8
    Server: MarkLogic
    Content-Length: 134
    Connection: Keep-Alive
    Keep-Alive: timeout=5
    
    <?xml version="1.0" encoding="UTF-8"?>
    <json type="object" xmlns="http://marklogic.com/xdmp/json/basic"><a type="string">b</a></json>
  6. Make another PUT request to the /documents service to insert a document into the database, using the xqjson2xml transform, but this time with a URI with a .json suffix. The transform modifies both the document type and the URI extension.
    $ curl --anyauth --user user:password -i -X PUT \
        -d'{"c": "d"}' -H "Content-type: application/json" \
        'http://localhost:8000/LATEST/documents?uri=/doc/j2x_2.json&transform=xqjson2xml'
  7. Make a GET request to the /documents service to retrieve the inserted XML document, using the modified URI. For example:
    $ curl --anyauth --user user:password -X GET \
        'http://localhost:8000/LATEST/documents?uri=/doc/j2x_2.xml'
    ...
    HTTP/1.1 200 OK
    vnd.marklogic.document-format: xml
    Content-type: application/xml; charset=utf-8
    Server: MarkLogic
    Content-Length: 134
    Connection: Keep-Alive
    Keep-Alive: timeout=5
    
    <?xml version="1.0" encoding="UTF-8"?>
    <json type="object" xmlns="http://marklogic.com/xdmp/json/basic"><c type="string">d</c></json>

Note that you can write a similar transformation in JavaScript.

« Previous chapter
Next chapter »