blob: e5a4d412a4adb93ef8eaba854e803ac24ab341dc [file] [log] [blame] [edit]
// 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) : m_Input(str) {}
CFX_CSSSyntaxParser::~CFX_CSSSyntaxParser() = default;
void CFX_CSSSyntaxParser::SetParseOnlyDeclarations() {
m_eMode = Mode::kPropertyName;
}
CFX_CSSSyntaxParser::Status CFX_CSSSyntaxParser::DoSyntaxParse() {
m_Output.Clear();
if (m_bHasError)
return Status::kError;
while (!m_Input.IsEOF()) {
wchar_t wch = m_Input.GetChar();
switch (m_eMode) {
case Mode::kRuleSet:
switch (wch) {
case '}':
m_bHasError = true;
return Status::kError;
case '/':
if (m_Input.GetNextChar() == '*') {
SaveMode(Mode::kRuleSet);
m_eMode = Mode::kComment;
break;
}
[[fallthrough]];
default:
if (wch <= ' ') {
m_Input.MoveNext();
} else if (IsSelectorStart(wch)) {
m_eMode = Mode::kSelector;
return Status::kStyleRule;
} else {
m_bHasError = true;
return Status::kError;
}
break;
}
break;
case Mode::kSelector:
switch (wch) {
case ',':
m_Input.MoveNext();
if (!m_Output.IsEmpty())
return Status::kSelector;
break;
case '{':
if (!m_Output.IsEmpty())
return Status::kSelector;
m_Input.MoveNext();
SaveMode(Mode::kRuleSet); // Back to validate ruleset again.
m_eMode = Mode::kPropertyName;
return Status::kDeclOpen;
case '/':
if (m_Input.GetNextChar() == '*') {
SaveMode(Mode::kSelector);
m_eMode = Mode::kComment;
if (!m_Output.IsEmpty())
return Status::kSelector;
break;
}
[[fallthrough]];
default:
m_Output.AppendCharIfNotLeadingBlank(wch);
m_Input.MoveNext();
break;
}
break;
case Mode::kPropertyName:
switch (wch) {
case ':':
m_Input.MoveNext();
m_eMode = Mode::kPropertyValue;
return Status::kPropertyName;
case '}':
m_Input.MoveNext();
if (!RestoreMode())
return Status::kError;
return Status::kDeclClose;
case '/':
if (m_Input.GetNextChar() == '*') {
SaveMode(Mode::kPropertyName);
m_eMode = Mode::kComment;
if (!m_Output.IsEmpty())
return Status::kPropertyName;
break;
}
[[fallthrough]];
default:
m_Output.AppendCharIfNotLeadingBlank(wch);
m_Input.MoveNext();
break;
}
break;
case Mode::kPropertyValue:
switch (wch) {
case ';':
m_Input.MoveNext();
[[fallthrough]];
case '}':
m_eMode = Mode::kPropertyName;
return Status::kPropertyValue;
case '/':
if (m_Input.GetNextChar() == '*') {
SaveMode(Mode::kPropertyValue);
m_eMode = Mode::kComment;
if (!m_Output.IsEmpty())
return Status::kPropertyValue;
break;
}
[[fallthrough]];
default:
m_Output.AppendCharIfNotLeadingBlank(wch);
m_Input.MoveNext();
break;
}
break;
case Mode::kComment:
if (wch == '*' && m_Input.GetNextChar() == '/') {
if (!RestoreMode())
return Status::kError;
m_Input.MoveNext();
}
m_Input.MoveNext();
break;
}
}
if (m_eMode == Mode::kPropertyValue && !m_Output.IsEmpty())
return Status::kPropertyValue;
return Status::kEOS;
}
void CFX_CSSSyntaxParser::SaveMode(Mode mode) {
m_ModeStack.push(mode);
}
bool CFX_CSSSyntaxParser::RestoreMode() {
if (m_ModeStack.empty()) {
m_bHasError = true;
return false;
}
m_eMode = m_ModeStack.top();
m_ModeStack.pop();
return true;
}
WideStringView CFX_CSSSyntaxParser::GetCurrentString() const {
return m_Output.GetTrailingBlankTrimmedString();
}