chromium/third_party/blink/renderer/core/css/parser/css_proto_converter.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "third_party/blink/renderer/core/css/parser/css_proto_converter.h"
#include <string>

// TODO(metzman): Figure out how to remove this include and use DCHECK.
#include "base/notreached.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/core/css/parser/css.pb.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

// TODO(bikineev): "IN" comes as a macro from <windows.h>. It conflicts with
// Length::IN from the generated proto file. Change the name in css.proto rather
// than hacking with directives here.
#if BUILDFLAG(IS_WIN) && defined(IN)
#undef IN
#endif

namespace css_proto_converter {

const int Converter::kAtRuleDepthLimit = 5;
const int Converter::kSupportsConditionDepthLimit = 5;

const std::string Converter::kViewportPropertyLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "min-width",  "max-width", "width",       "min-height",
    "max-height", "height",    "zoom",        "min-zoom",
    "user-zoom",  "max-zoom",  "orientation",
};

const std::string Converter::kViewportValueLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "landscape", "portrait", "auto", "zoom", "fixed", "none",
};

const std::string Converter::kPseudoLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "-internal-autofill-previewed",
    "-internal-autofill-selected",
    "-internal-dialog-in-top-layer",
    "-internal-is-html",
    "-internal-list-box",
    "-internal-media-controls-overlay-cast-button",
    "-internal-multi-select-focus",
    "-internal-popover-in-top-layer",
    "-internal-shadow-host-has-non-auto-appearance",
    "-internal-spatial-navigation-focus",
    "-internal-video-persistent",
    "-internal-video-persistent-ancestor",
    "-webkit-any-link",
    "-webkit-autofill",
    "-webkit-drag",
    "-webkit-full-page-media",
    "-webkit-full-screen",
    "-webkit-full-screen-ancestor",
    "-webkit-resizer",
    "-webkit-scrollbar",
    "-webkit-scrollbar-button",
    "-webkit-scrollbar-corner",
    "-webkit-scrollbar-thumb",
    "-webkit-scrollbar-track",
    "-webkit-scrollbar-track-piece",
    "active",
    "active-view-transition",
    "active-view-transition-type",
    "after",
    "autofill",
    "backdrop",
    "before",
    "checked",
    "closed",
    "corner-present",
    "cue",
    "decrement",
    "default",
    "defined",
    "disabled",
    "double-button",
    "empty",
    "enabled",
    "end",
    "first",
    "first-child",
    "first-letter",
    "first-line",
    "first-of-type",
    "focus",
    "focus-within",
    "fullscreen",
    "future",
    "horizontal",
    "host",
    "hover",
    "in-range",
    "increment",
    "indeterminate",
    "invalid",
    "last-child",
    "last-of-type",
    "left",
    "link",
    "no-button",
    "only-child",
    "only-of-type",
    "open",
    "optional",
    "out-of-range",
    "past",
    "placeholder",
    "placeholder-shown",
    "popover-open",
    "read-only",
    "read-write",
    "required",
    "right",
    "root",
    "scope",
    "selection",
    "single-button",
    "start",
    "state",
    "target",
    "user-invalid",
    "user-valid",
    "valid",
    "vertical",
    "visited",
    "window-inactive",
    "-webkit-any",
    "host-context",
    "lang",
    "not",
    "nth-child",
    "nth-last-child",
    "nth-last-of-type",
    "nth-of-type",
    "slotted",
    "xr-overlay",
    "INVALID_PSEUDO_VALUE"};

const std::string Converter::kMediaTypeLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "all",
    "braille",
    "embossed",
    "handheld",
    "print",
    "projection",
    "screen",
    "speech",
    "tty",
    "tv",
    "INVALID_MEDIA_TYPE"};

