I am trying to unit test a map with following code,
protected string Map(TransformBase map, string xml)
{
StringWriter str = new StringWriter();
XmlTextWriter writer = new XmlTextWriter(str);
map.Transform.Transform(new XPathDocument(new StringReader(xml)), new XsltArgumentList(), writer);
return str.ToString();
}
And it's invoked as follows,
[Test]
public void Map_Test()
{
var result = Map(new TestMap(),File.ReadAllText(_dataDir.GetFiles("TestRequest.xml")[0].FullName));
Assert.IsTrue(result.Contains("4323432"));
}
This works fine for most of the maps, however if I uses a function from an external assembly, this does not works and fails with the error
Result Message: System.Xml.Xsl.XslTransformException : Cannot find a script or an extension object associated with namespace 'http://schemas.microsoft.com/BizTalk/2003/ScriptNS0'.
Result StackTrace:
at System.Xml.Xsl.Runtime.XmlQueryContext.InvokeXsltLateBoundFunction(String name, String namespaceUri, IList`1[] args)
at <xsl:template match="/workOrderRequest">(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, XPathNavigator {urn:schemas-microsoft-com:xslt-debug}current)
at <xsl:template match="/">(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, XPathNavigator {urn:schemas-microsoft-com:xslt-debug}current)
at Root(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
at Execute(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlSequenceWriter results)
at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer)
at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results)
It is not possible to debug custom xslt with reference to external assembly.
Check this thread for more information
Edit : this might also interest you.
Finally, I have managed to do this with following code (BT 2013 R2),
The method Map do the actual mapping and returns the result xml,
Parameters,
map -> new object of the map type instance
xml -> source xml content
extObjects -> *_extxml.xml file content generated when executing validate instance on the map
Code
protected string Map(TransformBase map, string xml, string extObjects = null)
{
string result = string.Empty;
XslCompiledTransform transform = new XslCompiledTransform();
XsltSettings setting = new XsltSettings(false, true);
transform.Load(XmlReader.Create(new StringReader(map.XmlContent)), setting, new XmlUrlResolver());
using (StringWriter writer = new StringWriter())
{
transform.Transform(XmlReader.Create(new StringReader(xml)),
GetExtensionObjects(extObjects), XmlWriter.Create(writer));
result = writer.ToString();
}
return result;
}
protected XsltArgumentList GetExtensionObjects(string extObjects)
{
XsltArgumentList arguments = new XsltArgumentList();
if (extObjects == null)
return arguments;
XDocument extObjectsXDoc = XDocument.Parse(extObjects);
foreach (XElement node in extObjectsXDoc.Descendants("ExtensionObject"))
{
string assembly_qualified_name = String.Format("{0}, {1}", node.Attribute("ClassName").Value, node.Attribute("AssemblyName").Value);
object extension_object = Activator.CreateInstance(Type.GetType(assembly_qualified_name));
arguments.AddExtensionObject(node.Attribute("Namespace").Value, extension_object);
}
return arguments;
}
Sample usage
[Test]
public void Map_Test()
{
var result = Map(new A_To_B()
, File.ReadAllText("A.xml")
, File.ReadAllText("A_To_B_extxml.xml"));
Assert.IsNotNullOrEmpty(result);
}
Related
I am writing an XML file using XDocument. I want to make adjustments to the file layout. Let me explain, here is an extract of the generated file:
<ROOTELEMENT>
<CHILDELEMENT>
<INFO1>Test a 1</INFO1>
<INFO2>Test a 2</INFO2>
</CHILDELEMENT>
<CHILDELEMENT>
<INFO1>Test b 1</INFO1>
<INFO2>Test b 2</INFO2>
</CHILDELEMENT>
<ROOTELEMENT>
I want my file to look like this instead :
<ROOTELEMENT>
<CHILDELEMENT><INFO1>Test a 1</INFO1><INFO2>Test a 2</INFO2></CHILDELEMENT>
<CHILDELEMENT><INFO1>Test b 1</INFO1><INFO2>Test b 2</INFO2></CHILDELEMENT>
</ROOTELEMENT>
Here is my code:
var myDoc = new XDocument(new XElement("ROOTELEMENT",
new XElement("CHILDELEMENT",
new XElement("INFO1", "Test a 1"),
new XElement("INFO2", "Test a 2")),
new XElement("CHILDELEMENT",
new XElement("INFO1", "Test b 1"),
new XElement("INFO2", "Test b 2"))));
myDoc.Save("Test.xml");
Formatting of XML output is controlled by XmlWriter not XElement or XDocument, so if you need precise control of formatting you will need to create your own writer by subclassing one of the implementations of XmlWriter, specifically XmlTextWriter whose Formatting property is mutable and can be changed during writing.
For instance, here is a version that disables indentation when the element depth exceeds 1:
public class CustomFormattingXmlTextWriter : XmlTextWriter
{
readonly Stack<Formatting> stack = new Stack<Formatting>();
public CustomFormattingXmlTextWriter(TextWriter writer, int indentDepth) : base(writer)
{
this.Formatting = Formatting.Indented;
this.IndentDepth = indentDepth;
}
int IndentDepth { get; }
void OnElementStarted(string localName, string ns)
{
stack.Push(Formatting);
// You could e.g. modify the logic here to check to see whether localName == CHILDELEMENT
// if (localName == "CHILDELEMENT")
if (stack.Count == IndentDepth+1)
Formatting = Formatting.None;
}
void OnElementEnded()
{
var old = stack.Pop();
if (old != Formatting)
Formatting = old;
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement(prefix, localName, ns);
OnElementStarted(localName, ns);
}
public override void WriteEndElement()
{
base.WriteEndElement();
OnElementEnded();
}
public override void WriteFullEndElement()
{
base.WriteEndElement();
OnElementEnded();
}
}
public static partial class XNodeExtensions
{
public static void SaveWithCustomFormatting(this XDocument doc, string filename, int indentDepth)
{
using (var textWriter = new StreamWriter(filename))
using (var writer = new CustomFormattingXmlTextWriter(textWriter, indentDepth))
{
doc.Save(writer);
}
}
}
Using it, you can do:
myDoc.SaveWithCustomFormatting(fileName, 1);
which outputs, as required:
<ROOTELEMENT>
<CHILDELEMENT><INFO1>Test a 1</INFO1><INFO2>Test a 2</INFO2></CHILDELEMENT>
<CHILDELEMENT><INFO1>Test b 1</INFO1><INFO2>Test b 2</INFO2></CHILDELEMENT>
</ROOTELEMENT>
Notes:
You can modify the logic in CustomFormattingXmlTextWriter.OnElementStarted() to disable the formatting using any criteria you desire such as checking to see whether the incoming localName is CHILDELEMENT.
XmlTextWriter is deprecated in favor of XmlWriter -- but the latter does not have a mutable Formatting property. If you must needs use XmlWriter you might look at Is there a way to serialize multiple XElements onto the same line?.
Demo fiddle #1 here.
I would like to use JsonFx to convert XML to/from custom types and LINQ queries. Can anyone please provide an example to de-serialisation and serialisation back again?
Here's an example of the XML I'm working with.
XML pasted here: http://pastebin.com/wURiaJM2
JsonFx Supports several strategies of binding json to .net objects including dynamic objects. https://github.com/jsonfx/jsonfx
Kind regards
Si
PS I did try pasting the xml document into StackOverflow but it removed a lot of the documents quotes and XML declaration.
Here's a method that I have used. It may require some tweaking:
public static string SerializeObject<T>(T item, string rootName, Encoding encoding)
{
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.OmitXmlDeclaration = true;
writerSettings.Indent = true;
writerSettings.NewLineHandling = NewLineHandling.Entitize;
writerSettings.IndentChars = " ";
writerSettings.Encoding = encoding;
StringWriter stringWriter = new StringWriter();
using (XmlWriter xml = XmlWriter.Create(stringWriter, writerSettings))
{
XmlAttributeOverrides aor = null;
if (rootName != null)
{
XmlAttributes att = new XmlAttributes();
att.XmlRoot = new XmlRootAttribute(rootName);
aor = new XmlAttributeOverrides();
aor.Add(typeof(T), att);
}
XmlSerializer xs = new XmlSerializer(typeof(T), aor);
XmlSerializerNamespaces xNs = new XmlSerializerNamespaces();
xNs.Add("", "");
xs.Serialize(xml, item, xNs);
}
return stringWriter.ToString();
}
And for Deserialization:
public static T DeserializeObject<T>(string xml)
{
using (StringReader rdr = new StringReader(xml))
{
return (T)new XmlSerializer(typeof(T)).Deserialize(rdr);
}
}
And call it like this:
string xmlString = Serialization.SerializeObject(instance, "Root", Encoding.UTF8);
ObjectType obj = Serialization.DeserializeObject<ObjectType>(xmlString);
Hope this helps. The rootName parameter in the Serialize method lets you customize the value of the root node in the resulting xml string. Also, your classes must be decorated with the proper Xml attributes which will control how an entity is serialized.
This post explains how to create an XSD and a Classes from an XML file and then covers serialisation and de-serialisation.
http://geekswithblogs.net/CWeeks/archive/2008/03/11/120465.aspx
Using this technique with the XSD.exe to create an XSD and then classes in a CS file I was able to serialisation and then de-serialisation back again.
However the serialisation process does not create an exact representation of the source XML, so there's still some post work to be done there.
I'm having some trouble using a combination of XElement and XslCompiledTransform. I've put the sample code I'm using below. If I get my input XML using the GetXmlDocumentXml() method, it works fine. If I use the GetXElementXml() method instead, I get an InvalidOperationException when calling the Transform method of XslComiledTransform:
Token Text in state Start would result in an invalid XML document. Make sure that the ConformanceLevel setting is set to ConformanceLevel.Fragment or ConformanceLevel.Auto if you want to write an XML fragment.
The CreateNavigator method on both XElement and XmlDocument returns an XPathNavigator. What extra stuff is XmlDocument doing so this all works, and how can I do the same with XElement? Am I just doing something insane?
static void Main(string[] args)
{
XslCompiledTransform stylesheet = GetStylesheet(); // not shown for brevity
IXPathNavigable input = this.GetXElementXml();
using (MemoryStream ms = this.TransformXml(input, stylesheet))
{
XmlReader xr = XmlReader.Create(ms);
xr.MoveToContent();
}
}
private MemoryStream TransformXml(
IXPathNavigable xml,
XslCompiledTransform stylesheet)
{
MemoryStream transformed = new MemoryStream();
XmlWriter writer = XmlWriter.Create(transformed);
stylesheet.Transform(xml, null, writer);
transformed.Position = 0;
return transformed;
}
private IXPathNavigable GetXElementXml()
{
var xml = new XElement("x", new XElement("y", "sds"));
return xml.CreateNavigator();
}
private IXPathNavigable GetXmlDocumentXml()
{
var xml = new XmlDocument();
xml.LoadXml("<x><y>sds</y></x>");
return xml.CreateNavigator();
}
Oh, that was easy. The solution was to wrap the XElement in an XDocument object. Problem solved!
.NET 2.0/VS2005
I am trying to use the XslCompiledTransform class to perform a XSL Transformation. I have two XSL files, the first of which includes a reference to the other in the form of an <xsl:include> statement :
Main.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="Included.xsl" />
...
...
</xsl:stylesheet>
Now, If I could load the "Main.xsl" file itself as a URI, my transformation code would be as simple as :
// This is a function that works. For demo only.
private string Transform(string xslFileURI)
{
XslCompiledTransform xslt = new XslCompiledTransform();
// This load works just fine, if I provide the path to "Main.xsl".
// The xsl:include is automatically resolved.
xslTransform.Load(xslFileURI);
StringWriter sw = new StringWriter();
xslt.Transform(Server.MapPath("~/XML/input.xml"), null, sw);
return sw.ToString();
}
The problem is that I receive the contents of the Main.xsl file as a string and need to load the string as an XmlReader/IXpathNavigable. This is a necessary restriction at this time. When I try to do the same using an XmlReader/XpathDocument, it fails because the code looks for "Included.xsl" in the C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ folder! Obviously, the XmlResolver is not able to resolve the relative URL because it only receives a string as input XSL.
My efforts in this direction look like:
// This doesn't work! Halp!
private string Transform(string xslContents)
{
XslCompiledTransform xslt = new XslCompiledTransform();
XmlUrlResolver resolver = new XmlUrlResolver();
resolver.Credentials = CredentialCache.DefaultCredentials;
//METHOD 1: This method does not work.
XmlReaderSettings settings = new XmlReaderSettings();
settings.XmlResolver = resolver;
XmlReader xR = XmlReader.Create(new StringReader(xslContents), settings);
xslt.Load(xR); // fails
// METHOD 2: Does not work either.
XPathDocument xpDoc = new XPathDocument(new StringReader(xslContents));
xslt.Load(xpDoc, new XsltSettings(true, true), resolver); //fails.
StringWriter sw = new StringWriter();
xslt.Transform(Server.MapPath("~/XML/input.xml"), null, sw);
return sw.ToString();
}
I have tried to use the ResolveUri method of the XmlUrlResolver to obtain a Stream referencing the XSL file to be included, but am confused as to how to use this Stream. IOW, how do I tell the XslCompiledTransform object to use this stream in addition to the Main.xsl XmlReader:
Uri mainURI = new Uri(Request.PhysicalApplicationPath + "Main.xsl");
Uri uri = resolver.ResolveUri(mainURI, "Included.xsl");
// I can verify that the Included.xsl file loads in the Stream below.
Stream s = resolver.GetEntity(uri, null, typeof(Stream)) as Stream;
// How do I use this Stream in the function above??
Any help is greatly appreciated. Sorry for the long post!
For your reference, the Exception StackTrace looks like this:
[FileNotFoundException: Could not find file 'C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\Included.xsl'.]
System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) +328
System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy) +1038
System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize) +113
System.Xml.XmlDownloadManager.GetStream(Uri uri, ICredentials credentials) +78
System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn) +51
System.Xml.Xsl.Xslt.XsltLoader.CreateReader(Uri uri, XmlResolver xmlResolver) +22
System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(Uri uri, Boolean include) +33
System.Xml.Xsl.Xslt.XsltLoader.LoadInclude() +349
System.Xml.Xsl.Xslt.XsltLoader.LoadRealStylesheet() +704
System.Xml.Xsl.Xslt.XsltLoader.LoadDocument() +293
System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader reader, Boolean include) +173
Use a custom XmlUrlResolver
class MyXmlUrlResolver : XmlUrlResolver
{
public override Uri ResolveUri(Uri baseUri, string relativeUri)
{
if (baseUri != null)
return base.ResolveUri(baseUri, relativeUri);
else
return base.ResolveUri(new Uri("http://mypath/"), relativeUri);
}
}
And use it in load function of XslCompiledTransform,
resolver=new MyXmlUrlResolver();
xslt.Load(xR,null,resolver);
As Gee's answer mentions, you want to use a custom XmlResolver (of which XmlUrlResolver is already derived), but if you also override the method GetEntity you can resolve references in the primary XSLT document in fun and interesting ways. A deliberately simple example of how you could resolve the reference to Included.xsl:
public class CustomXmlResolver : XmlResolver
{
public CustomXmlResolver() { }
public override ICredentials Credentials
{
set { }
}
public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
{
MemoryStream entityStream = null;
switch (absoluteUri.Scheme)
{
case "custom-scheme":
string absoluteUriOriginalString = absoluteUri.OriginalString;
string ctgXsltEntityName = absoluteUriOriginalString.Substring(absoluteUriOriginalString.IndexOf(":") + 1);
string entityXslt = "";
// TODO: Replace the following with your own code to load data for referenced entities.
switch (ctgXsltEntityName)
{
case "Included.xsl":
entityXslt = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n <xsl:template name=\"Included\">\n\n </xsl:template>\n</xsl:stylesheet>";
break;
}
UTF8Encoding utf8Encoding = new UTF8Encoding();
byte[] entityBytes = utf8Encoding.GetBytes(entityXslt);
entityStream = new MemoryStream(entityBytes);
break;
}
return entityStream;
}
public override Uri ResolveUri(Uri baseUri, string relativeUri)
{
// You might want to resolve all reference URIs using a custom scheme.
if (baseUri != null)
return base.ResolveUri(baseUri, relativeUri);
else
return new Uri("custom-scheme:" + relativeUri);
}
}
When you load the Main.xsl document you'd change the relevant code to the following:
xslt.Load(xpDoc, new XsltSettings(true, true), new CustomXmlResolver());
The above example was based on info I picked-up in the MSDN article Resolving the Unknown: Building Custom XmlResolvers in the .NET Framework.
I am probably missing the obvious but is there a reason you don't just change the URI of Included.xsl to be a true URL? This could either be done in the XSL doc if you have access or using string manipulation otherwise?
I already have success with doing transformations using all in memory:
Having a xslt containing the following includes:
import href="Common.xslt" and
import href="Xhtml.xslt"
private string Transform(string styleSheet, string xmlToParse)
{
XslCompiledTransform xslt = new XslCompiledTransform();
MemoryResourceResolver resolver = new MemoryResourceResolver();
XmlTextReader xR = new XmlTextReader(new StringReader(styleSheet));
xslt.Load(xR, null, resolver);
StringWriter sw = new StringWriter();
using (var inputReader = new StringReader(xmlToParse))
{
var input = new XmlTextReader(inputReader);
xslt.Transform(input,
null,
sw);
}
return sw.ToString();
}
public class MemoryResourceResolver : XmlResolver
{
public override object GetEntity(Uri absoluteUri,
string role, Type ofObjectToReturn)
{
if (absoluteUri.ToString().Contains("Common"))
{
return new MemoryStream(Encoding.UTF8.GetBytes("Xml with with common data"));
}
if (absoluteUri.ToString().Contains("Xhtml"))
{
return new MemoryStream(Encoding.UTF8.GetBytes("Xml with with xhtml data"));
}
return "";
}
}
Note that absolutely all content is as strings: styleSheet, xmlToParse and the content of the "Common" and "Xhtml" imports
I'm serializing an object in a C# VS2003 / .Net 1.1 application. I need it serialized without the processing instruction, however. The XmlSerializer class puts out something like this:
<?xml version="1.0" encoding="utf-16" ?>
<MyObject>
<Property1>Data</Property1>
<Property2>More Data</Property2>
</MyObject>
Is there any way to get something like the following, without processing the resulting text to remove the tag?
<MyObject>
<Property1>Data</Property1>
<Property2>More Data</Property2>
</MyObject>
For those that are curious, my code looks like this...
XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
StringBuilder builder = new StringBuilder();
using ( TextWriter stringWriter = new StringWriter(builder) )
{
serializer.Serialize(stringWriter, comments);
return builder.ToString();
}
I made a small correction
XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
StringBuilder builder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using ( XmlWriter stringWriter = XmlWriter.Create(builder, settings) )
{
serializer.Serialize(stringWriter, comments);
return builder.ToString();
}
In 2.0, you would use XmLWriterSettings.OmitXmlDeclaration, and serialize to an XmlWriter - however I don't think this exists in 1.1; so not entirely useful - but just one more "consider upgrading" thing... and yes, I realise it isn't always possible.
The following link will take you to a post where someone has a method of supressing the processing instruction by using an XmlWriter and getting into an 'Element' state rather than a 'Start' state. This causes the processing instruction to not be written.
Suppress Processing Instruction
If you pass an XmlWriter to the serializer, it will only emit a processing
instruction if the XmlWriter's state is 'Start' (i.e., has not had anything
written to it yet).
// Assume we have a type named 'MyType' and a variable of this type named
'myObject'
System.Text.StringBuilder output = new System.Text.StringBuilder();
System.IO.StringWriter internalWriter = new System.IO.StringWriter(output);
System.Xml.XmlWriter writer = new System.Xml.XmlTextWriter(internalWriter);
System.Xml.Serialization.XmlSerializer serializer = new
System.Xml.Serialization.XmlSerializer(typeof(MyType));
writer.WriteStartElement("MyContainingElement");
serializer.Serialize(writer, myObject);
writer.WriteEndElement();
In this case, the writer will be in a state of 'Element' (inside an element)
so no processing instruction will be written. One you finish writing the
XML, you can extract the text from the underlying stream and process it to
your heart's content.
What about omitting namespaces ?
instead of using
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
ex:
<message xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
If by "processing instruction" you mean the xml declaration, then you can avoid this by setting the OmitXmlDeclaration property of XmlWriterSettings. You'll need to serialize using an XmlWriter, to accomplish this.
XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
StringBuilder builder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using ( XmlWriter stringWriter = new StringWriter(builder, settings) )
{
serializer.Serialize(stringWriter, comments);
return builder.ToString();
}
But ah, this doesn't answer your question for 1.1. Well, for reference to others.
This works in .NET 1.1. (But you should still consider upgrading)
XmlSerializer s1= new XmlSerializer(typeof(MyClass));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add( "", "" );
MyClass c= new MyClass();
c.PropertyFromDerivedClass= "Hallo";
sw = new System.IO.StringWriter();
s1.Serialize(new XTWND(sw), c, ns);
....
/// XmlTextWriterFormattedNoDeclaration
/// helper class : eliminates the XML Documentation at the
/// start of a XML doc.
/// XTWFND = XmlTextWriterFormattedNoDeclaration
public class XTWFND : System.Xml.XmlTextWriter
{
public XTWFND(System.IO.TextWriter w) : base(w) { Formatting = System.Xml.Formatting.Indented; }
public override void WriteStartDocument() { }
}