I'm copying components between versions of essentially the same schema, of which the latter has a stricter XSLT attached to it's RTF field.
The code (TOM.Net):
value = ((TextField)oldField).Value;
var def = (XhtmlFieldDefinition)field.Definition;
var tfm = new XslCompiledTransform();
tfm.Load(def.FilterXslt);
var settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (var sw = new StringWriter())
{
using (var sr = new StringReader(value))
{
using (var reader = XmlReader.Create(sr, settings))
{
tfm.Transform(reader, null, sw);
field.Value = sw.ToString();
}
}
}
The problems i'm getting:
If it runs "as is", all XML is stripped off. That's likely because input value has xmlns="http://www.w3.org/1999/xhtml" declaration on some (not all, by the way) of it's HTML tags, but in XSLT these tags are not bound to anything but default xmlns="http://www.w3.org/1999/XSL/Transform".
If i do an unfair move by exterminating "xmlns" in content, say, like this: new StringReader(value.Replace(@" xmlns=""http://www.w3.org/1999/xhtml""", string.Empty)), then transform works fine, but upon calling component.Save(), it says something like this:
XML validation error. Reason: The element 'text' in namespace 'http://www.unilever.com/general' has invalid child element 'h2'. List of possible elements expected: any element in namespace 'http://www.w3.org/1999/xhtml'..
As i understand, the proper way of tackling my problem would be to alter XSLT in such a way as to: 1. Declare XHTML namespace, 2. Modify XPath expressions to use FQ names.
Unfortunately, modifying field's XSLT is not an option.
Also, ILSpy cannot find any references of XhtmlFieldDefinition.FilterXslt to get an idea how Tridion itself does it.
How to transform XHtmlField value according to attached filter XSLT?
EDIT LinqPad snipped that demonstrates the transformation (but not the behavior during Save!): https://gist.github.com/esteewhy/6248993
EDIT (Solution) Got it working using user978511 idea/hack of re-applying XHTML namespace to top-most elements. Still would be curious how Tridion does it internally. Nevertheless, solutions looks like this:
// remove XHTML NS which confused XSLT
var input = value.Replace(@"xmlns=""http://www.w3.org/1999/xhtml""", string.Empty);
// apply XSLT as shown above
// put back XHTML NS on top-most elements
field.Value = AssignTopLevelNS(sb.ToString());
// .. and the essential method itself
static string AssignTopLevelNS(string xmlFragment)
{
var k = new XmlDocument();
k.LoadXml("<xml>" + xmlFragment + "</xml>");
var xKey = k.CreateAttribute("xmlns");
xKey.Value = "http://www.w3.org/1999/xhtml";
k.DocumentElement.ChildNodes.Cast<XmlNode>().ToList().ForEach(element => {
element.Attributes.SetNamedItem(xKey);
});
return k.DocumentElement.InnerXml;
}