const std::string Converter::kMfNameLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "any-hover",
    "any-pointer",
    "color",
    "color-index",
    "color-gamut",
    "grid",
    "monochrome",
    "height",
    "hover",
    "width",
    "orientation",
    "aspect-ratio",
    "device-aspect-ratio",
    "-webkit-device-pixel-ratio",
    "device-height",
    "device-width",
    "display-mode",
    "max-color",
    "max-color-index",
    "max-aspect-ratio",
    "max-device-aspect-ratio",
    "-webkit-max-device-pixel-ratio",
    "max-device-height",
    "max-device-width",
    "max-height",
    "max-monochrome",
    "max-width",
    "max-resolution",
    "min-color",
    "min-color-index",
    "min-aspect-ratio",
    "min-device-aspect-ratio",
    "-webkit-min-device-pixel-ratio",
    "min-device-height",
    "min-device-width",
    "min-height",
    "min-monochrome",
    "min-width",
    "min-resolution",
    "pointer",
    "resolution",
    "-webkit-transform-3d",
    "scan",
    "shape",
    "immersive",
    "dynamic-range",
    "video-dynamic-range",
    "INVALID_NAME"};

const std::string Converter::kImportLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "'custom.css'", "url(\"chrome://communicator/skin/\")"};

const std::string Converter::kEncodingLookupTable[] = {
    "",  // This is just to fill the zeroth spot. It should not be used.
    "UTF-8",
    "UTF-16",
    "UTF-32",
};

#include "third_party/blink/renderer/core/css/parser/css_proto_converter_generated.h"

Converter::Converter() = default;

std::string Converter::Convert(const StyleSheet& style_sheet_message) {
  Reset();
  Visit(style_sheet_message);
  return string_;
}

void Converter::Visit(const Unicode& unicode) {
  string_ += "\\";
  string_ += static_cast<char>(unicode.ascii_value_1());

  if (unicode.has_ascii_value_2()) {
    string_ += static_cast<char>(unicode.ascii_value_2());
  }
  if (unicode.has_ascii_value_3()) {
    string_ += static_cast<char>(unicode.ascii_value_3());
  }
  if (unicode.has_ascii_value_4()) {
    string_ += static_cast<char>(unicode.ascii_value_4());
  }
  if (unicode.has_ascii_value_5()) {
    string_ += static_cast<char>(unicode.ascii_value_5());
  }
  if (unicode.has_ascii_value_6()) {
    string_ += static_cast<char>(unicode.ascii_value_6());
  }

  if (unicode.has_unrepeated_w()) {
    Visit(unicode.unrepeated_w());
  }
}

void Converter::Visit(const Escape& escape) {
  if (escape.has_ascii_value()) {
    string_ += "\\";
    string_ += static_cast<char>(escape.ascii_value());
  } else if (escape.has_unicode()) {
    Visit(escape.unicode());
  }
}

void Converter::Visit(const Nmstart& nmstart) {
  if (nmstart.has_ascii_value()) {
    string_ += static_cast<char>(nmstart.ascii_value());
  } else if (nmstart.has_escape()) {
    Visit(nmstart.escape());
  }
}

void Converter::Visit(const Nmchar& nmchar) {
  if (nmchar.has_ascii_value()) {
    string_ += static_cast<char>(nmchar.ascii_value());
  } else if (nmchar.has_escape()) {
    Visit(nmchar.escape());
  }
}

void Converter::Visit(const String& string) {
  bool use_single_quotes = string.use_single_quotes();
  if (use_single_quotes) {
    string_ += "'";
  } else {
    string_ += "\"";
  }

  for (auto& string_char_quote : string.string_char_quotes()) {
    Visit(string_char_quote, use_single_quotes);
  }

  if (use_single_quotes) {
    string_ += "'";
  } else {
    string_ += "\"";
  }
}

void Converter::Visit(const StringCharOrQuote& string_char_quote,
                      bool using_single_quote) {
  if (string_char_quote.has_string_char()) {
    Visit(string_char_quote.string_char());
  } else if (string_char_quote.quote_char()) {
    if (using_single_quote) {
      string_ += "\"";
    } else {
      string_ += "'";
    }
  }
}

void Converter::Visit(const StringChar& string_char) {
  if (string_char.has_url_char()) {
    Visit(string_char.url_char());
  } else if (string_char.has_space()) {
    string_ += " ";
  } else if (string_char.has_nl()) {
    Visit(string_char.nl());
  }
}

