├── .gitignore ├── LICENSE ├── README.md ├── cfuncs.go ├── examples └── location.go ├── go.mod ├── gosax.go ├── gosax_test.go ├── pointer └── pointer.go └── testfiles ├── badfile.xml └── fruit.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | cpu.prof 14 | 15 | # binaries 16 | location 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gosax 2 | 3 | For some background information, motivation and a basic usage sample see 4 | [this blog post](https://eli.thegreenplace.net/2019/faster-xml-stream-processing-in-go/). 5 | 6 | ## Building 7 | 8 | To install libxml2 for development, do: 9 | 10 | ``` 11 | $ sudo apt-get install libxml2 libxml2-dev 12 | ``` 13 | 14 | Or build from source. You would need it installed to use ``gosax``. 15 | 16 | ## License 17 | 18 | gosax's code is in the public domain (see `LICENSE` for details). It uses 19 | [libxml](http://www.xmlsoft.org/index.html), which has its own (MIT) license. 20 | 21 | The `pointer` directory vendors https://github.com/mattn/go-pointer/ with some 22 | modifications. `go-pointer` has an MIT license, which applies to my version too. 23 | -------------------------------------------------------------------------------- /cfuncs.go: -------------------------------------------------------------------------------- 1 | // gosax: Go wrapper for libxml SAX. 2 | // 3 | // C helpers for internal use. 4 | // 5 | // Eli Bendersky [https://eli.thegreenplace.net] 6 | // This code is in the public domain. 7 | package gosax 8 | 9 | /* 10 | #cgo pkg-config: libxml-2.0 11 | 12 | #include 13 | #include 14 | 15 | extern void goStartDocument(void*); 16 | extern void goEndDocument(void*); 17 | extern void goStartElement(void*, const xmlChar*, const xmlChar**, int); 18 | extern void goStartElementNoAttr(void*, const xmlChar*); 19 | extern void goEndElement(void*, const xmlChar*); 20 | extern void goCharacters(void*, const xmlChar*, int); 21 | extern void goCharactersRaw(void*, const xmlChar*, int); 22 | 23 | void startDocumentCgo(void* user_data) { 24 | goStartDocument(user_data); 25 | } 26 | 27 | void endDocumentCgo(void* user_data) { 28 | goEndDocument(user_data); 29 | } 30 | 31 | void startElementCgo(void* user_data, 32 | const xmlChar* name, 33 | const xmlChar** attrs) { 34 | // The attrs array is terminated with a NULL pointer. To make it usable in 35 | // Go, we find the length and pass it explicitly to the Go callback. 36 | int i = 0; 37 | if (attrs != NULL) { 38 | while (attrs[i] != NULL) { 39 | i++; 40 | } 41 | } 42 | goStartElement(user_data, name, attrs, i); 43 | } 44 | 45 | void startElementNoAttrCgo(void* user_data, 46 | const xmlChar* name, 47 | const xmlChar** attrs) { 48 | goStartElementNoAttr(user_data, name); 49 | } 50 | 51 | void endElementCgo(void* user_data, const xmlChar* name) { 52 | goEndElement(user_data, name); 53 | } 54 | 55 | void charactersCgo(void* user_data, const xmlChar* ch, int len) { 56 | goCharacters(user_data, ch, len); 57 | } 58 | 59 | void charactersRawCgo(void* user_data, const xmlChar* ch, int len) { 60 | goCharactersRaw(user_data, ch, len); 61 | } 62 | */ 63 | import "C" 64 | -------------------------------------------------------------------------------- /examples/location.go: -------------------------------------------------------------------------------- 1 | // gosax: example of using gosax. 2 | // 3 | // Eli Bendersky [https://eli.thegreenplace.net] 4 | // This code is in the public domain. 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "log" 11 | "os" 12 | "runtime/pprof" 13 | "strings" 14 | "unsafe" 15 | 16 | "github.com/eliben/gosax" 17 | ) 18 | 19 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 20 | 21 | func main() { 22 | flag.Parse() 23 | if *cpuprofile != "" { 24 | f, err := os.Create(*cpuprofile) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | pprof.StartCPUProfile(f) 29 | defer pprof.StopCPUProfile() 30 | } 31 | 32 | counter := 0 33 | inLocation := false 34 | 35 | scb := gosax.SaxCallbacks{ 36 | StartElement: func(name string, attrs []string) { 37 | if name == "location" { 38 | inLocation = true 39 | } else { 40 | inLocation = false 41 | } 42 | }, 43 | 44 | // this overrides StartElement 45 | StartElementNoAttr: func(name string) { 46 | if name == "location" { 47 | inLocation = true 48 | } else { 49 | inLocation = false 50 | } 51 | }, 52 | 53 | EndElement: func(name string) { 54 | inLocation = false 55 | }, 56 | 57 | Characters: func(contents string) { 58 | if inLocation && strings.Contains(contents, "Africa") { 59 | counter++ 60 | } 61 | }, 62 | 63 | // this overrides Characters 64 | CharactersRaw: func(ch unsafe.Pointer, chlen int) { 65 | if inLocation { 66 | if strings.Contains(gosax.UnpackString(ch, chlen), "Africa") { 67 | counter++ 68 | } 69 | } 70 | }, 71 | } 72 | 73 | err := gosax.ParseFile(flag.Args()[0], scb) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | fmt.Println("counter =", counter) 79 | } 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eliben/gosax 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /gosax.go: -------------------------------------------------------------------------------- 1 | // gosax: Go wrapper for libxml SAX. 2 | // 3 | // This file contains all the exported functionality of the module. 4 | // 5 | // Eli Bendersky [https://eli.thegreenplace.net] 6 | // This code is in the public domain. 7 | package gosax 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "strings" 13 | "sync" 14 | "unsafe" 15 | 16 | "github.com/eliben/gosax/pointer" 17 | ) 18 | 19 | /* 20 | #cgo pkg-config: libxml-2.0 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | extern void startDocumentCgo(void*); 27 | extern void endDocumentCgo(void*); 28 | extern void startElementCgo(void*, const xmlChar*, const xmlChar**); 29 | extern void startElementNoAttrCgo(void*, const xmlChar*, const xmlChar**); 30 | extern void endElementCgo(void*, const xmlChar*); 31 | extern void charactersCgo(void*, const xmlChar*, int); 32 | extern void charactersRawCgo(void*, const xmlChar*, int); 33 | 34 | // Since this structure contains pointers, take extra care to zero it out 35 | // before passing it to Go code. 36 | static inline xmlSAXHandler newHandlerStruct() { 37 | xmlSAXHandler h = {0}; 38 | return h; 39 | } 40 | 41 | // Wrap a C macro in a function callable from Go. 42 | static inline xmlError* getLastError() { 43 | return xmlGetLastError(); 44 | } 45 | */ 46 | import "C" 47 | 48 | // Used to ensure that xmlInitParser is only called once. 49 | var initOnce sync.Once 50 | 51 | func init() { 52 | initOnce.Do(func() { 53 | C.xmlInitParser() 54 | }) 55 | } 56 | 57 | // SaxCallbacks collects callback functions to invoke on SAX events. Only 58 | // populate callbacks you're interested in - callbacks left as nil will not 59 | // be registered with the C layer and may save processing time. 60 | // Some callbacks override others for optimization purposes - check the comments 61 | // for more information. 62 | type SaxCallbacks struct { 63 | // StartDocument is invoked on the "start document" event. 64 | StartDocument StartDocumentFunc 65 | 66 | // EndDocument is invoked on the "end document" event 67 | EndDocument EndDocumentFunc 68 | 69 | // StartElement is invoked whenever the beginning of a new element is found. 70 | // name will be the element name, and attrs a slice of attributes where 71 | // attribute names alternate with values. For example, given the element 72 | // the callback will get name="elem" and 73 | // attrs=["foo", "bar", "id", "100"]. 74 | StartElement StartElementFunc 75 | 76 | // StartElementNoAttr will override StartElement, if set. When you don't 77 | // care about the attributes of an element, use this one - it will be faster 78 | // because it doesn't have to do attribute unpacking, which is expensive. 79 | StartElementNoAttr StartElementNoAttrFunc 80 | 81 | // EndElement is invoked at the end of parsing an element (after closing tag 82 | // has been processed), with name being the element name. 83 | EndElement EndElementFunc 84 | 85 | // Characters is invoked on character data inside elements. contents is the 86 | // data, as string. Note that this callback may be invoked multiple times 87 | // within a single tag. 88 | Characters CharactersFunc 89 | 90 | // CharactersRaw will override Characters, if set. It doesn't translate XML 91 | // data into a Go string, but leaves it as an opaque pair of (ch, chlen), 92 | // which you could use UnpackString to convert to a string if needed. This 93 | // could be a useful optimization if you're only occasionally interested in 94 | // the contents of character data. 95 | CharactersRaw CharactersRawFunc 96 | } 97 | 98 | type StartDocumentFunc func() 99 | type EndDocumentFunc func() 100 | type StartElementFunc func(name string, attrs []string) 101 | type StartElementNoAttrFunc func(name string) 102 | type EndElementFunc func(name string) 103 | type CharactersFunc func(contents string) 104 | type CharactersRawFunc func(ch unsafe.Pointer, chlen int) 105 | 106 | // UnpackString unpacks the opaque ch, chlen pair (that some callbacks in 107 | // this package may create) into a Go string. 108 | func UnpackString(ch unsafe.Pointer, chlen int) string { 109 | return C.GoStringN((*C.char)(ch), C.int(chlen)) 110 | } 111 | 112 | // ParseFile parses an XML file with the given name using SAX, with cb as 113 | // the callbacks. The file name is required, rather than a reader, because it 114 | // gets passed directly to the C layer. 115 | func ParseFile(filename string, cb SaxCallbacks) error { 116 | var cfilename *C.char = C.CString(filename) 117 | defer C.free(unsafe.Pointer(cfilename)) 118 | 119 | // newHandlerStruct zeroes out all the pointers; we assign only those that 120 | // are passed as non-nil in SaxCallbacks. 121 | SAXhandler := C.newHandlerStruct() 122 | 123 | if cb.StartDocument != nil { 124 | SAXhandler.startDocument = C.startDocumentSAXFunc(C.startDocumentCgo) 125 | } 126 | 127 | if cb.EndDocument != nil { 128 | SAXhandler.endDocument = C.endDocumentSAXFunc(C.endDocumentCgo) 129 | } 130 | 131 | if cb.StartElement != nil { 132 | SAXhandler.startElement = C.startElementSAXFunc(C.startElementCgo) 133 | } 134 | // StartElementNoAttr overrides StartElement 135 | if cb.StartElementNoAttr != nil { 136 | SAXhandler.startElement = C.startElementSAXFunc(C.startElementNoAttrCgo) 137 | } 138 | 139 | if cb.EndElement != nil { 140 | SAXhandler.endElement = C.endElementSAXFunc(C.endElementCgo) 141 | } 142 | 143 | if cb.Characters != nil { 144 | SAXhandler.characters = C.charactersSAXFunc(C.charactersCgo) 145 | } 146 | // CharactersRaw overrides Characters 147 | if cb.CharactersRaw != nil { 148 | SAXhandler.characters = C.charactersSAXFunc(C.charactersRawCgo) 149 | } 150 | 151 | // Pack the callbacks structure into an opaque unsafe.Pointer which we'll 152 | // pass to C as user_data, and C will pass it back to our Go callbacks. 153 | user_data := pointer.Save(&cb) 154 | defer pointer.Unref(user_data) 155 | 156 | rc := C.xmlSAXUserParseFile(&SAXhandler, user_data, cfilename) 157 | if rc != 0 { 158 | xmlErr := C.getLastError() 159 | msg := strings.TrimSpace(C.GoString(xmlErr.message)) 160 | return fmt.Errorf("line %v: error: %v", xmlErr.line, msg) 161 | } 162 | 163 | return nil 164 | } 165 | 166 | // ParseMem parses XML from in-memory buffer with SAX, applying cb as callbacks. 167 | func ParseMem(buf bytes.Buffer, cb SaxCallbacks) error { 168 | // newHandlerStruct zeroes out all the pointers; we assign only those that 169 | // are passed as non-nil in SaxCallbacks. 170 | SAXhandler := C.newHandlerStruct() 171 | 172 | if cb.StartDocument != nil { 173 | SAXhandler.startDocument = C.startDocumentSAXFunc(C.startDocumentCgo) 174 | } 175 | 176 | if cb.EndDocument != nil { 177 | SAXhandler.endDocument = C.endDocumentSAXFunc(C.endDocumentCgo) 178 | } 179 | 180 | if cb.StartElement != nil { 181 | SAXhandler.startElement = C.startElementSAXFunc(C.startElementCgo) 182 | } 183 | // StartElementNoAttr overrides StartElement 184 | if cb.StartElementNoAttr != nil { 185 | SAXhandler.startElement = C.startElementSAXFunc(C.startElementNoAttrCgo) 186 | } 187 | 188 | if cb.EndElement != nil { 189 | SAXhandler.endElement = C.endElementSAXFunc(C.endElementCgo) 190 | } 191 | 192 | if cb.Characters != nil { 193 | SAXhandler.characters = C.charactersSAXFunc(C.charactersCgo) 194 | } 195 | // CharactersRaw overrides Characters 196 | if cb.CharactersRaw != nil { 197 | SAXhandler.characters = C.charactersSAXFunc(C.charactersRawCgo) 198 | } 199 | 200 | // Pack the callbacks structure into an opaque unsafe.Pointer which we'll 201 | // pass to C as user_data, and C will pass it back to our Go callbacks. 202 | user_data := pointer.Save(&cb) 203 | defer pointer.Unref(user_data) 204 | 205 | bufPtr := unsafe.Pointer(&buf.Bytes()[0]) 206 | rc := C.xmlSAXUserParseMemory(&SAXhandler, user_data, (*C.char)(bufPtr), C.int(buf.Len())) 207 | 208 | // Check if SAX call was finished succesfully 209 | if rc != 0 { 210 | xmlErr := C.getLastError() 211 | msg := strings.TrimSpace(C.GoString(xmlErr.message)) 212 | return fmt.Errorf("line %v: error: %v", xmlErr.line, msg) 213 | } 214 | 215 | return nil 216 | } 217 | 218 | //export goStartDocument 219 | func goStartDocument(user_data unsafe.Pointer) { 220 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 221 | gcb.StartDocument() 222 | } 223 | 224 | //export goEndDocument 225 | func goEndDocument(user_data unsafe.Pointer) { 226 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 227 | gcb.EndDocument() 228 | } 229 | 230 | //export goStartElement 231 | func goStartElement(user_data unsafe.Pointer, name *C.char, attrs **C.char, attrlen C.int) { 232 | // Passing attrs to Go is tricky because it's an array of C strings, 233 | // terminated with a NULL pointer. The C callback startElementCgo calculates 234 | // the length of the array and passes it in as attrlen. We still have to 235 | // convert it to a Go slice, by mapping a slice on the underlying storage 236 | // and copying the attributes, one by one. This is all rather expensive, so 237 | // consider using the StartElementNoAttr callback instead, when applicable. 238 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 239 | length := int(attrlen) 240 | var goattrs []string 241 | if length > 0 { 242 | tmpslice := (*[1 << 30]*C.char)(unsafe.Pointer(attrs))[:length:length] 243 | goattrs = make([]string, length) 244 | for i, s := range tmpslice { 245 | goattrs[i] = C.GoString(s) 246 | } 247 | } 248 | gcb.StartElement(C.GoString(name), goattrs) 249 | } 250 | 251 | //export goStartElementNoAttr 252 | func goStartElementNoAttr(user_data unsafe.Pointer, name *C.char) { 253 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 254 | gcb.StartElementNoAttr(C.GoString(name)) 255 | } 256 | 257 | //export goEndElement 258 | func goEndElement(user_data unsafe.Pointer, name *C.char) { 259 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 260 | gcb.EndElement(C.GoString(name)) 261 | } 262 | 263 | //export goCharacters 264 | func goCharacters(user_data unsafe.Pointer, ch *C.char, chlen C.int) { 265 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 266 | gcb.Characters(C.GoStringN(ch, chlen)) 267 | } 268 | 269 | //export goCharactersRaw 270 | func goCharactersRaw(user_data unsafe.Pointer, ch *C.char, chlen C.int) { 271 | gcb := pointer.Restore(user_data).(*SaxCallbacks) 272 | gcb.CharactersRaw(unsafe.Pointer(ch), int(chlen)) 273 | } 274 | -------------------------------------------------------------------------------- /gosax_test.go: -------------------------------------------------------------------------------- 1 | // Tests. 2 | // 3 | // Eli Bendersky [https://eli.thegreenplace.net] 4 | // This code is in the public domain. 5 | package gosax 6 | 7 | import ( 8 | "bytes" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func TestInit(*testing.T) { 14 | // Test that nothing crashed in init() 15 | } 16 | 17 | func TestBasic(t *testing.T) { 18 | var plantId string 19 | var numOrigins int 20 | var startDoc bool 21 | var endDoc bool 22 | 23 | scb := SaxCallbacks{ 24 | StartDocument: func() { 25 | startDoc = true 26 | }, 27 | EndDocument: func() { 28 | endDoc = true 29 | }, 30 | StartElement: func(name string, attrs []string) { 31 | if name == "plant" { 32 | if len(attrs) < 2 { 33 | t.Errorf("want len(attrs) at least 2, got %v", len(attrs)) 34 | } 35 | if attrs[0] != "id" { 36 | t.Errorf("want 'id' attr, got %v", attrs[0]) 37 | } 38 | plantId = attrs[1] 39 | } else if name == "origin" { 40 | numOrigins++ 41 | } 42 | }, 43 | EndElement: func(name string) { 44 | }, 45 | } 46 | 47 | err := ParseFile("testfiles/fruit.xml", scb) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | if plantId != "27" { 53 | t.Errorf("want plant id %v, got %v", 27, plantId) 54 | } 55 | 56 | if numOrigins != 2 { 57 | t.Errorf("want num origins 2, got %v", numOrigins) 58 | } 59 | if !startDoc { 60 | t.Errorf("want doc start, found none") 61 | } 62 | 63 | if !endDoc { 64 | t.Errorf("want doc end, found none") 65 | } 66 | } 67 | 68 | func TestCharacters(t *testing.T) { 69 | m := make(map[string]bool) 70 | scb := SaxCallbacks{ 71 | Characters: func(contents string) { 72 | m[contents] = true 73 | }, 74 | } 75 | 76 | err := ParseFile("testfiles/fruit.xml", scb) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | chars := []string{"Coffee", "Ethiopia", "Brazil"} 82 | for _, c := range chars { 83 | if _, ok := m[c]; !ok { 84 | t.Errorf("expected to find %v characters", c) 85 | } 86 | } 87 | } 88 | 89 | func TestError(t *testing.T) { 90 | scb := SaxCallbacks{} 91 | err := ParseFile("testfiles/badfile.xml", scb) 92 | if err == nil { 93 | t.Errorf("want non-nil error") 94 | } 95 | if !strings.Contains(err.Error(), "Start tag expected") { 96 | t.Errorf("want start tag error, got '%v'", err) 97 | } 98 | } 99 | 100 | func TestMemParse(t *testing.T) { 101 | m := make(map[string]bool) 102 | scb := SaxCallbacks{ 103 | Characters: func(contents string) { 104 | m[contents] = true 105 | }, 106 | } 107 | buf := bytes.Buffer{} 108 | buf.WriteString(` 109 | 110 | Coffee 111 | Ethiopia 112 | Brazil 113 | `) 114 | err := ParseMem(buf, scb) 115 | if err != nil { 116 | panic(err) 117 | } 118 | 119 | chars := []string{"Coffee", "Ethiopia", "Brazil"} 120 | for _, c := range chars { 121 | if _, ok := m[c]; !ok { 122 | t.Errorf("expected to find %v characters", c) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /pointer/pointer.go: -------------------------------------------------------------------------------- 1 | // package pointer helps pass arbitrary Go values to C code and back. 2 | // 3 | // Taken from https://github.com/mattn/go-pointer/ with small modifications. 4 | // mattn's MIT license applies. 5 | package pointer 6 | 7 | // #include 8 | import "C" 9 | import ( 10 | "sync" 11 | "unsafe" 12 | ) 13 | 14 | var ( 15 | mutex sync.RWMutex 16 | store = map[unsafe.Pointer]interface{}{} 17 | ) 18 | 19 | func Save(v interface{}) unsafe.Pointer { 20 | if v == nil { 21 | return nil 22 | } 23 | 24 | var ptr unsafe.Pointer = C.malloc(C.size_t(1)) 25 | if ptr == nil { 26 | panic("C.malloc failed") 27 | } 28 | 29 | mutex.Lock() 30 | store[ptr] = v 31 | mutex.Unlock() 32 | 33 | return ptr 34 | } 35 | 36 | func Restore(ptr unsafe.Pointer) (v interface{}) { 37 | if ptr == nil { 38 | return nil 39 | } 40 | 41 | mutex.RLock() 42 | v = store[ptr] 43 | mutex.RUnlock() 44 | return 45 | } 46 | 47 | func Unref(ptr unsafe.Pointer) { 48 | if ptr == nil { 49 | return 50 | } 51 | 52 | mutex.Lock() 53 | delete(store, ptr) 54 | mutex.Unlock() 55 | 56 | C.free(ptr) 57 | } 58 | -------------------------------------------------------------------------------- /testfiles/badfile.xml: -------------------------------------------------------------------------------- 1 | 2 | plant id="27"> 3 | 4 | -------------------------------------------------------------------------------- /testfiles/fruit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Coffee 4 | Ethiopia 5 | Brazil 6 | 7 | --------------------------------------------------------------------------------