Question

I'm attempting to perform a very simply transform of Timed Text Markup Language (TTML) documents. Here's a minimalist example of a TTML file:

<?xml version="1.0" encoding="UTF-8"?>
<tt xml:lang="en" xmlns="http://www.w3.org/2006/04/ttaf1"
    xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling">
    <head>
    </head>
    <body>
        <div xml:lang="en" style="1">
            <p begin="00:00:00.20" dur="00:00:02.26">&gt;&gt; One time entry<br/>with a line break.</p>
        </div>
    </body>
</tt>

Note the document's default namespace. That's key to the problem I'm having.

Here is the transform I'm using. This is the whole thing, it's very simple.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
    xmlns:tt="http://www.w3.org/2006/04/ttaf1">
    <xsl:output method="text" indent="yes" />
    <xsl:strip-space elements="*" />
    <xsl:preserve-space elements="tt:p"/>

    <!-- The indentation of the close tag for the following node is crucial to the transformed layout. Don't move it! -->
    <xsl:template match="tt:p"><xsl:apply-templates />&#160;
</xsl:template>

    <xsl:template match="tt:p/text()"><xsl:copy />&#160;</xsl:template>

</xsl:stylesheet>

Our dataset has hundreds of documents and they don't all have the same default namespace. However, as you can see from the above XSLT, the transform is simple and based on the core data element <p /> so ultimately the namespace variations are unimportant.

The above XSL currently works with some of the source files since the XSLT file has an explicitly defined namespace prefix (tt). However, this doesn't work when encountering another source file with a different default namespace.

So I need to find a way to provide the XSLT with an arbitrary, unknown namespace value for a known prefix. I have code that can be used to find the source document default namespace:

XPathDocument sourceDoc = new XPathDocument("sourcefile.xml");
XPathNavigator sourceNav = sourceDoc.CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
string defNS = sourceNS.Single(n => n.Key =="").Value;

This correctly gives me http://www.w3.org/2006/04/ttaf1. However, I can't seem to figure out what to do with this value. I have searched and experimented for many hours trying to somehow provide the XslCompiledTransform instance the variable namespace. It appears that there's nothing within the area of the transform itself that can take it.

I've tried explicitly loading the XSLT file into an XmlDocument in order to manipulate the name table (after removing the explicit xmlns:tt="..." namespace declaration in the above XSLT):

XslCompiledTransform objXsl = new XslCompiledTransform();
StringWriter writer = new StringWriter();
XPathDocument sourceDoc;
XmlDocument xslDoc = new XmlDocument();
XPathNavigator sourceNav, xslNav;
XmlNamespaceManager xslNsManager;

sourceDoc = new XPathDocument("sourcefile.xml");
sourceNav = sourceDoc.CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);

xslDoc.Load("transform.xslt");

xslNsManager = new XmlNamespaceManager(xslDoc.NameTable);
xslNsManager.AddNamespace("tt", sourceNS.Single(n => n.Key =="").Value);

xslNav = xslDoc.CreateNavigator();
objXsl.Load(xslNav);

objXsl.Transform(sourceNav, null, writer);

This runs up until the actual transform, where I get an XslLoadException stating that Prefix 'tt' is not defined.

I'm at a loss at this point. Everything I can find from searching discusses putting the namespace into the XSLT document (which I already have and it works fine for one namespace value). I can't find anything in MSDN documentation or elsewhere that explains how to add the namespace definitions into the transform on the fly.

Anyone have any ideas?

No correct solution

OTHER TIPS

A colleague recommended that I just add/manipulate the variable namespace on the XSL document. My attempt using a name table change was headed in the right direction, but it wasn't working. Here's what I came up with based on his suggestion:

XslCompiledTransform xslXform = new XslCompiledTransform();
StringWriter writer = new StringWriter();
XmlDocument xslDoc = new XmlDocument();
XPathNavigator sourceNav;

sourceNav = new XPathDocument(sourceFile).CreateNavigator();
sourceNav.MoveToFollowing(XPathNodeType.Element);
var sourceNS = sourceNav.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml);
string ttNamespace = sourceNS.Single(n => n.Key == "").Value;

xslDoc.Load(xslFile);
xslDoc.DocumentElement.SetAttribute("xmlns:tt", ttNamespace);
xslXform.Load(xslDoc.CreateNavigator());
xslXform.Transform(sourceNav, null, writer);

This works but it feels a bit hacky to me. I think the core problem is that I don't understand the relationship between (Xml|XPath)Document types and the name table/namespace manager associated with them.

I try it , this work.

using System;
using System.Xml.Xsl;

class Sample {
    static public void Main(){
        XslCompiledTransform xslt = new XslCompiledTransform();
        xslt.Load("transform.xslt");
        xslt.Transform("sourcefile.xml", "result.txt");
    }
}

I think Namespace no problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top