void Converter::Visit(const Ident& ident) {
  if (ident.starting_minus()) {
    string_ += "-";
  }
  Visit(ident.nmstart());
  for (auto& nmchar : ident.nmchars()) {
    Visit(nmchar);
  }
}

void Converter::Visit(const Num& num) {
  if (num.has_float_value()) {
    string_ += std::to_string(num.float_value());
  } else {
    string_ += std::to_string(num.signed_int_value());
  }
}

void Converter::Visit(const UrlChar& url_char) {
  string_ += static_cast<char>(url_char.ascii_value());
}

// TODO(metzman): implement W
void Converter::Visit(const UnrepeatedW& unrepeated_w) {
  string_ += static_cast<char>(unrepeated_w.ascii_value());
}

void Converter::Visit(const Nl& nl) {
  string_ += "\\";
  if (nl.newline_kind() == Nl::CR_LF) {
    string_ += "\r\n";
  } else {  // Otherwise newline_kind is the ascii value of the char we want.
    string_ += static_cast<char>(nl.newline_kind());
  }
}

void Converter::Visit(const Length& length) {
  Visit(length.num());
  if (length.unit() == Length::PX) {
    string_ += "px";
  } else if (length.unit() == Length::CM) {
    string_ += "cm";
  } else if (length.unit() == Length::MM) {
    string_ += "mm";
  } else if (length.unit() == Length::IN) {
    string_ += "in";
  } else if (length.unit() == Length::PT) {
    string_ += "pt";
  } else if (length.unit() == Length::PC) {
    string_ += "pc";
  } else {
    NOTREACHED_IN_MIGRATION();
  }
}

void Converter::Visit(const Angle& angle) {
  Visit(angle.num());
  if (angle.unit() == Angle::DEG) {
    string_ += "deg";
  } else if (angle.unit() == Angle::RAD) {
    string_ += "rad";
  } else if (angle.unit() == Angle::GRAD) {
    string_ += "grad";
  } else {
    NOTREACHED_IN_MIGRATION();
  }
}

void Converter::Visit(const Time& time) {
  Visit(time.num());
  if (time.unit() == Time::MS) {
    string_ += "ms";
  } else if (time.unit() == Time::S) {
    string_ += "s";
  } else {
    NOTREACHED_IN_MIGRATION();
  }
}

void Converter::Visit(const Freq& freq) {
  Visit(freq.num());
  // Hack around really dumb build bug
  if (freq.unit() == Freq::_HZ) {
    string_ += "Hz";
  } else if (freq.unit() == Freq::KHZ) {
    string_ += "kHz";
  } else {
    NOTREACHED_IN_MIGRATION();
  }
}

void Converter::Visit(const Uri& uri) {
  string_ += "url(\"chrome://communicator/skin/\");";
}

void Converter::Visit(const FunctionToken& function_token) {
  Visit(function_token.ident());
  string_ += "(";
}

void Converter::Visit(const StyleSheet& style_sheet) {
  if (style_sheet.has_charset_declaration()) {
    Visit(style_sheet.charset_declaration());
  }
  for (auto& import : style_sheet.imports()) {
    Visit(import);
  }
  for (auto& _namespace : style_sheet.namespaces()) {
    Visit(_namespace);
  }
  for (auto& nested_at_rule : style_sheet.nested_at_rules()) {
    Visit(nested_at_rule);
  }
}

void Converter::Visit(const ViewportValue& viewport_value) {
  if (viewport_value.has_length()) {
    Visit(viewport_value.length());
  } else if (viewport_value.has_num()) {
    Visit(viewport_value.num());
  } else {  // Default value.
    AppendTableValue<ViewportValue_ValueId_ValueId_ARRAYSIZE>(
        viewport_value.value_id(), kViewportValueLookupTable);
  }
}

