package main import ( "bufio" "encoding/xml" "fmt" "io" "io/ioutil" "mime/quotedprintable" "os" "strings" "time" ) func optExit(err error) { if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(-1) } } func unfold(s string) string { replacer := strings.NewReplacer("\r\n ", "", "\r\n\t", "") return replacer.Replace(s) } func vCardValue(line string) string { start := strings.LastIndex(line, ":") end := strings.LastIndex(line, ";") if start == -1 { return "" } if end < start { end = len(line) } return line[start+1 : end] } type Contact struct { Name string Numbers NumberSet } type ContactSet []Contact var uniqueID int = 1741 const xmlPreamble = ` AVM Ansage (HD)500@hd-telefonie.avm.de0HD-Musik200@hd-telefonie.avm.de1HD-Sprache100@hd-telefonie.avm.de2 ` const xmlEnd = ` ` func (c ContactSet) ToFritzboxXML() string { var s strings.Builder s.WriteString(xmlPreamble) for _, contact := range c { contact.WriteFritzboxXML(&s) } s.WriteString(xmlEnd) return s.String() } func (c *Contact) WriteFritzboxXML(s io.Writer) { if c == nil { return } uniqueID++ fmt.Fprint(s, "0") xml.Escape(s, []byte(c.Name)) fmt.Fprintf(s, "\n", len(c.Numbers)) c.Numbers.WriteFritzboxXML(s) fmt.Fprintf(s, "%d\n\n", uniqueID) } func (c *Contact) setName(line string) { if c == nil { c = &Contact{} } c.Name = vCardValue(line) if strings.Count(c.Name, "=") > 1 { tmp, err := ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(c.Name))) if err == nil { c.Name = string(tmp) } } } func (c *Contact) addNumber(line string) { if c == nil { c = &Contact{} } tolower := strings.ToLower(line) val := vCardValue(line) var n Number if strings.Contains(tolower, "cell") { n = Number{val, "mobile"} } else { n = Number{val, "home"} } c.Numbers = append(c.Numbers, n) } func (c *Contact) setValue(line string) { if strings.HasPrefix(line, "FN") { c.setName(line) } else if strings.HasPrefix(line, "TEL") { c.addNumber(line) } } type Number struct { Value string Kind string } type NumberSet []Number func (n NumberSet) WriteFritzboxXML(s io.Writer) { for i, number := range n { number.WriteFritzboxXML(s, i) } } func (n *Number) WriteFritzboxXML(s io.Writer, i int) { if n == nil { return } var prio int if i == 0 { prio = 1 } else { prio = 0 } fmt.Fprintf(s, `%s`, n.Kind, prio, i, n.Value) fmt.Fprint(s, "\n") } func GetContacts(buf []byte) (c ContactSet) { s := unfold(string(buf)) scanner := bufio.NewScanner(strings.NewReader(s)) var current *Contact for scanner.Scan() { l := strings.TrimSpace(scanner.Text()) if l == "BEGIN:VCARD" { current = &Contact{} } else if l == "END:VCARD" { c = append(c, *current) current = nil } else { current.setValue(l) } } optExit(scanner.Err()) if current != nil { c = append(c, *current) } return } func main() { if len(os.Args) != 2 { fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "VCF-FILE") os.Exit(-1) } b, err := ioutil.ReadFile(os.Args[1]) optExit(err) outfile := os.Args[1] + "." + time.Now().Format("20060102-150405") + ".xml" err = ioutil.WriteFile(outfile, []byte(GetContacts(b).ToFritzboxXML()), 0644) optExit(err) fmt.Println(outfile) }