You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

361 lines
8.1 KiB

  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, ignoreInlineComment 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. // Check if ignore inline comment
  201. if !ignoreInlineComment {
  202. i := strings.IndexAny(line, "#;")
  203. if i > -1 {
  204. p.comment.WriteString(line[i:])
  205. line = strings.TrimSpace(line[:i])
  206. }
  207. }
  208. // Trim single quotes
  209. if hasSurroundedQuote(line, '\'') ||
  210. hasSurroundedQuote(line, '"') {
  211. line = line[1 : len(line)-1]
  212. }
  213. return line, nil
  214. }
  215. // parse parses data through an io.Reader.
  216. func (f *File) parse(reader io.Reader) (err error) {
  217. p := newParser(reader)
  218. if err = p.BOM(); err != nil {
  219. return fmt.Errorf("BOM: %v", err)
  220. }
  221. // Ignore error because default section name is never empty string.
  222. section, _ := f.NewSection(DEFAULT_SECTION)
  223. var line []byte
  224. var inUnparseableSection bool
  225. for !p.isEOF {
  226. line, err = p.readUntil('\n')
  227. if err != nil {
  228. return err
  229. }
  230. line = bytes.TrimLeftFunc(line, unicode.IsSpace)
  231. if len(line) == 0 {
  232. continue
  233. }
  234. // Comments
  235. if line[0] == '#' || line[0] == ';' {
  236. // Note: we do not care ending line break,
  237. // it is needed for adding second line,
  238. // so just clean it once at the end when set to value.
  239. p.comment.Write(line)
  240. continue
  241. }
  242. // Section
  243. if line[0] == '[' {
  244. // Read to the next ']' (TODO: support quoted strings)
  245. // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
  246. closeIdx := bytes.LastIndex(line, []byte("]"))
  247. if closeIdx == -1 {
  248. return fmt.Errorf("unclosed section: %s", line)
  249. }
  250. name := string(line[1:closeIdx])
  251. section, err = f.NewSection(name)
  252. if err != nil {
  253. return err
  254. }
  255. comment, has := cleanComment(line[closeIdx+1:])
  256. if has {
  257. p.comment.Write(comment)
  258. }
  259. section.Comment = strings.TrimSpace(p.comment.String())
  260. // Reset aotu-counter and comments
  261. p.comment.Reset()
  262. p.count = 1
  263. inUnparseableSection = false
  264. for i := range f.options.UnparseableSections {
  265. if f.options.UnparseableSections[i] == name ||
  266. (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
  267. inUnparseableSection = true
  268. continue
  269. }
  270. }
  271. continue
  272. }
  273. if inUnparseableSection {
  274. section.isRawSection = true
  275. section.rawBody += string(line)
  276. continue
  277. }
  278. kname, offset, err := readKeyName(line)
  279. if err != nil {
  280. // Treat as boolean key when desired, and whole line is key name.
  281. if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
  282. kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
  283. if err != nil {
  284. return err
  285. }
  286. key, err := section.NewBooleanKey(kname)
  287. if err != nil {
  288. return err
  289. }
  290. key.Comment = strings.TrimSpace(p.comment.String())
  291. p.comment.Reset()
  292. continue
  293. }
  294. return err
  295. }
  296. // Auto increment.
  297. isAutoIncr := false
  298. if kname == "-" {
  299. isAutoIncr = true
  300. kname = "#" + strconv.Itoa(p.count)
  301. p.count++
  302. }
  303. value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
  304. if err != nil {
  305. return err
  306. }
  307. key, err := section.NewKey(kname, value)
  308. if err != nil {
  309. return err
  310. }
  311. key.isAutoIncrement = isAutoIncr
  312. key.Comment = strings.TrimSpace(p.comment.String())
  313. p.comment.Reset()
  314. }
  315. return nil
  316. }