void Converter::Visit(const Viewport& viewport) {
  string_ += " @viewport {";
  for (auto& property_and_value : viewport.properties_and_values()) {
    AppendPropertyAndValue<ViewportProperty_PropertyId_PropertyId_ARRAYSIZE>(
        property_and_value, kViewportPropertyLookupTable);
  }
  string_ += " } ";
}

void Converter::Visit(const CharsetDeclaration& charset_declaration) {
  string_ += "@charset ";  // CHARSET_SYM
  string_ += "\"";
  AppendTableValue<CharsetDeclaration_EncodingId_EncodingId_ARRAYSIZE>(
      charset_declaration.encoding_id(), kEncodingLookupTable);
  string_ += "\"; ";
}

void Converter::Visit(const AtRuleOrRulesets& at_rule_or_rulesets, int depth) {
  Visit(at_rule_or_rulesets.first(), depth);
  for (auto& later : at_rule_or_rulesets.laters()) {
    Visit(later, depth);
  }
}

void Converter::Visit(const AtRuleOrRuleset& at_rule_or_ruleset, int depth) {
  if (at_rule_or_ruleset.has_at_rule()) {
    Visit(at_rule_or_ruleset.at_rule(), depth);
  } else {  // Default.
    Visit(at_rule_or_ruleset.ruleset());
  }
}

void Converter::Visit(const NestedAtRule& nested_at_rule, int depth) {
  if (++depth > kAtRuleDepthLimit) {
    return;
  }

  if (nested_at_rule.has_ruleset()) {
    Visit(nested_at_rule.ruleset());
  } else if (nested_at_rule.has_media()) {
    Visit(nested_at_rule.media());
  } else if (nested_at_rule.has_viewport()) {
    Visit(nested_at_rule.viewport());
  } else if (nested_at_rule.has_supports_rule()) {
    Visit(nested_at_rule.supports_rule(), depth);
  }
  // Else apppend nothing.
  // TODO(metzman): Support pages and font-faces.
}

void Converter::Visit(const SupportsRule& supports_rule, int depth) {
  string_ += "@supports ";
  Visit(supports_rule.supports_condition(), depth);
  string_ += " { ";
  for (auto& at_rule_or_ruleset : supports_rule.at_rule_or_rulesets()) {
    Visit(at_rule_or_ruleset, depth);
  }
  string_ += " } ";
}

void Converter::AppendBinarySupportsCondition(
    const BinarySupportsCondition& binary_condition,
    std::string binary_operator,
    int depth) {
  Visit(binary_condition.condition_1(), depth);
  string_ += " " + binary_operator + " ";
  Visit(binary_condition.condition_2(), depth);
}

void Converter::Visit(const SupportsCondition& supports_condition, int depth) {
  bool under_depth_limit = ++depth <= kSupportsConditionDepthLimit;

  if (supports_condition.not_condition()) {
    string_ += " not ";
  }

  string_ += "(";

  if (under_depth_limit && supports_condition.has_and_supports_condition()) {
    AppendBinarySupportsCondition(supports_condition.or_supports_condition(),
                                  "and", depth);
  } else if (under_depth_limit &&
             supports_condition.has_or_supports_condition()) {
    AppendBinarySupportsCondition(supports_condition.or_supports_condition(),
                                  "or", depth);
  } else {
    // Use the required property_and_value field if the or_supports_condition
    // and and_supports_condition are unset or if we have reached the depth
    // limit and don't want another nested condition.
    Visit(supports_condition.property_and_value());
  }

  string_ += ")";
}

void Converter::Visit(const Import& import) {
  string_ += "@import ";
  AppendTableValue<Import_SrcId_SrcId_ARRAYSIZE>(import.src_id(),
                                                 kImportLookupTable);
  string_ += " ";
  if (import.has_media_query_list()) {
    Visit(import.media_query_list());
  }
  string_ += "; ";
}

void Converter::Visit(const MediaQueryList& media_query_list) {
  bool first = true;
  for (auto& media_query : media_query_list.media_queries()) {
    if (first) {
      first = false;
    } else {
      string_ += ", ";
    }
    Visit(media_query);
  }
}

