Meaningful XML

Author: Dave Cassel  |  Category: Software Development

Another day, another code review. Today I came across a function that generates non-meaningful XML. This is something that at first glance feels like it has a nice structure to it, but there’s a better way. First, let take a look at the XML (I’ve changed the XML and the code to make it anonymous, but preserved the idea; the original had several distinct param elements):

<search-criteria id="1">
  <param key="keywords">food</param>
  <param key="project-name">Important Work</param>
  <param key="date">2011-12-31</param>
</search-criteria>

As you can see, the function is creating generic element names (“param”) with an attribute designed to give it meaning. And here’s the function:

declare function local:create-search-criteria($params as map:map) as element(search-criteria)
{
  let $form-elements := (
    "project-name",
    "date"
  )  

  let $keywords as xs:string := (map:get($params, "keyword"), "")[1] 
  let $grant-id as xs:unsignedLong := local:get-next-id()

  return
    <search-criteria id="{ $grant-id }">
      <param key="keywords">{ $keywords }</param>
      {
        for $key in $form-elements
        let $value := map:get($params, $key)
        return 
          <param key="{ $key }">{ $value }</param>
      }
    </search-criteria>
};

Why Not?

It’s certainly fair to ask, “Why not do it this way?” After all, we can still use XPath to select a specific part of the XML. For instance:

local:create-search-criteria($params)/param[@key = "date"]

That’s good enough to get the date parameter element.

While that’s true, I can think of two arguments against doing it this way, one specific to MarkLogic, one that is perhaps a purist’s argument.

Meaningful Structure

The first reason I would avoid this structure is simply that there is a way to express it that is more concise without being unclear. As with writing, if you can say the same thing more briefly without losing clarity, then it is probably a good idea. In this situation, the param element name does not contribute to our understanding of the content.

Indexes

In MarkLogic, there is no way to set up a range element index that is dependent on the value of an attribute. For instance, we could not set up a range element index on param that only had the values where the key parameter is “date”. A range element attribute index doesn’t solve the problem, because that indexes the values in the attribute itself.

Revised

Let’s start with how we might like the XML to look:

<search-criteria id="1">
  <keyword>food</keyword>
  <project-name>Important Work</project-name>
  <date>2011-12-31</date>
</search-criteria>

Now our structure is simple and concise and it will be easy to set up some useful indexes.

For the function, we can let the $param map govern the output structure.

declare function local:create-search-criteria($params as map:map) as element(search-criteria)
{
  <search-criteria id="{ local:get-next-id() }">
  {
    for $key in map:keys($params)
    return element { $key } { map:get($params, $key) }
  }
  </search-criteria>
};

Or if we prefer to control which fields get pulled out of the map, that’s a simple matter of setting up a sequence, similar to the original.

declare function local:create-search-criteria($params as map:map) as element(search-criteria)
{
  <search-criteria id="{ local:get-next-id() }">
  {
    for $key in ("keyword", "project", "date")
    return element { $key } { map:get($params, $key) }
  }
  </search-criteria>
};

Tags: , ,

Leave a Reply