parser.go (4486B)
1 package sdp 2 3 import ( 4 "bufio" 5 "fmt" 6 "net/url" 7 "strconv" 8 "strings" 9 ) 10 11 type parser struct { 12 *bufio.Scanner 13 err error 14 // Field name and value from the current line. 15 key, value string 16 next []string // expected next field names 17 18 session Session 19 } 20 21 var ftab = [...]string{"i", "u", "e", "p", "c", "b", "t", "r", "z", "a", "m"} 22 23 var mtab = [...]string{"i", "c", "b", "a", "m"} 24 25 func (p *parser) scan() bool { 26 if !p.Scan() { 27 p.err = p.Err() 28 return false 29 } 30 line := strings.TrimSpace(p.Text()) 31 if line == "" { 32 p.err = fmt.Errorf("illegal empty line") 33 return false 34 } 35 k, v, found := strings.Cut(line, "=") 36 if !found { 37 p.err = fmt.Errorf("parse field %q, missing %q", k, "=") 38 return false 39 } 40 p.key = k 41 p.value = v 42 43 if p.next != nil { 44 for i := range p.next { 45 if p.next[i] == p.key { 46 return true 47 } 48 } 49 p.err = fmt.Errorf("unexpected field %q: expected one of %q", k, p.next) 50 return false 51 } 52 return true 53 } 54 55 func (p *parser) parse() error { 56 next := "v" 57 for p.scan() { 58 if p.key != next { 59 return fmt.Errorf("expected key %q, found %q", next, p.key) 60 } 61 switch p.key { 62 case "v": 63 i, err := strconv.Atoi(p.value) 64 if err != nil { 65 return fmt.Errorf("parse version: %w", err) 66 } 67 if i != 0 { 68 return fmt.Errorf("unsupported version %d", i) 69 } 70 next = "o" 71 case "o": 72 o, err := parseOrigin(p.value) 73 if err != nil { 74 return fmt.Errorf("parse origin: %w", err) 75 } 76 p.session.Origin = o 77 next = "s" 78 case "s": 79 if p.value == "" { 80 return fmt.Errorf("empty name") 81 } 82 p.session.Name = p.value 83 return p.parseOptional() 84 } 85 } 86 return p.err 87 } 88 89 func (p *parser) parseOptional() error { 90 p.next = ftab[:] 91 for p.scan() { 92 switch p.key { 93 case "i": 94 p.session.Info = p.value 95 p.next = ftab[1:] 96 case "u": 97 u, err := url.Parse(p.value) 98 if err != nil { 99 return fmt.Errorf("parse uri: %w", err) 100 } 101 p.session.URI = u 102 p.next = ftab[2:] 103 case "e": 104 addr, err := parseEmail(p.value) 105 if err != nil { 106 return fmt.Errorf("parse email: %w", err) 107 } 108 p.session.Email = addr 109 p.next = ftab[3:] 110 case "p": 111 p.session.Phone = cleanPhone(p.value) 112 p.next = ftab[4:] 113 case "c": 114 conn, err := parseConnInfo(p.value) 115 if err != nil { 116 return fmt.Errorf("parse connection info: %w", err) 117 } 118 p.session.Connection = &conn 119 p.next = ftab[5:] 120 case "b": 121 bw, err := parseBandwidth(p.value) 122 if err != nil { 123 return fmt.Errorf("parse bandwidth line %q: %w", p.value, err) 124 } 125 p.session.Bandwidth = &bw 126 p.next = ftab[6:] 127 case "t": 128 when, err := parseTimes(p.value) 129 if err != nil { 130 return fmt.Errorf("parse time description: %w", err) 131 } 132 p.session.Time = when 133 p.next = ftab[7:] 134 case "r": 135 repeat, err := parseRepeat(p.value) 136 if err != nil { 137 return fmt.Errorf("parse repeat: %w", err) 138 } 139 p.session.Repeat = &repeat 140 p.next = ftab[8:] 141 case "z": 142 var err error 143 p.session.Adjustments, err = parseAdjustments(p.value) 144 if err != nil { 145 return fmt.Errorf("parse time adjustments: %w", err) 146 } 147 p.next = ftab[9:] 148 case "a": 149 p.session.Attributes = strings.Fields(p.value) 150 p.next = ftab[10:] 151 case "m": 152 m, err := parseMedia(p.value) 153 if err != nil { 154 return fmt.Errorf("parse media info from %q: %w", p.value, err) 155 } 156 p.session.Media = append(p.session.Media, m) 157 p.next = mtab[:] 158 return p.parseMedia() 159 default: 160 return fmt.Errorf("unknown field key %s", p.key) 161 } 162 } 163 return p.err 164 } 165 166 func (p *parser) parseMedia() error { 167 var media *Media 168 if len(p.session.Media) > 0 { 169 media = &p.session.Media[len(p.session.Media)-1] 170 } 171 for p.scan() { 172 switch p.key { 173 case "i": 174 media.Title = p.value 175 p.next = mtab[1:] 176 case "c": 177 conn, err := parseConnInfo(p.value) 178 if err != nil { 179 return fmt.Errorf("parse connection info: %w", err) 180 } 181 media.Connection = &conn 182 p.next = mtab[2:] 183 case "b": 184 bw, err := parseBandwidth(p.value) 185 if err != nil { 186 return fmt.Errorf("parse bandwidth: %w", err) 187 } 188 media.Bandwidth = &bw 189 p.next = mtab[3:] 190 case "a": 191 media.Attributes = strings.Fields(p.value) 192 p.next = mtab[4:] 193 case "m": 194 m, err := parseMedia(p.value) 195 if err != nil { 196 return fmt.Errorf("parse media description: %w", err) 197 } 198 p.session.Media = append(p.session.Media, m) 199 media = &p.session.Media[len(p.session.Media)-1] 200 p.next = mtab[:] 201 default: 202 return fmt.Errorf("unsupported field char %s", p.key) 203 } 204 } 205 return p.err 206 }