1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "linereader.h"
4
5#include <base/dbg.h>
6#include <base/io.h>
7#include <base/str.h>
8
9#include <cstdlib>
10
11static bool IsValidLine(const char *pLine)
12{
13 if(!str_utf8_check(str: pLine))
14 {
15 // Skip lines containing invalid UTF-8
16 return false;
17 }
18
19 // Skip lines containing any control character except `\t`
20 for(size_t Index = 0; pLine[Index] != '\0'; ++Index)
21 {
22 if((unsigned char)pLine[Index] < ' ' && pLine[Index] != '\t')
23 {
24 return false;
25 }
26 }
27
28 return true;
29}
30
31CLineReader::CLineReader()
32{
33 m_pBuffer = nullptr;
34}
35
36CLineReader::~CLineReader()
37{
38 free(ptr: m_pBuffer);
39}
40
41bool CLineReader::OpenFile(IOHANDLE File)
42{
43 if(!File)
44 {
45 return false;
46 }
47 char *pBuffer = io_read_all_str(io: File);
48 io_close(io: File);
49 if(pBuffer == nullptr)
50 {
51 return false;
52 }
53 OpenBuffer(pBuffer);
54 return true;
55}
56
57void CLineReader::OpenBuffer(char *pBuffer)
58{
59 dbg_assert(pBuffer != nullptr, "Line reader initialized without valid buffer");
60
61 m_pBuffer = pBuffer;
62 m_BufferPos = 0;
63 m_ReadLastLine = false;
64
65 // Skip UTF-8 BOM
66 if(m_pBuffer[0] == '\xEF' && m_pBuffer[1] == '\xBB' && m_pBuffer[2] == '\xBF')
67 {
68 m_BufferPos += 3;
69 }
70}
71
72const char *CLineReader::Get()
73{
74 dbg_assert(m_pBuffer != nullptr, "Line reader not initialized");
75 if(m_ReadLastLine)
76 {
77 return nullptr;
78 }
79
80 unsigned LineStart = m_BufferPos;
81 while(true)
82 {
83 if(m_pBuffer[m_BufferPos] == '\0' || m_pBuffer[m_BufferPos] == '\n' || (m_pBuffer[m_BufferPos] == '\r' && m_pBuffer[m_BufferPos + 1] == '\n'))
84 {
85 if(m_pBuffer[m_BufferPos] == '\0')
86 {
87 m_ReadLastLine = true;
88 }
89 else
90 {
91 if(m_pBuffer[m_BufferPos] == '\r')
92 {
93 m_pBuffer[m_BufferPos] = '\0';
94 ++m_BufferPos;
95 }
96 m_pBuffer[m_BufferPos] = '\0';
97 ++m_BufferPos;
98 }
99
100 if(!IsValidLine(pLine: &m_pBuffer[LineStart]))
101 {
102 if(m_ReadLastLine)
103 {
104 return nullptr;
105 }
106 LineStart = m_BufferPos;
107 continue;
108 }
109 // Skip trailing empty line
110 if(m_ReadLastLine && m_pBuffer[LineStart] == '\0')
111 {
112 return nullptr;
113 }
114 return &m_pBuffer[LineStart];
115 }
116 ++m_BufferPos;
117 }
118}
119