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?
Comments
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
}