Как определиться с количеством пробелов, которые использует fmt.Fscanf?
Я пытаюсь реализовать декодер PPM в Go. PPM - это формат изображения, который состоит из заголовка открытого текста и затем некоторых двоичных данных изображения. Заголовок выглядит так (из спецификации):
Каждое изображение PPM состоит из следующего:
- "Магическое число" для определения типа файла. Волшебное число изображения в ppm - это два символа "P6".
- Пробелы (пробелы, табуляции, CR, LF).
- Ширина, отформатированная как символы ASCII в десятичном формате.
- Пробелы.
- Высота, опять же в десятичном виде ASCII.
- Пробелы.
- Максимальное значение цвета (Maxval), опять же в десятичном виде ASCII. Должно быть меньше 65536 и больше нуля.
- Один символ пробела (обычно перевод строки).
Я пытаюсь расшифровать этот заголовок с 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)
}
}