Saturday, April 10, 2010

Outputting a keyword hierarchy in XML

With Tridion 2009 we saw the introduction of hierarchical keywords - aka Taxonomy, aka Intelligent Navigation - and with it a whole new world of possibilities for content classification.

However, in some cases, you really don't want all the intelligence around it and would much rather have just an "old style" xml hierarchy. If you tried this yourself, you probably figured out by now that the keyword hierarchy doesn't look so... hierarchical when viewed through the API. Not at all.

It looks rather flat.

I had to write a template recently with some information about every keyword in the hierarchy (including its metadata) for a site navigation, and keeping the keyword "parent/child'' relationships.

Here's how can you do it.

Category Navigation = engine.GetObject(NAVIGATION_CATEGORY) as Category;
using (MemoryStream ms = new MemoryStream())
{
    XmlTextWriter w = new XmlTextWriter(ms, new System.Text.UTF8Encoding(false));
    w.Indentation = 4;
    w.Formatting = Formatting.Indented;

    w.WriteStartDocument();
    w.WriteStartElement("Navigation");
    log.Debug("/Navigation created");

    Filter filter = new Filter();
    filter.Conditions.Add("Recursive", false);
    foreach (XmlNode RootChildren in Navigation.GetListKeywords(filter).SelectNodes("//*[@IsRoot='true']"))
    {
        Keyword RootKeyword = engine.GetObject(RootChildren.Attributes["ID"].Value) as Keyword;
        log.Debug("Generating XML for keyword " + RootKeyword.Title);
        w.WriteStartElement("Item");
        w.WriteAttributeString("ID", RootKeyword.Id);
        w.WriteAttributeString("Title", RootKeyword.Title);
        if (RootKeyword.Metadata != null)
        {
            WriteKeywordMeta(RootKeyword, w);
        }
        WriteChildrenXml(RootKeyword.Id.ToString(), w);
        w.WriteEndElement();
    }
    w.WriteEndElement();
    w.WriteEndDocument();
    w.Flush();
    w.Close();

    package.PushItem(Package.OutputName, package.CreateStringItem(ContentType.Xml, Encoding.UTF8.GetString(ms.ToArray())));
}

And the recursive "WriteChildrenXml" method would look more or less like this:

private void WriteChildrenXml(String RootKeywordId, XmlWriter writer)
{
    Keyword ParentKeyword = _engine.GetObject(RootKeywordId) as Keyword;
    Filter filter = new Filter();
    filter.Conditions.Add("Recursive", false);

    foreach (Keyword ChildKeyword in ParentKeyword.GetChildKeywords(filter))
    {
        writer.WriteStartElement("Item");
        writer.WriteAttributeString("ID", ChildKeyword.Id);
        writer.WriteAttributeString("Title", ChildKeyword.Title);
        if (ChildKeyword.Metadata != null)
        {
            WriteKeywordMeta(ChildKeyword, writer);
        }
        WriteChildrenXml(ChildKeyword.Id.ToString(), writer);
        writer.WriteEndElement();
    }
}
Nuno

12 comments:

vivek kwatra said...

What is implementation of the "WriteKeywordMeta"?

Nuno said...

I don't actually have that code anymore (at customer's repository) but it basically looped through the keyword's metadata field and added some elements to the XML output. nothing too fancy.

N

Praveen said...

I was trying to create a keyword as a child to another keyword, but then the "Parent keywords" field is empty and there is no provision for me to add a parent keyword info.
How do we make use of the "parent keywords" attribute in a keyword? and a keyword can have how many parents?

Nuno said...

Praveen, you can create child keywords by right-clicking a keyword and selecting New -> Keyword.

The interface will only allow you to create one parent per keyword, but the API supports more. You'll need to use the Tridion Object Model API to add parents to a keyword, and these parents will show in the interface correctly.

N

Praveen said...

Though I created a new keyword as you have mentioned through the interface, the parent keyword attribute is empty. What could be the reason?

Nuno said...

That's weird. I do see the parent keyword in the Parent field.

Are you on 2009 GA or SP1?

Does the keyword show correctly in the interface as a child of its parent keyword?

N

Praveen said...

I am on 2009 Tridion. How do I verify the SP version?

When I click on the parent keyword, it does show the child keyword with in it (but the attribute "parent keyword" is still empty).

Can we assign permissions for each field of the component/schema (make it read only/invisible/read-write etc)?

Nuno said...

Click on the SDL Tridion 2009 icon on top right, check the CME version number. 5.4.0.1046 is SP1.

You could have read the manual to figure out that security is only set at folder or structure group level.

There is an extension in wwww.SDLTridionWorld.com that shows how to make a field read/only, but this is not based on security.

N

Praveen said...

The CME version number is - 5.4.0.1046 as you have mentioned.

Nuno said...

That's SP1 then. Weird, on my system it shows.

Might be worth contacting customer support on that one.

N

Dylan said...

Hi Nuno,

I'm working with a client that is using this mechanism (creating an XML file to query the relationships and present lists of related items) in 2009 SP1.

The stated reason is that "to query the broker and parse the XML takes to long" and "the use of the static XML improves the response speed with a cached file and simple XSLT".

Whilst that's a reasonable work-around my concern is the dependancy on keeping the XML updated as keywords are added/updated between each other and content (we don't want to introduce the Event System just to implement what I feel is some sort of "work-around").

Since this post have you still used this approach or are there any 'best-practice' articles you're aware of in running keyword (related item) queries on the broker in 2009 SP1?

Thanks in advance..

Nuno said...

Hi Dylan,

I haven't used a different approach since (and I haven't done anything similar since this post neither). When I wrote this post I have a very simple use case for this, which was to build navigation from a hierarchy of Keywords instead of Structure Groups. For something more complex (like your use-case seems to be) I would certainly prefer to use the Taxonomy API and probably (if performance is such a real issue) cache the hell out of it - seems more forward-looking than having a page that needs to be republished very often when you already have the content you need on the delivery side.

Regarding performance - whenever I have serious performance issues I try to work with support/R&D to figure out what I'm doing wrong. I am sure Tridion developers want the software to be as fast as it can be, and sometimes there's little changes we can do in our implementation code that make worlds of difference.

With this said, I have seen performance issues on large taxonomies with many inter-keyword relationships, and the answer was that "the taxonomy is too complex"...

N