Talend exercises in Golang: ‘Advanced’ XML

This is a port of the previous post to golang. Golang is the closest I’ve gotten to writing production-code in a statically typed C-like language. Its nice, though I still can’t get over reading The Go Programming Language and seeing them refer to C and Go as high-level languages. Having started out in Lisp, it just feels weird.

Anyway, this is code that took about an afternoon to write. Since this is a statically-typed language, I fired up my RubyMine with the Go plugin and its great. I started writing the code using regular slices and maps, and then started switching to custom types since that is what the XML-encoder works with. It was so easy firing up new structs and using them instead of the pre-existing data-structures. I enjoyed doing the refactoring very much.

RubyMine’s (IntelliJ’s? JetBrains’?) plugin for Go is great to use. The debugger doesn’t seem to be working, which was a bit of a pain. When programming in Ruby its almost second nature for me to add a breakpoint and run amok in the runtime. I essentially instantiate my very own REPL in the correct context of the code. When doing Clojure, I always have a REPL open. It was a shift to debug only with PrintLns, but its a legitimate enough method. And with the compiler telling me what I’m doing wrong, I had to use the debugging statement less than I would have with Clojure or Ruby. Good stuff.

The code came down to 85 lines. 85 lines of code to write a file-format conversion isn’t bad at all. And this is something that would compile to a single executable file that can be handed over to anyone. Very nice.

Notes

  • RubyMine and Go Plugin: Since this is a statically-typed language, it made sense to try the Go plugin. I’m very happy with it. It made refactorings so much easier, and this is making me a big fan of static-typing.
  • Since the executable starts and runs so quickly, it made more sense for me to write the results to stdout instead of to a file, just as one would with any well-behaved UNIX utility.
package main

import (
	"os"
	"encoding/csv"
	"log"
	"strings"
	"encoding/xml"
	"time"
)

type OrderLine struct {
	XMLName    xml.Name `xml:"ORDER_LINE"`
	OrderId    string `xml:"-"`
	LineId     string `xml:"ID,attr"`
	Sku        string `xml:"SKU,attr"`
	Quantity   string `xml:"QUANTITY,attr"`
	Status     string `xml:"STATUS,attr"`
	TrackingId string `xml:"TRACKING_ID,attr"`
	DispatchedDate string `xml:"DISPATCHED_DATE,attr"`
}

type Order struct {
	ID string `xml:"ID,attr"`
	OrderLines []OrderLine `xml:"ORDER"`
}

type DispatchDocket struct {
	XMLName xml.Name `xml:"DISPATCH_DOCKET`
	Orders []Order `xml:"ORDER"`
}

func recordToOrderLine(record string) OrderLine {
	split := strings.Split(record, ";")
	return OrderLine{
		OrderId: split[0],
		LineId: split[1],
		Sku: split[2],
		Quantity: split[3],
		Status: split[4],
		DispatchedDate: time.Now().Format(time.UnixDate),
		TrackingId: split[5]}
}

func recordsToOrders(records [][]string) (out []Order) {
	final := make(map[string][]OrderLine)

	for _, v := range records {
		order := recordToOrderLine(v[0])
		order_id := order.OrderId
		if order.Status == "shipped" {
			final[order_id] = append(final[order_id], order)
		}
	}

	for k, order_lines:= range final {
		out = append(out, Order{ID: k, OrderLines: order_lines})
	}

	return
}

func main() {
	f, _ := os.Open("/home/ravi/myprogs/GETTINGSTARTEDTOS_DEMO_DATA/SampleDataFiles/Chapter3/order_status.csv")
	defer f.Close()

	r := csv.NewReader(f)

	records, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}
	if len(records) < 2 {
		log.Fatalf("We need more than 1 line of data.", err)
	}

	orders := DispatchDocket{Orders: recordsToOrders(records[1:])}

	output, err := xml.MarshalIndent(&orders, "", "    ")
	if err != nil {
		log.Fatal(err)
	}

	os.Stdout.Write(output)
}

Leave a Reply