Как определиться с количеством пробелов, которые использует fmt.Fscanf?

Я пытаюсь реализовать декодер PPM в Go. PPM - это формат изображения, который состоит из заголовка открытого текста и затем некоторых двоичных данных изображения. Заголовок выглядит так (из спецификации):

Каждое изображение PPM состоит из следующего:

  1. "Магическое число" для определения типа файла. Волшебное число изображения в ppm - это два символа "P6".
  2. Пробелы (пробелы, табуляции, CR, LF).
  3. Ширина, отформатированная как символы ASCII в десятичном формате.
  4. Пробелы.
  5. Высота, опять же в десятичном виде ASCII.
  6. Пробелы.
  7. Максимальное значение цвета (Maxval), опять же в десятичном виде ASCII. Должно быть меньше 65536 и больше нуля.
  8. Один символ пробела (обычно перевод строки).

Я пытаюсь расшифровать этот заголовок с fmt.Fscanf функция. Следующий звонокfmt.Fscanf анализирует заголовок (без учета пояснения, поясненного ниже):

var magic string
var width, height, maxVal uint

fmt.Fscanf(input,"%2s %d %d %d",&magic,&width,&height,&maxVal)

Документация fmt состояния:

Замечания: Fscan и т. д. может прочитать один символ (руну) после ввода, которое они возвращают, что означает, что цикл, вызывающий процедуру сканирования, может пропустить часть ввода. Обычно это проблема, только если между входными значениями нет пробела. Если читатель предоставил Fscan инвентарь ReadRuneэтот метод будет использоваться для чтения символов. Если читатель также реализует UnreadRuneэтот метод будет использоваться для сохранения символа, и последующие вызовы не потеряют данные. Приложить ReadRune а также UnreadRune методы для читателя без этой возможности, использовать bufio.NewReader,

Поскольку следующий символ после последнего пробела уже является началом данных изображения, я должен быть уверен в том, сколько пробелов fmt.Fscanf действительно потреблял после прочтения MaxVal, Мой код должен работать на любом считывателе, который был предоставлен вызывающей стороной, и его части не должны считываться после конца заголовка, поэтому перенос содержимого в буферизованный считыватель не возможен; читатель с буферизацией может читать больше из ввода, чем я на самом деле хочу читать.

Некоторые тесты показывают, что анализ фиктивного символа в конце решает следующие проблемы:

var magic string
var width, height, maxVal uint
var dummy byte

fmt.Fscanf(input,"%2s %d %d %d%c",&magic,&width,&height,&maxVal,&dummy)

Это гарантированно работает в соответствии со спецификацией?

1 ответ

Решение

Нет, я не считаю это безопасным. Хотя теперь это работает, в документации говорится, что функция оставляет за собой право считывать значение после одного символа, если у вас нет UnreadRune() метод.

Заворачивая ваш читатель в bufio.Reader, вы можете убедиться, что у читателя есть UnreadRune() метод. Затем вам нужно будет прочитать последний пробел самостоятельно.

buf := bufio.NewReader(input)
fmt.Fscanf(buf,"%2s %d %d %d",&magic,&width,&height,&maxVal)
buf.ReadRune() // remove next rune (the whitespace) from the buffer.


Редактировать:

Как мы уже обсуждали в чате, вы можете предположить, что метод фиктивного символа работает, а затем написать тест, чтобы вы знали, когда он перестанет работать. Тест может быть что-то вроде:

func TestFmtBehavior(t *testing.T) {
    // use multireader to prevent r from implementing io.RuneScanner
    r := io.MultiReader(bytes.NewReader([]byte("data  ")))

    n, err := fmt.Fscanf(r, "%s%c", new(string), new(byte))
    if n != 2 || err != nil {
        t.Error("failed scan", n, err)
    }

    // the dummy char read 1 extra char past "data".
    // one byte should still remain
    if n, err := r.Read(make([]byte, 5)); n != 1 {
        t.Error("assertion failed", n, err)
    }
}
Другие вопросы по тегам