How to canonicalize XML enabled classes?
I have a registered object class that extends %XML.Adaptor and I want to convert an object of it into canonical XML.
From reading the documentation and some trial and error, it seems like I would need to use %XML.Writer to write the object to XML first using the RootObject() method, then read it with %XML.Reader, and then use the Canonicalize() method of %XML.Writer to write it out again.
Is there a better approach?
Product version: Caché 2017.1
I had to do something like this a few years ago to add a digital signature to a message in XML format. If I remember correctly you have to get your object into a %XML.Document which works in conjunction with %XML.Node. %XML.Node is used to traverse the %XML.Document to get to the section you want in canonical form. Then you pass the Node to ##class(%XML.Writer).Canonicalize(Node) to get the XML as a string which is then passed to the encryption function you use to get your digest/signature. You can pass the whole document or just a subsection to the canonicalize function.
I can't say if it's the only or best way to do it but it was sufficiently quick enough to handle thousands of messages per minute.
Here's the code I use (by @Dmitry Zasypkin):
/// Canonicalize XML. /// in: XML string or stream to canonicalize. /// out: Canonicalized XML is returned in this argument. If it's a string, out must be passed by refrence. /// elementId: attrubute Id to canonicalize. If elementId="", the entire document would be canonicalized. /// prefixList: a local of namespace=prefix pairs to add to a root tag, only in a case of exclusive canonicalization. ClassMethod canonicalize(in As %Stream.Object, ByRef out As %Stream.Object, isInclusive As %Boolean = {$$$NO}, keepWhitespace = {$$$YES}, elementId As %String = "", ByRef prefixList As %String = "", writer As %XML.Writer = {##class(%XML.Writer).%New()}) As %Status { #dim sc As %Status = $$$OK #dim importHandler As %XML.Document = ##class(%XML.Document).%New() set importHandler.KeepWhitespace = keepWhitespace if $isObject(in) { set sc = ##class(%XML.SAX.Parser).ParseStream(in, importHandler,, $$$SAXFULLDEFAULT-$$$SAXVALIDATIONSCHEMA) } else { set sc = ##class(%XML.SAX.Parser).ParseString(in, importHandler,, $$$SAXFULLDEFAULT-$$$SAXVALIDATIONSCHEMA) } if $$$ISERR(sc) quit sc if $isObject(in) && $isObject($get(out)) && (in = out) do in.Clear() if $isObject($get(out)) { set sc = writer.OutputToStream(out) } else { set sc = writer.OutputToString() } if $$$ISERR(sc) quit sc #dim node As %XML.Node = importHandler.GetDocumentElement() if (elementId '= "") set node = importHandler.GetNode(importHandler.GetNodeById(elementId)) // Main part if isInclusive { set sc = writer.Canonicalize(node, "c14n") } else { if (+$data(prefixList) >= 10) { #dim prefix As %String = "" for { set prefix = $order(prefixList(prefix)) if (prefix = "") quit do writer.AddNamespace(prefixList(prefix), prefix) } } set sc = writer.Canonicalize(node) } if $$$ISERR(sc) quit sc if '$isObject($get(out)) { set out = writer.GetXMLString(.sc) if $$$ISERR(sc) quit sc } do writer.Reset() quit $$$OK }