xref: /aosp_15_r20/external/pdfium/core/fxcrt/css/cfx_csssyntaxparser.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2014 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fxcrt/css/cfx_csssyntaxparser.h"
8 
9 #include "core/fxcrt/css/cfx_cssdata.h"
10 #include "core/fxcrt/css/cfx_cssdeclaration.h"
11 #include "core/fxcrt/fx_codepage.h"
12 #include "core/fxcrt/fx_extension.h"
13 
14 namespace {
15 
IsSelectorStart(wchar_t wch)16 bool IsSelectorStart(wchar_t wch) {
17   return wch == '.' || wch == '#' || wch == '*' ||
18          (isascii(wch) && isalpha(wch));
19 }
20 
21 }  // namespace
22 
CFX_CSSSyntaxParser(WideStringView str)23 CFX_CSSSyntaxParser::CFX_CSSSyntaxParser(WideStringView str) : m_Input(str) {}
24 
25 CFX_CSSSyntaxParser::~CFX_CSSSyntaxParser() = default;
26 
SetParseOnlyDeclarations()27 void CFX_CSSSyntaxParser::SetParseOnlyDeclarations() {
28   m_eMode = Mode::kPropertyName;
29 }
30 
DoSyntaxParse()31 CFX_CSSSyntaxParser::Status CFX_CSSSyntaxParser::DoSyntaxParse() {
32   m_Output.Clear();
33   if (m_bHasError)
34     return Status::kError;
35 
36   while (!m_Input.IsEOF()) {
37     wchar_t wch = m_Input.GetChar();
38     switch (m_eMode) {
39       case Mode::kRuleSet:
40         switch (wch) {
41           case '}':
42             m_bHasError = true;
43             return Status::kError;
44           case '/':
45             if (m_Input.GetNextChar() == '*') {
46               SaveMode(Mode::kRuleSet);
47               m_eMode = Mode::kComment;
48               break;
49             }
50             [[fallthrough]];
51           default:
52             if (wch <= ' ') {
53               m_Input.MoveNext();
54             } else if (IsSelectorStart(wch)) {
55               m_eMode = Mode::kSelector;
56               return Status::kStyleRule;
57             } else {
58               m_bHasError = true;
59               return Status::kError;
60             }
61             break;
62         }
63         break;
64       case Mode::kSelector:
65         switch (wch) {
66           case ',':
67             m_Input.MoveNext();
68             if (!m_Output.IsEmpty())
69               return Status::kSelector;
70             break;
71           case '{':
72             if (!m_Output.IsEmpty())
73               return Status::kSelector;
74             m_Input.MoveNext();
75             SaveMode(Mode::kRuleSet);  // Back to validate ruleset again.
76             m_eMode = Mode::kPropertyName;
77             return Status::kDeclOpen;
78           case '/':
79             if (m_Input.GetNextChar() == '*') {
80               SaveMode(Mode::kSelector);
81               m_eMode = Mode::kComment;
82               if (!m_Output.IsEmpty())
83                 return Status::kSelector;
84               break;
85             }
86             [[fallthrough]];
87           default:
88             m_Output.AppendCharIfNotLeadingBlank(wch);
89             m_Input.MoveNext();
90             break;
91         }
92         break;
93       case Mode::kPropertyName:
94         switch (wch) {
95           case ':':
96             m_Input.MoveNext();
97             m_eMode = Mode::kPropertyValue;
98             return Status::kPropertyName;
99           case '}':
100             m_Input.MoveNext();
101             if (!RestoreMode())
102               return Status::kError;
103 
104             return Status::kDeclClose;
105           case '/':
106             if (m_Input.GetNextChar() == '*') {
107               SaveMode(Mode::kPropertyName);
108               m_eMode = Mode::kComment;
109               if (!m_Output.IsEmpty())
110                 return Status::kPropertyName;
111               break;
112             }
113             [[fallthrough]];
114           default:
115             m_Output.AppendCharIfNotLeadingBlank(wch);
116             m_Input.MoveNext();
117             break;
118         }
119         break;
120       case Mode::kPropertyValue:
121         switch (wch) {
122           case ';':
123             m_Input.MoveNext();
124             [[fallthrough]];
125           case '}':
126             m_eMode = Mode::kPropertyName;
127             return Status::kPropertyValue;
128           case '/':
129             if (m_Input.GetNextChar() == '*') {
130               SaveMode(Mode::kPropertyValue);
131               m_eMode = Mode::kComment;
132               if (!m_Output.IsEmpty())
133                 return Status::kPropertyValue;
134               break;
135             }
136             [[fallthrough]];
137           default:
138             m_Output.AppendCharIfNotLeadingBlank(wch);
139             m_Input.MoveNext();
140             break;
141         }
142         break;
143       case Mode::kComment:
144         if (wch == '*' && m_Input.GetNextChar() == '/') {
145           if (!RestoreMode())
146             return Status::kError;
147           m_Input.MoveNext();
148         }
149         m_Input.MoveNext();
150         break;
151     }
152   }
153   if (m_eMode == Mode::kPropertyValue && !m_Output.IsEmpty())
154     return Status::kPropertyValue;
155 
156   return Status::kEOS;
157 }
158 
SaveMode(Mode mode)159 void CFX_CSSSyntaxParser::SaveMode(Mode mode) {
160   m_ModeStack.push(mode);
161 }
162 
RestoreMode()163 bool CFX_CSSSyntaxParser::RestoreMode() {
164   if (m_ModeStack.empty()) {
165     m_bHasError = true;
166     return false;
167   }
168   m_eMode = m_ModeStack.top();
169   m_ModeStack.pop();
170   return true;
171 }
172 
GetCurrentString() const173 WideStringView CFX_CSSSyntaxParser::GetCurrentString() const {
174   return m_Output.GetTrailingBlankTrimmedString();
175 }
176