<|start_filename|>wordide/context.go<|end_filename|>
package wordide

import (
	"archive/zip"
	"fmt"
)

type Context struct {
	Reader *zip.ReadCloser
}

func OpenContext(filePath string) (*Context, error) {
	r, err := zip.OpenReader(filePath)
	if err != nil {
		return nil, err
	}

	return &Context{Reader: r}, nil
}

func (ctx *Context) GetFile(path string) (*zip.File, error) {
	for _, f := range ctx.Reader.File {
		if f.Name == path {
			return f, nil
		}
	}

	return nil, fmt.Errorf("could not find file %s in context", path)
}

func (ctx *Context) Close() {
	ctx.Reader.Close()
}


<|start_filename|>Makefile<|end_filename|>

GO=go
BIN=wide

.PHONY: build clean install

build:
	$(GO) build -o $(BIN)

clean:
	go clean

install:
	cp $(BIN) /usr/bin


<|start_filename|>main.go<|end_filename|>
package main

import (
	"fmt"
	"io"
	"os"

	"github.com/FiftyLinesOfCode/wordide/wordide"
)

const (
	PROG_NAME          string = "wide"
	PROG_VERSION_MAJOR int    = 1
	PROG_VERSION_MINOR int    = 0
)

func printUsage() {
	println("USAGE: wide <command> <filename>.odt")
	println("Available commands:")
	println("\tcompile - Outputs plain text in stdout")
	println("\thelp - displays this page")
}

func main() {
	args := os.Args
	if len(args) <= 2 {
		printUsage()
		return
	}

	switch args[1] {
	case "help":
		printUsage()
		return

	case "compile":
		ctx, err := wordide.OpenContext(args[2])
		if err != nil {
			println("Can't open a context. Are you sure the file specified exists?")
			return
		}
		defer ctx.Close()

		file, err := ctx.GetFile("content.xml")
		if err != nil {
			println("The specified file doesn't contain a content.xml. Are you sure it's a valid .odt file?")
			return
		}

		reader, _ := file.Open()
		fileContent, err := io.ReadAll(reader)
		if err != nil {
			println("Failed to read from content.xml")
			return
		}

		generated, err := wordide.Parse(fileContent)
		if err != nil {
			println("Couldn't parse content.xml. Are you sure it's a valid .odt file?")
			return
		}
		fmt.Println(generated)

	default:
		printUsage()
		return
	}
}


<|start_filename|>wordide/parser.go<|end_filename|>
package wordide

import (
	"encoding/xml"
	"strconv"
)

func GetIntAttr(attrs *[]xml.Attr, name string) int {
	for _, attr := range *attrs {
		if attr.Name.Local == name {
			i, _ := strconv.Atoi(attr.Value)
			return i
		}
	}

	return -1 // FIXME: We need to return an error!
}

type DocumentText struct {
	Data string
}

func (dt *DocumentText) ParseElement(d *xml.Decoder) xml.Token {
	token, _ := d.Token()
	switch tt := token.(type) {
	case xml.StartElement:
		switch tt.Name.Local {
		case "p":
			for {
				switch tt := dt.ParseElement(d).(type) {
				case xml.EndElement:
					if tt.Name.Local == "p" {
						dt.Data += "\n"
						return tt
					}
				}
			}

		case "s":
			for i := 0; i < GetIntAttr(&tt.Attr, "c"); i += 1 {
				dt.Data += " "
			}

		case "tab":
			dt.Data += "\t"

		case "span":
			for {
				switch tt := dt.ParseElement(d).(type) {
				case xml.EndElement:
					if tt.Name.Local == "span" {
						return tt
					}
				}
			}

		default:
			for {
				switch x := dt.ParseElement(d).(type) {
				case xml.EndElement:
					if tt.Name.Local == x.Name.Local {
						return x
					}
				}
			}
		}

	case xml.EndElement:
		return tt

	case xml.CharData:
		dt.Data += string(tt)
	}

	return token
}

func (dt *DocumentText) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	for {
		switch x := dt.ParseElement(d).(type) {
		case xml.EndElement:
			if x.Name.Local == "text" {
				return nil // Yes we need to return errors some time
			}
		}
	}
}

type DocumentBody struct {
	XMLName xml.Name     `xml:"body"`
	Text    DocumentText `xml:"text"`
}

type DocumentContent struct {
	XMLName xml.Name `xml:"document-content"`
	Body    DocumentBody
}

func Parse(file []byte) (*DocumentContent, error) {
	content := new(DocumentContent)

	err := xml.Unmarshal([]byte(file), content)
	if err != nil {
		return nil, err
	}

	return content, nil
}

func (content *DocumentContent) String() string {
	return content.Body.Text.Data
}
