blob: 4308bb11db817fa2b0b8d92a60ea570840026abd [file] [log] [blame]
// Copyright 2014 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fxcrt/css/cfx_csssyntaxparser.h"
#include "core/fxcrt/css/cfx_cssdata.h"
#include "core/fxcrt/css/cfx_cssdeclaration.h"
#include "core/fxcrt/fx_codepage.h"
#include "core/fxcrt/fx_extension.h"
namespace {
bool IsSelectorStart(wchar_t wch) {
return wch == '.' || wch == '#' || wch == '*' ||
(isascii(wch) && isalpha(wch));
}
} // namespace
CFX_CSSSyntaxParser::CFX_CSSSyntaxParser(WideStringView str) : input_(str) {}
CFX_CSSSyntaxParser::~CFX_CSSSyntaxParser() = default;
void CFX_CSSSyntaxParser::SetParseOnlyDeclarations() {
mode_ = Mode::kPropertyName;
}
CFX_CSSSyntaxParser::Status CFX_CSSSyntaxParser::DoSyntaxParse() {
output_.Clear();
if (has_error_) {
return Status::kError;
}
while (!input_.IsEOF()) {
wchar_t wch = input_.GetChar();
switch (mode_) {
case Mode::kRuleSet:
switch (wch) {
case '}':
has_error_ = true;
return Status::kError;
case '/':
if (input_.GetNextChar() == '*') {
SaveMode(Mode::kRuleSet);
mode_ = Mode::kComment;
break;
}
[[fallthrough]];
default:
if (wch <= ' ') {
input_.MoveNext();
} else if (IsSelectorStart(wch)) {
mode_ = Mode::kSelector;
return Status::kStyleRule;
} else {
has_error_ = true;
return Status::kError;
}
break;
}
break;
case Mode::kSelector:
switch (wch) {
case ',':
input_.MoveNext();
if (!output_.IsEmpty()) {
return Status::kSelector;
}
break;
case '{':
if (!output_.IsEmpty()) {
return Status::kSelector;
}
input_.MoveNext();
SaveMode(Mode::kRuleSet); // Back to validate ruleset again.
mode_ = Mode::kPropertyName;
return Status::kDeclOpen;
case '/':
if (input_.GetNextChar() == '*') {
SaveMode(Mode::kSelector);
mode_ = Mode::kComment;
if (!output_.IsEmpty()) {
return Status::kSelector;
}
break;
}
[[fallthrough]];
default:
output_.AppendCharIfNotLeadingBlank(wch);
input_.MoveNext();
break;
}
break;
case Mode::kPropertyName:
switch (wch) {
case ':':
input_.MoveNext();
mode_ = Mode::kPropertyValue;
return Status::kPropertyName;
case '}':
input_.MoveNext();
if (!RestoreMode()) {
return Status::kError;
}
return Status::kDeclClose;
case '/':
if (input_.GetNextChar() == '*') {
SaveMode(Mode::kPropertyName);
mode_ = Mode::kComment;
if (!output_.IsEmpty()) {
return Status::kPropertyName;
}
break;
}
[[fallthrough]];
default:
output_.AppendCharIfNotLeadingBlank(wch);
input_.MoveNext();
break;
}
break;
case Mode::kPropertyValue:
switch (wch) {
case ';':
input_.MoveNext();
[[fallthrough]];
case '}':
mode_ = Mode::kPropertyName;
return Status::kPropertyValue;
case '/':
if (input_.GetNextChar() == '*') {
SaveMode(Mode::kPropertyValue);
mode_ = Mode::kComment;
if (!output_.IsEmpty()) {
return Status::kPropertyValue;
}
break;
}
[[fallthrough]];
default:
output_.AppendCharIfNotLeadingBlank(wch);
input_.MoveNext();
break;
}
break;
case Mode::kComment:
if (wch == '*' && input_.GetNextChar() == '/') {
if (!RestoreMode()) {
return Status::kError;
}
input_.MoveNext();
}
input_.MoveNext();
break;
}
}
if (mode_ == Mode::kPropertyValue && !output_.IsEmpty()) {
return Status::kPropertyValue;
}
return Status::kEOS;
}
void CFX_CSSSyntaxParser::SaveMode(Mode mode) {
mode_stack_.push(mode);
}
bool CFX_CSSSyntaxParser::RestoreMode() {
if (mode_stack_.empty()) {
has_error_ = true;
return false;
}
mode_ = mode_stack_.top();
mode_stack_.pop();
return true;
}
WideStringView CFX_CSSSyntaxParser::GetCurrentString() const {
return output_.GetTrailingBlankTrimmedString();
}