parser.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. // Copyright 2015 Unknwon
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package ini
  15. import (
  16. "bufio"
  17. "bytes"
  18. "fmt"
  19. "io"
  20. "strconv"
  21. "strings"
  22. "unicode"
  23. )
  24. type tokenType int
  25. const (
  26. _TOKEN_INVALID tokenType = iota
  27. _TOKEN_COMMENT
  28. _TOKEN_SECTION
  29. _TOKEN_KEY
  30. )
  31. type parser struct {
  32. buf *bufio.Reader
  33. isEOF bool
  34. count int
  35. comment *bytes.Buffer
  36. }
  37. func newParser(r io.Reader) *parser {
  38. return &parser{
  39. buf: bufio.NewReader(r),
  40. count: 1,
  41. comment: &bytes.Buffer{},
  42. }
  43. }
  44. // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
  45. // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
  46. func (p *parser) BOM() error {
  47. mask, err := p.buf.Peek(2)
  48. if err != nil && err != io.EOF {
  49. return err
  50. } else if len(mask) < 2 {
  51. return nil
  52. }
  53. switch {
  54. case mask[0] == 254 && mask[1] == 255:
  55. fallthrough
  56. case mask[0] == 255 && mask[1] == 254:
  57. p.buf.Read(mask)
  58. case mask[0] == 239 && mask[1] == 187:
  59. mask, err := p.buf.Peek(3)
  60. if err != nil && err != io.EOF {
  61. return err
  62. } else if len(mask) < 3 {
  63. return nil
  64. }
  65. if mask[2] == 191 {
  66. p.buf.Read(mask)
  67. }
  68. }
  69. return nil
  70. }
  71. func (p *parser) readUntil(delim byte) ([]byte, error) {
  72. data, err := p.buf.ReadBytes(delim)
  73. if err != nil {
  74. if err == io.EOF {
  75. p.isEOF = true
  76. } else {
  77. return nil, err
  78. }
  79. }
  80. return data, nil
  81. }
  82. func cleanComment(in []byte) ([]byte, bool) {
  83. i := bytes.IndexAny(in, "#;")
  84. if i == -1 {
  85. return nil, false
  86. }
  87. return in[i:], true
  88. }
  89. func readKeyName(in []byte) (string, int, error) {
  90. line := string(in)
  91. // Check if key name surrounded by quotes.
  92. var keyQuote string
  93. if line[0] == '"' {
  94. if len(line) > 6 && string(line[0:3]) == `"""` {
  95. keyQuote = `"""`
  96. } else {
  97. keyQuote = `"`
  98. }
  99. } else if line[0] == '`' {
  100. keyQuote = "`"
  101. }
  102. // Get out key name
  103. endIdx := -1
  104. if len(keyQuote) > 0 {
  105. startIdx := len(keyQuote)
  106. // FIXME: fail case -> """"""name"""=value
  107. pos := strings.Index(line[startIdx:], keyQuote)
  108. if pos == -1 {
  109. return "", -1, fmt.Errorf("missing closing key quote: %s", line)
  110. }
  111. pos += startIdx
  112. // Find key-value delimiter
  113. i := strings.IndexAny(line[pos+startIdx:], "=:")
  114. if i < 0 {
  115. return "", -1, ErrDelimiterNotFound{line}
  116. }
  117. endIdx = pos + i
  118. return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
  119. }
  120. endIdx = strings.IndexAny(line, "=:")
  121. if endIdx < 0 {
  122. return "", -1, ErrDelimiterNotFound{line}
  123. }
  124. return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
  125. }
  126. func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
  127. for {
  128. data, err := p.readUntil('\n')
  129. if err != nil {
  130. return "", err
  131. }
  132. next := string(data)
  133. pos := strings.LastIndex(next, valQuote)
  134. if pos > -1 {
  135. val += next[:pos]
  136. comment, has := cleanComment([]byte(next[pos:]))
  137. if has {
  138. p.comment.Write(bytes.TrimSpace(comment))
  139. }
  140. break
  141. }
  142. val += next
  143. if p.isEOF {
  144. return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
  145. }
  146. }
  147. return val, nil
  148. }
  149. func (p *parser) readContinuationLines(val string) (string, error) {
  150. for {
  151. data, err := p.readUntil('\n')
  152. if err != nil {
  153. return "", err
  154. }
  155. next := strings.TrimSpace(string(data))
  156. if len(next) == 0 {
  157. break
  158. }
  159. val += next
  160. if val[len(val)-1] != '\\' {
  161. break
  162. }
  163. val = val[:len(val)-1]
  164. }
  165. return val, nil
  166. }
  167. // hasSurroundedQuote check if and only if the first and last characters
  168. // are quotes \" or \'.
  169. // It returns false if any other parts also contain same kind of quotes.
  170. func hasSurroundedQuote(in string, quote byte) bool {
  171. return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote &&
  172. strings.IndexByte(in[1:], quote) == len(in)-2
  173. }
  174. func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
  175. line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
  176. if len(line) == 0 {
  177. return "", nil
  178. }
  179. var valQuote string
  180. if len(line) > 3 && string(line[0:3]) == `"""` {
  181. valQuote = `"""`
  182. } else if line[0] == '`' {
  183. valQuote = "`"
  184. }
  185. if len(valQuote) > 0 {
  186. startIdx := len(valQuote)
  187. pos := strings.LastIndex(line[startIdx:], valQuote)
  188. // Check for multi-line value
  189. if pos == -1 {
  190. return p.readMultilines(line, line[startIdx:], valQuote)
  191. }
  192. return line[startIdx : pos+startIdx], nil
  193. }
  194. // Won't be able to reach here if value only contains whitespace.
  195. line = strings.TrimSpace(line)
  196. // Check continuation lines when desired.
  197. if !ignoreContinuation && line[len(line)-1] == '\\' {
  198. return p.readContinuationLines(line[:len(line)-1])
  199. }
  200. i := strings.IndexAny(line, "#;")
  201. if i > -1 {
  202. p.comment.WriteString(line[i:])
  203. line = strings.TrimSpace(line[:i])
  204. }
  205. // Trim single quotes
  206. if hasSurroundedQuote(line, '\'') ||
  207. hasSurroundedQuote(line, '"') {
  208. line = line[1 : len(line)-1]
  209. }
  210. return line, nil
  211. }
  212. // parse parses data through an io.Reader.
  213. func (f *File) parse(reader io.Reader) (err error) {
  214. p := newParser(reader)
  215. if err = p.BOM(); err != nil {
  216. return fmt.Errorf("BOM: %v", err)
  217. }
  218. // Ignore error because default section name is never empty string.
  219. section, _ := f.NewSection(DEFAULT_SECTION)
  220. var line []byte
  221. var inUnparseableSection bool
  222. for !p.isEOF {
  223. line, err = p.readUntil('\n')
  224. if err != nil {
  225. return err
  226. }
  227. line = bytes.TrimLeftFunc(line, unicode.IsSpace)
  228. if len(line) == 0 {
  229. continue
  230. }
  231. // Comments
  232. if line[0] == '#' || line[0] == ';' {
  233. // Note: we do not care ending line break,
  234. // it is needed for adding second line,
  235. // so just clean it once at the end when set to value.
  236. p.comment.Write(line)
  237. continue
  238. }
  239. // Section
  240. if line[0] == '[' {
  241. // Read to the next ']' (TODO: support quoted strings)
  242. // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
  243. closeIdx := bytes.LastIndex(line, []byte("]"))
  244. if closeIdx == -1 {
  245. return fmt.Errorf("unclosed section: %s", line)
  246. }
  247. name := string(line[1:closeIdx])
  248. section, err = f.NewSection(name)
  249. if err != nil {
  250. return err
  251. }
  252. comment, has := cleanComment(line[closeIdx+1:])
  253. if has {
  254. p.comment.Write(comment)
  255. }
  256. section.Comment = strings.TrimSpace(p.comment.String())
  257. // Reset aotu-counter and comments
  258. p.comment.Reset()
  259. p.count = 1
  260. inUnparseableSection = false
  261. for i := range f.options.UnparseableSections {
  262. if f.options.UnparseableSections[i] == name ||
  263. (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
  264. inUnparseableSection = true
  265. continue
  266. }
  267. }
  268. continue
  269. }
  270. if inUnparseableSection {
  271. section.isRawSection = true
  272. section.rawBody += string(line)
  273. continue
  274. }
  275. kname, offset, err := readKeyName(line)
  276. if err != nil {
  277. // Treat as boolean key when desired, and whole line is key name.
  278. if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
  279. kname, err := p.readValue(line, f.options.IgnoreContinuation)
  280. if err != nil {
  281. return err
  282. }
  283. key, err := section.NewBooleanKey(kname)
  284. if err != nil {
  285. return err
  286. }
  287. key.Comment = strings.TrimSpace(p.comment.String())
  288. p.comment.Reset()
  289. continue
  290. }
  291. return err
  292. }
  293. // Auto increment.
  294. isAutoIncr := false
  295. if kname == "-" {
  296. isAutoIncr = true
  297. kname = "#" + strconv.Itoa(p.count)
  298. p.count++
  299. }
  300. key, err := section.NewKey(kname, "")
  301. if err != nil {
  302. return err
  303. }
  304. key.isAutoIncrement = isAutoIncr
  305. value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
  306. if err != nil {
  307. return err
  308. }
  309. key.SetValue(value)
  310. key.Comment = strings.TrimSpace(p.comment.String())
  311. p.comment.Reset()
  312. }
  313. return nil
  314. }