| // Copyright 2014 PDFium Authors. All rights reserved. | |
| // 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 <algorithm> | |
| #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" | |
| #include "third_party/base/compiler_specific.h" | |
| #include "third_party/base/logging.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 = SyntaxMode::kPropertyName; | |
| } | |
| CFX_CSSSyntaxStatus CFX_CSSSyntaxParser::DoSyntaxParse() { | |
| m_Output.Clear(); | |
| if (m_bHasError) | |
| return CFX_CSSSyntaxStatus::kError; | |
| while (!m_Input.IsEOF()) { | |
| wchar_t wch = m_Input.GetChar(); | |
| switch (m_eMode) { | |
| case SyntaxMode::kRuleSet: | |
| switch (wch) { | |
| case '}': | |
| m_bHasError = true; | |
| return CFX_CSSSyntaxStatus::kError; | |
| case '/': | |
| if (m_Input.GetNextChar() == '*') { | |
| SaveMode(SyntaxMode::kRuleSet); | |
| m_eMode = SyntaxMode::kComment; | |
| break; | |
| } | |
| FALLTHROUGH; | |
| default: | |
| if (wch <= ' ') { | |
| m_Input.MoveNext(); | |
| } else if (IsSelectorStart(wch)) { | |
| m_eMode = SyntaxMode::kSelector; | |
| return CFX_CSSSyntaxStatus::kStyleRule; | |
| } else { | |
| m_bHasError = true; | |
| return CFX_CSSSyntaxStatus::kError; | |
| } | |
| break; | |
| } | |
| break; | |
| case SyntaxMode::kSelector: | |
| switch (wch) { | |
| case ',': | |
| m_Input.MoveNext(); | |
| if (!m_Output.IsEmpty()) | |
| return CFX_CSSSyntaxStatus::kSelector; | |
| break; | |
| case '{': | |
| if (!m_Output.IsEmpty()) | |
| return CFX_CSSSyntaxStatus::kSelector; | |
| m_Input.MoveNext(); | |
| SaveMode(SyntaxMode::kRuleSet); // Back to validate ruleset again. | |
| m_eMode = SyntaxMode::kPropertyName; | |
| return CFX_CSSSyntaxStatus::kDeclOpen; | |
| case '/': | |
| if (m_Input.GetNextChar() == '*') { | |
| SaveMode(SyntaxMode::kSelector); | |
| m_eMode = SyntaxMode::kComment; | |
| if (!m_Output.IsEmpty()) | |
| return CFX_CSSSyntaxStatus::kSelector; | |
| break; | |
| } | |
| FALLTHROUGH; | |
| default: | |
| m_Output.AppendCharIfNotLeadingBlank(wch); | |
| m_Input.MoveNext(); | |
| break; | |
| } | |
| break; | |
| case SyntaxMode::kPropertyName: | |
| switch (wch) { | |
| case ':': | |
| m_Input.MoveNext(); | |
| m_eMode = SyntaxMode::kPropertyValue; | |
| return CFX_CSSSyntaxStatus::kPropertyName; | |
| case '}': | |
| m_Input.MoveNext(); | |
| if (!RestoreMode()) | |
| return CFX_CSSSyntaxStatus::kError; | |
| return CFX_CSSSyntaxStatus::kDeclClose; | |
| case '/': | |
| if (m_Input.GetNextChar() == '*') { | |
| SaveMode(SyntaxMode::kPropertyName); | |
| m_eMode = SyntaxMode::kComment; | |
| if (!m_Output.IsEmpty()) | |
| return CFX_CSSSyntaxStatus::kPropertyName; | |
| break; | |
| } | |
| FALLTHROUGH; | |
| default: | |
| m_Output.AppendCharIfNotLeadingBlank(wch); | |
| m_Input.MoveNext(); | |
| break; | |
| } | |
| break; | |
| case SyntaxMode::kPropertyValue: | |
| switch (wch) { | |
| case ';': | |
| m_Input.MoveNext(); | |
| FALLTHROUGH; | |
| case '}': | |
| m_eMode = SyntaxMode::kPropertyName; | |
| return CFX_CSSSyntaxStatus::kPropertyValue; | |
| case '/': | |
| if (m_Input.GetNextChar() == '*') { | |
| SaveMode(SyntaxMode::kPropertyValue); | |
| m_eMode = SyntaxMode::kComment; | |
| if (!m_Output.IsEmpty()) | |
| return CFX_CSSSyntaxStatus::kPropertyValue; | |
| break; | |
| } | |
| FALLTHROUGH; | |
| default: | |
| m_Output.AppendCharIfNotLeadingBlank(wch); | |
| m_Input.MoveNext(); | |
| break; | |
| } | |
| break; | |
| case SyntaxMode::kComment: | |
| if (wch == '*' && m_Input.GetNextChar() == '/') { | |
| if (!RestoreMode()) | |
| return CFX_CSSSyntaxStatus::kError; | |
| m_Input.MoveNext(); | |
| } | |
| m_Input.MoveNext(); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| break; | |
| } | |
| } | |
| if (m_eMode == SyntaxMode::kPropertyValue && !m_Output.IsEmpty()) | |
| return CFX_CSSSyntaxStatus::kPropertyValue; | |
| return CFX_CSSSyntaxStatus::kEOS; | |
| } | |
| void CFX_CSSSyntaxParser::SaveMode(SyntaxMode 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(); | |
| } |