void Converter::Visit(const MediaQuery& media_query) {
  if (media_query.has_media_query_part_two()) {
    Visit(media_query.media_query_part_two());
  } else {
    Visit(media_query.media_condition());
  }
}

void Converter::Visit(const MediaQueryPartTwo& media_query_part_two) {
  if (media_query_part_two.has_not_or_only()) {
    if (media_query_part_two.not_or_only() == MediaQueryPartTwo::NOT) {
      string_ += " not ";
    } else {
      string_ += " only ";
    }
  }
  Visit(media_query_part_two.media_type());
  if (media_query_part_two.has_media_condition_without_or()) {
    string_ += " and ";
    Visit(media_query_part_two.media_condition_without_or());
  }
}

void Converter::Visit(const MediaCondition& media_condition) {
  if (media_condition.has_media_not()) {
    Visit(media_condition.media_not());
  } else if (media_condition.has_media_or()) {
    Visit(media_condition.media_or());
  } else if (media_condition.has_media_in_parens()) {
    Visit(media_condition.media_in_parens());
  } else {
    Visit(media_condition.media_and());
  }
}

void Converter::Visit(const MediaConditionWithoutOr& media_condition) {
  if (media_condition.has_media_and()) {
    Visit(media_condition.media_and());
  } else if (media_condition.has_media_in_parens()) {
    Visit(media_condition.media_in_parens());
  } else {
    Visit(media_condition.media_not());
  }
}

void Converter::Visit(const MediaType& media_type) {
  AppendTableValue<MediaType_ValueId_ValueId_ARRAYSIZE>(media_type.value_id(),
                                                        kMediaTypeLookupTable);
}

void Converter::Visit(const MediaNot& media_not) {
  string_ += " not ";
  Visit(media_not.media_in_parens());
}

void Converter::Visit(const MediaAnd& media_and) {
  Visit(media_and.first_media_in_parens());
  string_ += " and ";
  Visit(media_and.second_media_in_parens());
  for (auto& media_in_parens : media_and.media_in_parens_list()) {
    string_ += " and ";
    Visit(media_in_parens);
  }
}

void Converter::Visit(const MediaOr& media_or) {
  Visit(media_or.first_media_in_parens());
  string_ += " or ";
  Visit(media_or.second_media_in_parens());
  for (auto& media_in_parens : media_or.media_in_parens_list()) {
    string_ += " or ";
    Visit(media_in_parens);
  }
}

void Converter::Visit(const MediaInParens& media_in_parens) {
  if (media_in_parens.has_media_condition()) {
    string_ += " (";
    Visit(media_in_parens.media_condition());
    string_ += " )";
  } else if (media_in_parens.has_media_feature()) {
    Visit(media_in_parens.media_feature());
  }
}

void Converter::Visit(const MediaFeature& media_feature) {
  string_ += "(";
  if (media_feature.has_mf_bool()) {
    Visit(media_feature.mf_bool());
  } else if (media_feature.has_mf_plain()) {
    AppendPropertyAndValue<MfName_ValueId_ValueId_ARRAYSIZE>(
        media_feature.mf_plain(), kMfNameLookupTable, false);
  }
  string_ += ")";
}

void Converter::Visit(const MfBool& mf_bool) {
  Visit(mf_bool.mf_name());
}

void Converter::Visit(const MfName& mf_name) {
  AppendTableValue<MfName_ValueId_ValueId_ARRAYSIZE>(mf_name.id(),
                                                     kMfNameLookupTable);
}

void Converter::Visit(const MfValue& mf_value) {
  if (mf_value.has_length()) {
    Visit(mf_value.length());
  } else if (mf_value.has_ident()) {
    Visit(mf_value.ident());
  } else {
    Visit(mf_value.num());
  }
}

void Converter::Visit(const Namespace& _namespace) {
  string_ += "@namespace ";
  if (_namespace.has_namespace_prefix()) {
    Visit(_namespace.namespace_prefix());
  }
  if (_namespace.has_string()) {
    Visit(_namespace.string());
  }
  if (_namespace.has_uri()) {
    Visit(_namespace.uri());
  }

  string_ += "; ";
}

