Having included the etree package in order to use its canonicalization function, it makes sense to replace all of our hokey XML handling with etree.Element. This is the smallest refactoring I can do, keeping the same basic structure of functions in xml.go but changing the return types. These types then propagate through the rest of the code, along with any necessary modifications.
package govtalk
import (
"log"
"os"
"reflect"
"github.com/unix-world/smartgoext/xml-utils/etree"
)
func addElements(to *etree.Element, elts []etree.Token) {
for _, e := range elts {
if e != nil && !reflect.ValueOf(e).IsNil() {
to.AddChild(e)
}
}
}
func ElementWithNesting(tag string, elts ...etree.Token) *etree.Element {
env := etree.NewElement(tag)
addElements(env, elts)
return env
}
func ElementWithText(tag, value string, elts ...etree.Token) *etree.Element {
env := etree.NewElement(tag)
env.AddChild(env.CreateText(value))
addElements(env, elts)
return env
}
func ContentFromFile(filename string) *etree.Element {
fp, err := os.Open(filename)
if err != nil {
log.Fatalf("Could not read %s: %v", filename, err)
}
defer fp.Close()
doc := etree.NewDocument()
_, err = doc.ReadFrom(fp)
if err != nil {
log.Fatalf("Could not read %s: %v", filename, err)
}
return doc.Element.ChildElements()[0]
}
func MakeGovTalkMessage(nesting ...etree.Token) *etree.Element {
ret := etree.NewElement("GovTalkMessage")
ret.Attr = append(ret.Attr, etree.Attr{Key: "xmlns", Value: "http://www.govtalk.gov.uk/CM/envelope"}, etree.Attr{Space: "xmlns", Key: "xsi", Value: "http://www.w3.org/2001/XMLSchema-instance"})
addElements(ret, nesting)
return ret
}
func MakeIRenvelopeMessage(nesting ...etree.Token) *etree.Element {
ret := etree.NewElement("IRenvelope")
ret.Attr = append(ret.Attr, etree.Attr{Key: "xmlns", Value: "http://www.govtalk.gov.uk/taxation/CT/5"}, etree.Attr{Space: "xmlns", Key: "xsi", Value: "http://www.w3.org/2001/XMLSchema-instance"})
addElements(ret, nesting)
return ret
}
func MakeCompanyTaxReturn(ty string, nested ...etree.Token) *etree.Element {
ret := etree.NewElement("CompanyTaxReturn")
ret.Attr = append(ret.Attr, etree.Attr{Key: "ReturnType", Value: ty})
addElements(ret, nested)
return ret
}
func Key(ty, value string) *etree.Element {
ret := etree.NewElement("Key")
ret.Attr = append(ret.Attr, etree.Attr{Key: "Type", Value: ty})
ret.AddChild(ret.CreateText(value))
return ret
}
CT600_REFACTOR_ETREE:accounts/internal/ct600/govtalk/xml.go
And when I run it again, it still works (although I can't guarantee that will continue forever).2026/01/20 10:35:10 Qualifier: acknowledgement
2026/01/20 10:35:10 Function: submit
2026/01/20 10:35:10 CorrelationID: 73350F413E2546D4B84ECC988508A2CD
2026/01/20 10:35:10 ResponseEndPoint PollInterval: 10
2026/01/20 10:35:10 ResponseEndPoint: https://test-transaction-engine.tax.service.gov.uk/poll
2026/01/20 10:35:20 <?xml version="1.0" encoding="UTF-8"?>
<GovTalkMessage xmlns="http://www.govtalk.gov.uk/CM/envelope">
<EnvelopeVersion>2.0</EnvelopeVersion>
<Header>
<MessageDetails>
<Class>HMRC-CT-CT600</Class>
<Qualifier>acknowledgement</Qualifier>
<Function>submit</Function>
<TransactionID></TransactionID>
<CorrelationID>73350F413E2546D4B84ECC988508A2CD</CorrelationID>
<ResponseEndPoint PollInterval="10">https://test-transaction-engine.tax.service.gov.uk/poll</ResponseEndPoint>
<GatewayTimestamp>2026-01-20T10:35:20.157</GatewayTimestamp>
</MessageDetails>
<SenderDetails/>
</Header>
<GovTalkDetails>
<Keys/>
</GovTalkDetails>
<Body/>
</GovTalkMessage>
Simplifying the Logic
In doing this refactoring, I had to duplicate the placeBefore method, because it ended up in two different packages. This is a code smell - on this occasion that submission/generate.go is in the wrong package and should be with govtalk.But while I'm here, I'm not really happy with the way in which placeBefore is used. We start off with abstract objects, then we generate XML, then we format that and then we edit the formatted XML. Except we don't. We use the placeBefore method in three places: once to add a "blank" line where the <IRmark> is going to go; once to insert the <IRmark>, and then once to insert the body into the <GovTalkMessage> envelope. And none of this is really clear from the structure (and names) of Generate. I know this, because I keep struggling to find anything.
So, I'm going to move Generate from submission to govtalk to be with all the other like-minded things, and then I can share the placeBefore method. And then I'm going to simplify that by straightening out the canonicalizing of the body. And thus I end up with this:
func Generate(file string, runlint bool, conf *config.Config, options *EnvelopeOptions) (io.Reader, error) {
gtxml, err := assembleGovTalkXML(conf, options)
if err != nil {
return nil, err
}
gtbs := writeXML(gtxml)
if options.IncludeBody {
bd := makeBody(options.IRenvelope)
bs, err := canonicaliseBody(bd)
if err != nil {
return nil, err
}
body, err := insertIRmark(bs)
if err != nil {
return nil, err
}
gtbs, err = attachBodyTo(gtbs, body)
if err != nil {
return nil, err
}
gtbs = []byte(string(gtbs) + "\n")
}
if file != "" {
err = checkAgainstSchema(file, runlint, gtbs)
if err != nil {
return nil, err
}
}
return bytes.NewReader(gtbs), nil
}
CT600_REFACTOR_GENERATE:accounts/internal/ct600/govtalk/generate.go
And everything continues working in the same flip-floppy way.
No comments:
Post a Comment