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 partif isInclusive
{
set sc = writer.Canonicalize(node, "c14n")
}
else
{
if (+$data(prefixList) >= 10)
{
#dim prefix As%String = ""for
{
set prefix = $order(prefixList(prefix))
if (prefix = "") quitdo 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
}