void Converter::Visit(const NamespacePrefix& namespace_prefix) {
  Visit(namespace_prefix.ident());
}

void Converter::Visit(const Media& media) {
  // MEDIA_SYM S*
  string_ += "@media ";  // "@media" {return MEDIA_SYM;}

  Visit(media.media_query_list());
  string_ += " { ";
  for (auto& ruleset : media.rulesets()) {
    Visit(ruleset);
  }
  string_ += " } ";
}

void Converter::Visit(const Page& page) {
  // PAGE_SYM
  string_ += "@page ";  // PAGE_SYM
  if (page.has_ident()) {
    Visit(page.ident());
  }
  if (page.has_pseudo_page()) {
    Visit(page.pseudo_page());
  }
  string_ += " { ";
  Visit(page.declaration_list());
  string_ += " } ";
}

void Converter::Visit(const PseudoPage& pseudo_page) {
  string_ += ":";
  Visit(pseudo_page.ident());
}

void Converter::Visit(const DeclarationList& declaration_list) {
  Visit(declaration_list.first_declaration());
  for (auto& declaration : declaration_list.later_declarations()) {
    Visit(declaration);
    string_ += "; ";
  }
}

void Converter::Visit(const FontFace& font_face) {
  string_ += "@font-face";
  string_ += "{";
  // Visit(font_face.declaration_list());
  string_ += "}";
}

void Converter::Visit(const Operator& _operator) {
  if (_operator.has_ascii_value()) {
    string_ += static_cast<char>(_operator.ascii_value());
  }
}

void Converter::Visit(const UnaryOperator& unary_operator) {
  string_ += static_cast<char>(unary_operator.ascii_value());
}

void Converter::Visit(const Property& property) {
  AppendTableValue<Property_NameId_NameId_ARRAYSIZE>(property.name_id(),
                                                     kPropertyLookupTable);
}

void Converter::Visit(const Ruleset& ruleset) {
  Visit(ruleset.selector_list());
  string_ += " {";
  Visit(ruleset.declaration_list());
  string_ += "} ";
}

void Converter::Visit(const SelectorList& selector_list) {
  Visit(selector_list.first_selector(), true);
  for (auto& selector : selector_list.later_selectors()) {
    Visit(selector, false);
  }
  string_ += " ";
}

// Also visits Attr
void Converter::Visit(const Selector& selector, bool is_first) {
  if (!is_first) {
    string_ += " ";
    if (selector.combinator() != Combinator::NONE) {
      string_ += static_cast<char>(selector.combinator());
      string_ += " ";
    }
  }
  if (selector.type() == Selector::ELEMENT) {
    string_ += "a";
  } else if (selector.type() == Selector::CLASS) {
    string_ += ".classname";
  } else if (selector.type() == Selector::ID) {
    string_ += "#idname";
  } else if (selector.type() == Selector::UNIVERSAL) {
    string_ += "*";
  } else if (selector.type() == Selector::ATTR) {
    std::string val1 = "href";
    std::string val2 = ".org";
    string_ += "a[" + val1;
    if (selector.attr().type() != Attr::NONE) {
      string_ += " ";
      string_ += static_cast<char>(selector.attr().type());
      string_ += +"= " + val2;
    }
    if (selector.attr().attr_i()) {
      string_ += " i";
    }
    string_ += "]";
  }
  if (selector.has_pseudo_value_id()) {
    string_ += ":";
    if (selector.pseudo_type() == PseudoType::ELEMENT) {
      string_ += ":";
    }
    AppendTableValue<Selector_PseudoValueId_PseudoValueId_ARRAYSIZE>(
        selector.pseudo_value_id(), kPseudoLookupTable);
  }
}

void Converter::Visit(const Declaration& declaration) {
  if (declaration.has_property_and_value()) {
    Visit(declaration.property_and_value());
  }
  // else empty
}

