// Copyright © 2011-12 Qtrac Ltd. // // This program or package and any associated files are licensed under the // Apache License, Version 2.0 (the "License"); you may not use these files // except in compliance with the License. You can get a copy of the License // at: http://www.apache.org/licenses/LICENSE-2.0. // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/xml" "fmt" "io" "strings" "time" ) type XMLMarshaler struct{} type XMLInvoices struct { XMLName xml.Name `xml:"INVOICES"` Version int `xml:"version,attr"` Invoice []*XMLInvoice `xml:"INVOICE"` } type XMLInvoice struct { XMLName xml.Name `xml:"INVOICE"` Id int `xml:",attr"` CustomerId int `xml:",attr"` DepartmentId string `xml:",attr"` Raised string `xml:",attr"` Due string `xml:",attr"` Paid bool `xml:",attr"` Note string `xml:"NOTE"` Item []*XMLItem `xml:"ITEM"` } type XMLItem struct { XMLName xml.Name `xml:"ITEM"` Id string `xml:",attr"` Price float64 `xml:",attr"` Quantity int `xml:",attr"` TaxBand int `xml:",attr"` Note string `xml:"NOTE"` } func XMLInvoicesForInvoices(invoices []*Invoice) *XMLInvoices { xmlInvoices := &XMLInvoices{ Version: fileVersion, Invoice: make([]*XMLInvoice, 0, len(invoices)), } for _, invoice := range invoices { xmlInvoices.Invoice = append(xmlInvoices.Invoice, XMLInvoiceForInvoice(invoice)) } return xmlInvoices } func XMLInvoiceForInvoice(invoice *Invoice) *XMLInvoice { xmlInvoice := &XMLInvoice{ Id: invoice.Id, CustomerId: invoice.CustomerId, DepartmentId: invoice.DepartmentId, Raised: invoice.Raised.Format(dateFormat), Due: invoice.Due.Format(dateFormat), Paid: invoice.Paid, Note: invoice.Note, Item: make([]*XMLItem, 0, len(invoice.Items)), } for _, item := range invoice.Items { xmlItem := &XMLItem{ Id: item.Id, Price: item.Price, Quantity: item.Quantity, TaxBand: item.TaxBand, Note: item.Note, } xmlInvoice.Item = append(xmlInvoice.Item, xmlItem) } return xmlInvoice } func (xmlInvoices *XMLInvoices) Invoices(version int) (invoices []*Invoice, err error) { invoices = make([]*Invoice, 0, len(xmlInvoices.Invoice)) for _, xmlInvoice := range xmlInvoices.Invoice { invoice, err := xmlInvoice.Invoice(version) if err != nil { return nil, err } invoices = append(invoices, invoice) } return invoices, nil } func (xmlInvoice *XMLInvoice) Invoice(version int) (invoice *Invoice, err error) { invoice = &Invoice{ Id: xmlInvoice.Id, CustomerId: xmlInvoice.CustomerId, Paid: xmlInvoice.Paid, Note: strings.TrimSpace(xmlInvoice.Note), Items: make([]*Item, 0, len(xmlInvoice.Item)), } if version == fileVersion { invoice.DepartmentId = xmlInvoice.DepartmentId } if invoice.Raised, err = time.Parse(dateFormat, xmlInvoice.Raised); err != nil { return nil, err } if invoice.Due, err = time.Parse(dateFormat, xmlInvoice.Due); err != nil { return nil, err } if invoice.Items, err = itemsForXMLItems(version, xmlInvoice.Item); err != nil { return nil, err } if version < fileVersion { updateInvoice(invoice) } return invoice, nil } func itemsForXMLItems(version int, xmlItems []*XMLItem) (items []*Item, err error) { for _, xmlItem := range xmlItems { item := &Item{ Id: xmlItem.Id, Price: xmlItem.Price, Quantity: xmlItem.Quantity, Note: strings.TrimSpace(xmlItem.Note), } if version == fileVersion { item.TaxBand = xmlItem.TaxBand } else if version < fileVersion { if err = updateItem(item); err != nil { return nil, err } } items = append(items, item) } return items, nil } func (XMLMarshaler) MarshalInvoices(writer io.Writer, invoices []*Invoice) error { if _, err := writer.Write([]byte(xml.Header)); err != nil { return err } xmlInvoices := XMLInvoicesForInvoices(invoices) encoder := xml.NewEncoder(writer) return encoder.Encode(xmlInvoices) } func (XMLMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice, error) { xmlInvoices := &XMLInvoices{} decoder := xml.NewDecoder(reader) if err := decoder.Decode(xmlInvoices); err != nil { return nil, err } if xmlInvoices.Version > fileVersion { return nil, fmt.Errorf("version %d is too new to read", xmlInvoices.Version) } return xmlInvoices.Invoices(xmlInvoices.Version) }