void Converter::Visit(const PropertyAndValue& property_and_value) {
  Visit(property_and_value.property());
  string_ += " : ";
  int value_id = 0;
  if (property_and_value.has_value_id()) {
    value_id = property_and_value.value_id();
  }
  Visit(property_and_value.expr(), value_id);
  if (property_and_value.has_prio()) {
    string_ += " !important ";
  }
}

void Converter::Visit(const Expr& expr, int declaration_value_id) {
  if (!declaration_value_id) {
    Visit(expr.term());
  } else {
    AppendTableValue<PropertyAndValue_ValueId_ValueId_ARRAYSIZE>(
        declaration_value_id, kValueLookupTable);
  }
  for (auto& operator_term : expr.operator_terms()) {
    Visit(operator_term);
  }
}

void Converter::Visit(const OperatorTerm& operator_term) {
  Visit(operator_term._operator());
  Visit(operator_term.term());
}

void Converter::Visit(const Term& term) {
  if (term.has_unary_operator()) {
    Visit(term.unary_operator());
  }

  if (term.has_term_part()) {
    Visit(term.term_part());
  } else if (term.has_string()) {
    Visit(term.string());
  }

  if (term.has_ident()) {
    Visit(term.ident());
  }
  if (term.has_uri()) {
    Visit(term.uri());
  }
  if (term.has_hexcolor()) {
    Visit(term.hexcolor());
  }
}

void Converter::Visit(const TermPart& term_part) {
  if (term_part.has_number()) {
    Visit(term_part.number());
  }
  // S* | PERCENTAGE
  if (term_part.has_percentage()) {
    Visit(term_part.percentage());
    string_ += "%";
  }
  // S* | LENGTH
  if (term_part.has_length()) {
    Visit(term_part.length());
  }
  // S* | EMS
  if (term_part.has_ems()) {
    Visit(term_part.ems());
    string_ += "em";
  }
  // S* | EXS
  if (term_part.has_exs()) {
    Visit(term_part.exs());
    string_ += "ex";
  }
  // S* | Angle
  if (term_part.has_angle()) {
    Visit(term_part.angle());
  }
  // S* | TIME
  if (term_part.has_time()) {
    Visit(term_part.time());
  }
  // S* | FREQ
  if (term_part.has_freq()) {
    Visit(term_part.freq());
  }
  // S* | function
  if (term_part.has_function()) {
    Visit(term_part.function());
  }
}

void Converter::Visit(const Function& function) {
  Visit(function.function_token());
  Visit(function.expr());
  string_ += ")";
}

void Converter::Visit(const Hexcolor& hexcolor) {
  string_ += "#";
  Visit(hexcolor.first_three());
  if (hexcolor.has_last_three()) {
    Visit(hexcolor.last_three());
  }
}

void Converter::Visit(const HexcolorThree& hexcolor_three) {
  string_ += static_cast<char>(hexcolor_three.ascii_value_1());
  string_ += static_cast<char>(hexcolor_three.ascii_value_2());
  string_ += static_cast<char>(hexcolor_three.ascii_value_3());
}

void Converter::Reset() {
  string_.clear();
}

template <size_t EnumSize, size_t TableSize>
void Converter::AppendTableValue(int id,
                                 const std::string (&lookup_table)[TableSize]) {
  // If you hit this assert, you likely need to modify
  // css/parser/templates/css.proto.tmpl.
  static_assert(EnumSize == TableSize,
                "Enum used as index should not overflow lookup table");
  CHECK(id > 0 && static_cast<size_t>(id) < TableSize);
  string_ += lookup_table[id];
}

template <size_t EnumSize, class T, size_t TableSize>
void Converter::AppendPropertyAndValue(
    T property_and_value,
    const std::string (&lookup_table)[TableSize],
    bool append_semicolon) {
  AppendTableValue<EnumSize>(property_and_value.property().id(), lookup_table);
  string_ += " : ";
  Visit(property_and_value.value());
  if (append_semicolon) {
    string_ += "; ";
  }
}
}  // namespace css_proto_converter