LCOV - code coverage report
Current view: top level - lib - assembler.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 444 452 98.2 %
Date: 2017-03-23 22:48:20 Functions: 38 39 97.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // CSS Preprocessor
       2             : // Copyright (C) 2015-2017  Made to Order Software Corp.
       3             : //
       4             : // This program is free software; you can redistribute it and/or modify
       5             : // it under the terms of the GNU General Public License as published by
       6             : // the Free Software Foundation; either version 2 of the License, or
       7             : // (at your option) any later version.
       8             : //
       9             : // This program is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             : // GNU General Public License for more details.
      13             : //
      14             : // You should have received a copy of the GNU General Public License
      15             : // along with this program; if not, write to the Free Software
      16             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      17             : 
      18             : /** \file
      19             :  * \brief Implementation of the CSS Preprocessor assembler.
      20             :  *
      21             :  * The CSS Preprocessor assembler generates the output files from whatever
      22             :  * the compiler generated.
      23             :  *
      24             :  * The assembler supports modes that allows one to define how the data is
      25             :  * output. The mode uses an internally defined class to handle the
      26             :  * formatting.
      27             :  *
      28             :  * \sa \ref lexer_rules
      29             :  */
      30             : 
      31             : #include "csspp/assembler.h"
      32             : 
      33             : #include "csspp/exceptions.h"
      34             : #include "csspp/lexer.h"
      35             : #include "csspp/nth_child.h"
      36             : #include "csspp/unicode_range.h"
      37             : 
      38             : #include <iostream>
      39             : 
      40             : namespace csspp
      41             : {
      42             : 
      43             : namespace
      44             : {
      45             : 
      46             : typedef uint32_t        flags_t;
      47             : 
      48             : flags_t const g_flag_optional_operator                  = 0x01;
      49             : flags_t const g_flag_optional_spaces                    = 0x02;
      50             : flags_t const g_flag_optional_space_before              = 0x04;
      51             : flags_t const g_flag_optional_space_after               = 0x08;
      52             : flags_t const g_flag_optional_spaces_or_newlines        = 0x10;
      53             : flags_t const g_flag_optional_space_before_or_newline   = 0x20;
      54             : flags_t const g_flag_optional_space_after_or_newline    = 0x40;
      55             : 
      56       34966 : void verify_dimension(node::pointer_t n)
      57             : {
      58       69932 :     std::string const dimension(n->get_string());
      59       34966 :     std::string::size_type pos(dimension.find_first_of(" */"));
      60       34966 :     if(pos != std::string::npos)
      61             :     {
      62           6 :         error::instance() << n->get_position()
      63           6 :                 << "\""
      64           6 :                 << dimension
      65           6 :                 << "\" is not a valid CSS dimension."
      66           6 :                 << error_mode_t::ERROR_ERROR;
      67             :     }
      68       34966 : }
      69             : 
      70             : } // no name namespace
      71             : 
      72             : // base class
      73             : class assembler_impl
      74             : {
      75             : public:
      76        7640 :     assembler_impl(std::ostream & out)
      77        7640 :         : f_out(out)
      78             :     {
      79        7640 :     }
      80             : 
      81        7640 :     virtual ~assembler_impl()
      82        7640 :     {
      83        7640 :     }
      84             : 
      85      127060 :     virtual void output_string(std::string const & str)
      86             :     {
      87      127060 :         if(!str.empty())
      88             :         {
      89      127060 :             f_started = str.back() != '\n';
      90             :         }
      91      127060 :         f_out << str;
      92      127060 :     }
      93             : 
      94      168749 :     virtual void output_operator(std::string const & str, flags_t flags)
      95             :     {
      96             :         static_cast<void>(flags);
      97             : 
      98             :         // the default prints that as is
      99      168749 :         if((flags & g_flag_optional_operator) == 0)
     100             :         {
     101      126692 :             output_string(str);
     102             :         }
     103      168749 :     }
     104             : 
     105         368 :     virtual void output_token(std::string const & str)
     106             :     {
     107             :         // the default prints that as is
     108         368 :         output_string(str);
     109         368 :     }
     110             : 
     111       41888 :     virtual void newline()
     112             :     {
     113             :         // by default do not write newlines
     114       41888 :     }
     115             : 
     116        7540 :     virtual void newline_if_not_empty()
     117             :     {
     118        7540 :         if(f_started)
     119             :         {
     120        7299 :             f_started = false;
     121        7299 :             f_out << std::endl;
     122             :         }
     123        7540 :     }
     124             : 
     125       42206 :     virtual void output_identation()
     126             :     {
     127             :         // by default do not write identation
     128       42206 :     }
     129             : 
     130             : protected:
     131             :     std::ostream &          f_out;
     132             :     bool                    f_started = false;
     133             : };
     134             : 
     135             : class assembler_compressed : public assembler_impl
     136             : {
     137             : public:
     138        7640 :     assembler_compressed(std::ostream & out)
     139        7640 :         : assembler_impl(out)
     140             :     {
     141        7640 :     }
     142             : 
     143       14967 :     virtual ~assembler_compressed()
     144        7640 :     {
     145       14967 :     }
     146             : };
     147             : 
     148             : class assembler_tidy : public assembler_compressed
     149             : {
     150             : public:
     151         313 :     assembler_tidy(std::ostream & out)
     152         313 :         : assembler_compressed(out)
     153             :     {
     154         313 :     }
     155             : 
     156         417 :     virtual ~assembler_tidy()
     157         313 :     {
     158         417 :     }
     159             : 
     160         355 :     virtual void newline()
     161             :     {
     162         355 :         f_started = false;
     163         355 :         f_out << std::endl;
     164         355 :     }
     165             : };
     166             : 
     167             : class assembler_compact : public assembler_tidy
     168             : {
     169             : public:
     170         209 :     assembler_compact(std::ostream & out)
     171         209 :         : assembler_tidy(out)
     172             :     {
     173         209 :     }
     174             : 
     175         313 :     virtual ~assembler_compact()
     176         209 :     {
     177         313 :     }
     178             : 
     179         921 :     virtual void output_operator(std::string const & str, flags_t flags)
     180             :     {
     181         921 :         f_started = true;
     182         921 :         if((flags & (g_flag_optional_spaces | g_flag_optional_spaces_or_newlines)) != 0)
     183             :         {
     184         122 :             f_out << " " << str << " ";
     185             :         }
     186         799 :         else if((flags & (g_flag_optional_space_before | g_flag_optional_space_before_or_newline)) != 0)
     187             :         {
     188         113 :             f_out << " " << str;
     189             :         }
     190         686 :         else if((flags & (g_flag_optional_space_after | g_flag_optional_space_after_or_newline)) != 0)
     191             :         {
     192         338 :             f_out << str << " ";
     193             :         }
     194             :         else
     195             :         {
     196         348 :             assembler_tidy::output_operator(str, flags);
     197             :         }
     198         921 :     }
     199             : };
     200             : 
     201             : class assembler_expanded : public assembler_compact
     202             : {
     203             : public:
     204         105 :     assembler_expanded(std::ostream & out)
     205         105 :         : assembler_compact(out)
     206             :     {
     207         105 :     }
     208             : 
     209         210 :     virtual ~assembler_expanded()
     210         105 :     {
     211         210 :     }
     212             : 
     213         641 :     virtual void output_operator(std::string const & str, flags_t flags)
     214             :     {
     215         641 :         if((flags & g_flag_optional_spaces_or_newlines) != 0)
     216             :         {
     217         102 :             f_started = false;
     218         102 :             f_out << std::endl << str << std::endl;
     219             :         }
     220         539 :         else if((flags & g_flag_optional_space_before_or_newline) != 0)
     221             :         {
     222         102 :             f_started = false;
     223         102 :             f_out << std::endl << str;
     224             :         }
     225         437 :         else if((flags & g_flag_optional_space_after_or_newline) != 0)
     226             :         {
     227          35 :             f_started = false;
     228          35 :             f_out << str << std::endl;
     229             :         }
     230         402 :         else if((flags & g_flag_optional_operator) != 0)
     231             :         {
     232         102 :             f_started = true;
     233         102 :             f_out << str;
     234             :         }
     235             :         else
     236             :         {
     237         300 :             assembler_compact::output_operator(str, flags);
     238             :         }
     239         641 :     }
     240             : 
     241         138 :     virtual void output_identation()
     242             :     {
     243         138 :         f_out << "  ";
     244         138 :     }
     245             : };
     246             : 
     247        7740 : assembler::assembler(std::ostream & out)
     248        7740 :     : f_out(out)
     249             : {
     250        7740 : }
     251             : 
     252       42559 : std::string assembler::escape_id(std::string const & id)
     253             : {
     254       42559 :     std::string result;
     255             : 
     256             :     // create a temporary lexer to apply the conversion
     257       85118 :     std::stringstream ss;
     258       85118 :     position pos("assembler.css");
     259       85118 :     lexer l(ss, pos);
     260             : 
     261       42559 :     bool first_char(true);
     262      143354 :     for(char const *s(id.c_str()); *s != '\0'; )
     263             :     {
     264             :         char mb[5];
     265      100795 :         unsigned char c(static_cast<unsigned char>(*s));
     266      100795 :         size_t len(1);
     267      100795 :         if(c >= 0xF0)
     268             :         {
     269          28 :             len = 4;
     270             :         }
     271      100767 :         else if(c >= 0xE0)
     272             :         {
     273           4 :             len = 3;
     274             :         }
     275      100763 :         else if(c >= 0xC0)
     276             :         {
     277           8 :             len = 2;
     278             :         }
     279             :         //else len = 1 -- already set to 1 by default
     280      201690 :         for(size_t i(0); i < len; ++i, ++s)
     281             :         {
     282      100895 :             if(*s == '\0')
     283             :             {
     284             :                 // UTF-8 should be perfect when we reach the assembler
     285             :                 throw csspp_exception_logic("assembler.cpp: assembler::escape_id(): invalid UTF-8 character found."); // LCOV_EXCL_LINE
     286             :             }
     287      100895 :             mb[i] = *s;
     288             :         }
     289      100795 :         mb[len] = '\0';
     290             : 
     291      100795 :         wide_char_t wc(l.mbtowc(mb));
     292             : 
     293      143354 :         if((first_char && lexer::is_start_identifier(wc))
     294      159048 :         || (!first_char && lexer::is_identifier(wc)))
     295             :         {
     296      100766 :             result += mb;
     297             :         }
     298             :         else
     299             :         {
     300          29 :             result += '\\';
     301          29 :             if(wc >= '0' && wc <= '9')
     302             :             {
     303             :                 // digits need to be defined as hexa
     304          16 :                 result += '3';
     305          16 :                 result += wc;
     306             :                 // add a space if the next character requires us to do so
     307             :                 // (by now identifier letters should all be lower case so
     308             :                 // the 'A' to 'F' should never match)
     309          32 :                 if((s[0] >= '0' && s[0] <= '9')
     310          10 :                 || (s[0] >= 'a' && s[0] <= 'f')
     311             :                 || (s[0] >= 'A' && s[0] <= 'F')) // LCOV_EXCL_LINE
     312             :                 {
     313          14 :                     result += ' ';
     314             :                 }
     315             :             }
     316             :             else
     317             :             {
     318          13 :                 result += mb;
     319             :             }
     320             :         }
     321      100795 :         first_char = false;
     322             :     }
     323             : 
     324       85118 :     return result;
     325             : }
     326             : 
     327        7740 : void assembler::output(node::pointer_t n, output_mode_t mode)
     328             : {
     329        7740 :     f_root = n;
     330             : 
     331        7740 :     f_impl.reset();
     332        7740 :     switch(mode)
     333             :     {
     334             :     case output_mode_t::COMPACT:
     335         104 :         f_impl.reset(new assembler_compact(f_out));
     336         104 :         break;
     337             : 
     338             :     case output_mode_t::COMPRESSED:
     339        7327 :         f_impl.reset(new assembler_compressed(f_out));
     340        7327 :         break;
     341             : 
     342             :     case output_mode_t::EXPANDED:
     343         105 :         f_impl.reset(new assembler_expanded(f_out));
     344         105 :         break;
     345             : 
     346             :     case output_mode_t::TIDY:
     347         104 :         f_impl.reset(new assembler_tidy(f_out));
     348         104 :         break;
     349             : 
     350             :     }
     351        7740 :     if(!f_impl)
     352             :     {
     353         100 :         throw csspp_exception_logic("assembler.cpp: assembler::output(): called with an invalid mode.");
     354             :     }
     355             : 
     356        7752 :     output(n);
     357        7528 : }
     358             : 
     359      312403 : void assembler::output(node::pointer_t n)
     360             : {
     361      312403 :     switch(n->get_type())
     362             :     {
     363             :     case node_type_t::ADD:
     364           6 :         f_impl->output_operator("+", g_flag_optional_spaces);
     365           6 :         break;
     366             : 
     367             :     case node_type_t::ARG:
     368             :         {
     369       84538 :             size_t const max_children(n->size());
     370      170119 :             for(size_t idx(0); idx < max_children; ++idx)
     371             :             {
     372       85581 :                 output(n->get_child(idx));
     373             :             }
     374             :         }
     375       84538 :         break;
     376             : 
     377             :     case node_type_t::AT_KEYWORD:
     378          26 :         output_at_keyword(n);
     379          26 :         break;
     380             : 
     381             :     case node_type_t::COLON:
     382         164 :         f_impl->output_operator(":", 0);
     383         164 :         break;
     384             : 
     385             :     case node_type_t::COLOR:
     386             :         {
     387         368 :             color c(n->get_color());
     388         368 :             f_impl->output_token(c.to_string());
     389             :         }
     390         368 :         break;
     391             : 
     392             :     // This should have been transformed to a list (ARG for selectors
     393             :     // and functions...)
     394             :     //case node_type_t::COMMA:
     395             :     //    f_impl->output_operator(",", g_flag_optional_space_after);
     396             :     //    break;
     397             : 
     398             :     case node_type_t::COMMENT:
     399        7540 :         output_comment(n);
     400        7540 :         break;
     401             : 
     402             :     case node_type_t::DASH_MATCH:
     403           4 :         f_impl->output_operator("|=", g_flag_optional_spaces);
     404           4 :         break;
     405             : 
     406             :     case node_type_t::DECIMAL_NUMBER:
     407             :         // this may be a dimension, if not f_string is empty anyway
     408       15278 :         verify_dimension(n);
     409       15278 :         f_out << decimal_number_to_string(n->get_decimal_number(), true) << n->get_string();
     410       15278 :         break;
     411             : 
     412             :     case node_type_t::DECLARATION:
     413             :         {
     414       42344 :             f_impl->output_identation();
     415       42344 :             f_out << n->get_string();
     416       42344 :             f_impl->output_operator(":", g_flag_optional_space_after);
     417       42344 :             size_t const max_children(n->size());
     418       84700 :             for(size_t idx(0); idx < max_children; ++idx)
     419             :             {
     420       84712 :                 node::pointer_t child(n->get_child(idx));
     421       42356 :                 output(child);
     422       84712 :                 if(child->is(node_type_t::ARG)
     423       42356 :                 && idx + 1 != max_children)
     424             :                 {
     425          12 :                     f_impl->output_operator(",", g_flag_optional_space_after);
     426             :                 }
     427             :             }
     428             :             // we make sure it appears at the end
     429       42344 :             if(n->get_flag("important"))
     430             :             {
     431           4 :                 f_impl->output_operator("!", g_flag_optional_space_before);
     432           4 :                 f_out << "important";
     433             :             }
     434             :         }
     435       42344 :         break;
     436             : 
     437             :     case node_type_t::DIVIDE:
     438           4 :         f_impl->output_operator("/", 0);
     439           4 :         break;
     440             : 
     441             :     case node_type_t::EQUAL:
     442          12 :         f_impl->output_operator("=", g_flag_optional_spaces);
     443          12 :         break;
     444             : 
     445             :     case node_type_t::FONT_METRICS:
     446             :         // this is a mouthful!
     447          22 :         f_out << decimal_number_to_string(n->get_font_size() * (n->get_dim1() == "%" ? 100.0 : 1.0), true) << n->get_dim1()
     448          33 :               << "/" << decimal_number_to_string(n->get_line_height() * (n->get_dim2() == "%" ? 100.0 : 1.0), true) << n->get_dim2();
     449          11 :         break;
     450             : 
     451             :     case node_type_t::FUNCTION:
     452             :         {
     453          55 :             f_out << n->get_string();
     454          55 :             f_impl->output_operator("(", 0);
     455          55 :             if(!n->empty())
     456             :             {
     457          55 :                 if(n->get_child(0)->is(node_type_t::ARG))
     458             :                 {
     459          15 :                     bool first(true);
     460          15 :                     size_t const max_children(n->size());
     461          34 :                     for(size_t idx(0); idx < max_children; ++idx)
     462             :                     {
     463          19 :                         if(first)
     464             :                         {
     465          15 :                             first = false;
     466             :                         }
     467             :                         else
     468             :                         {
     469           4 :                             f_impl->output_operator(",", g_flag_optional_space_after);
     470             :                         }
     471          19 :                         output(n->get_child(idx));
     472             :                     }
     473             :                 }
     474             :                 else
     475             :                 {
     476             :                     // no ARG then no commas; this happens in :not(),
     477             :                     // :lang(), nth-child(), etc.
     478          40 :                     size_t const max_children(n->size());
     479          88 :                     for(size_t idx(0); idx < max_children; ++idx)
     480             :                     {
     481          48 :                         output(n->get_child(idx));
     482             :                     }
     483             :                 }
     484             :             }
     485          55 :             f_impl->output_operator(")", 0);
     486             :         }
     487          55 :         break;
     488             : 
     489             :     case node_type_t::GREATER_THAN:
     490           4 :         f_impl->output_operator(">", g_flag_optional_spaces);
     491           4 :         break;
     492             : 
     493             :     case node_type_t::HASH:
     494          24 :         f_out << "#" << n->get_string();
     495          24 :         break;
     496             : 
     497             :     case node_type_t::IDENTIFIER:
     498       42559 :         f_out << escape_id(n->get_string());
     499       42559 :         break;
     500             : 
     501             :     case node_type_t::INCLUDE_MATCH:
     502           4 :         f_impl->output_operator("~=", g_flag_optional_spaces);
     503           4 :         break;
     504             : 
     505             :     case node_type_t::INTEGER:
     506             :         // this may be a dimension, if not f_string is empty anyway
     507       19688 :         verify_dimension(n);
     508       19688 :         f_out << n->get_integer() << n->get_string();
     509       19688 :         break;
     510             : 
     511             :     case node_type_t::LIST:
     512             :         {
     513        7644 :             size_t const max_children(n->size());
     514       57624 :             for(size_t idx(0); idx < max_children; ++idx)
     515             :             {
     516       99960 :                 node::pointer_t child(n->get_child(idx));
     517       49980 :                 output(child);
     518       99960 :                 if(child->is(node_type_t::DECLARATION)
     519       49980 :                 && idx + 1 != max_children)
     520             :                 {
     521         171 :                     f_impl->output_operator(";", g_flag_optional_space_after_or_newline);
     522             :                 }
     523             :             }
     524             :         }
     525        7644 :         break;
     526             : 
     527             :     case node_type_t::MULTIPLY:
     528          28 :         f_impl->output_operator("*", 0);
     529          28 :         break;
     530             : 
     531             :     case node_type_t::OPEN_CURLYBRACKET:
     532             :         {
     533       42143 :             f_impl->output_operator("{", g_flag_optional_spaces_or_newlines);
     534       42143 :             size_t const max_children(n->size());
     535       84294 :             for(size_t idx(0); idx < max_children; ++idx)
     536             :             {
     537       84302 :                 node::pointer_t item(n->get_child(idx));
     538       42151 :                 output(n->get_child(idx));
     539       84302 :                 if(item->is(node_type_t::DECLARATION)
     540       42151 :                 && idx + 1 < max_children)
     541             :                 {
     542           8 :                     f_impl->output_operator(";", g_flag_optional_space_after_or_newline);
     543             :                 }
     544             :             }
     545       42143 :             f_impl->output_operator(";", g_flag_optional_operator);
     546       42143 :             f_impl->output_operator("}", g_flag_optional_space_before_or_newline);
     547       42143 :             f_impl->newline();
     548             :         }
     549       42143 :         break;
     550             : 
     551             :     case node_type_t::OPEN_PARENTHESIS:
     552          16 :         output_parenthesis(n, 0);
     553          16 :         break;
     554             : 
     555             :     case node_type_t::OPEN_SQUAREBRACKET:
     556             :         {
     557          36 :             f_impl->output_operator("[", 0);
     558          36 :             size_t const max_children(n->size());
     559         136 :             for(size_t idx(0); idx < max_children; ++idx)
     560             :             {
     561         100 :                 output(n->get_child(idx));
     562             :             }
     563          36 :             f_impl->output_operator("]", 0);
     564             :         }
     565          36 :         break;
     566             : 
     567             :     case node_type_t::PERCENT:
     568        7060 :         f_out << decimal_number_to_string(n->get_decimal_number() * 100.0, true) << "%";
     569        7060 :         break;
     570             : 
     571             :     case node_type_t::PERIOD:
     572          44 :         f_impl->output_operator(".", 0);
     573          44 :         break;
     574             : 
     575             :     case node_type_t::PRECEDED:
     576           8 :         f_impl->output_operator("~", g_flag_optional_spaces);
     577           8 :         break;
     578             : 
     579             :     case node_type_t::PREFIX_MATCH:
     580           4 :         f_impl->output_operator("^=", g_flag_optional_spaces);
     581           4 :         break;
     582             : 
     583             :     case node_type_t::SCOPE:
     584          20 :         f_impl->output_operator("|", 0);
     585          20 :         break;
     586             : 
     587             :     case node_type_t::STRING:
     588          73 :         output_string(n->get_string());
     589          73 :         break;
     590             : 
     591             :     case node_type_t::SUBSTRING_MATCH:
     592           4 :         f_impl->output_operator("*=", g_flag_optional_spaces);
     593           4 :         break;
     594             : 
     595             :     case node_type_t::SUBTRACT: // for calc() / expression()
     596           9 :         f_impl->output_operator("-", 0);
     597           9 :         break;
     598             : 
     599             :     case node_type_t::SUFFIX_MATCH:
     600           4 :         f_impl->output_operator("$=", g_flag_optional_spaces);
     601           4 :         break;
     602             : 
     603             :     case node_type_t::UNICODE_RANGE:
     604             :         {
     605           7 :             unicode_range_t const range(static_cast<range_value_t>(n->get_integer()));
     606           7 :             f_out << "U+" << range.to_string();
     607             :         }
     608           7 :         break;
     609             : 
     610             :     case node_type_t::URL:
     611             :         // TODO: escape special characters or we won't be able to re-read this one
     612          21 :         output_url(n->get_string());
     613          21 :         break;
     614             : 
     615             :     case node_type_t::WHITESPACE:
     616             :         // explicit whitespace that we still have in the tree are kept as is
     617         358 :         f_out << " ";
     618         358 :         break;
     619             : 
     620             :     case node_type_t::COMPONENT_VALUE:
     621       42147 :         output_component_value(n);
     622       42147 :         break;
     623             : 
     624             :     case node_type_t::AN_PLUS_B:
     625             :         {
     626             :             // TODO: support adding around the operator?
     627          40 :             nth_child const an_b(n->get_integer());
     628          20 :             f_out << an_b.to_string();
     629             :         }
     630          20 :         break;
     631             : 
     632             :     case node_type_t::FRAME:
     633             :         {
     634             :             // output the frame position
     635             :             //
     636          16 :             decimal_number_t p(n->get_decimal_number());
     637          16 :             if(p >= 1.0)
     638             :             {
     639           4 :                 f_out << "to";  // strlen("to") < strlen("100%")!
     640             :             }
     641             :             else
     642             :             {
     643             :                 // strlen("from") > strlen("0%") so we use "0%"
     644             :                 //
     645          12 :                 if(p < 0.0)
     646             :                 {
     647           0 :                     p = 0.0;
     648             :                 }
     649          12 :                 f_out << decimal_number_to_string(p * 100.0, true) << "%";
     650             :             }
     651             : 
     652             :             // output the frame component values
     653             :             //
     654          16 :             f_impl->output_operator("{", g_flag_optional_spaces_or_newlines);
     655          16 :             size_t const max_children(n->size());
     656          32 :             for(size_t idx(0); idx < max_children; ++idx)
     657             :             {
     658          32 :                 node::pointer_t item(n->get_child(idx));
     659          16 :                 output(n->get_child(idx));
     660          32 :                 if(item->is(node_type_t::DECLARATION)
     661          16 :                 && idx + 1 < max_children)
     662             :                 {
     663           0 :                     f_impl->output_operator(";", g_flag_optional_space_after_or_newline);
     664             :                 }
     665             :             }
     666          16 :             f_impl->output_operator(";", g_flag_optional_operator);
     667          16 :             f_impl->output_operator("}", g_flag_optional_space_before_or_newline);
     668          16 :             f_impl->newline();
     669             :         }
     670          16 :         break;
     671             : 
     672             :     case node_type_t::UNKNOWN:
     673             :     case node_type_t::AND:
     674             :     case node_type_t::ASSIGNMENT:
     675             :     case node_type_t::ARRAY:
     676             :     case node_type_t::BOOLEAN:
     677             :     case node_type_t::CDC:
     678             :     case node_type_t::CDO:
     679             :     case node_type_t::CLOSE_CURLYBRACKET:
     680             :     case node_type_t::CLOSE_PARENTHESIS:
     681             :     case node_type_t::CLOSE_SQUAREBRACKET:
     682             :     case node_type_t::COLUMN:
     683             :     case node_type_t::COMMA:
     684             :     case node_type_t::CONDITIONAL:
     685             :     case node_type_t::DOLLAR:
     686             :     case node_type_t::EOF_TOKEN:
     687             :     case node_type_t::EXCLAMATION:
     688             :     case node_type_t::GREATER_EQUAL:
     689             :     case node_type_t::LESS_EQUAL:
     690             :     case node_type_t::LESS_THAN:
     691             :     case node_type_t::MAP:
     692             :     case node_type_t::MODULO:
     693             :     case node_type_t::NOT_EQUAL:
     694             :     case node_type_t::NULL_TOKEN:
     695             :     case node_type_t::PLACEHOLDER:
     696             :     case node_type_t::POWER:
     697             :     case node_type_t::REFERENCE:
     698             :     case node_type_t::SEMICOLON:
     699             :     case node_type_t::VARIABLE:
     700             :     case node_type_t::VARIABLE_FUNCTION:
     701             :     case node_type_t::max_type:
     702             :         // many of the nodes are not expected in a valid tree being compiled
     703             :         // all of those will generate this exception
     704             :         {
     705         224 :             std::stringstream ss;
     706         112 :             ss << "assembler.cpp: unexpected token "
     707         224 :                << n->get_type()
     708         112 :                << " in output() call.";
     709         112 :             throw csspp_exception_logic(ss.str());
     710             :         }
     711             : 
     712             :     }
     713      312291 : }
     714             : 
     715       42147 : void assembler::output_component_value(node::pointer_t n)
     716             : {
     717       42147 :     bool first(true);
     718       42147 :     bool has_arg(false);
     719       42147 :     size_t const max_children(n->size());
     720      126445 :     for(size_t idx(0); idx < max_children; ++idx)
     721             :     {
     722      168596 :         node::pointer_t c(n->get_child(idx));
     723       84298 :         if(c->is(node_type_t::OPEN_CURLYBRACKET))
     724             :         {
     725       42147 :             if(has_arg)
     726             :             {
     727       42143 :                 output(c);
     728             :             }
     729             :         }
     730       42151 :         else if(!c->is(node_type_t::ARG))
     731             :         {
     732             :             // unexpected for a component value
     733             :             //
     734             :             std::stringstream ss;                                                                           // LCOV_EXCL_LINE
     735             :             ss << "assembler.cpp: expected all direct children of COMPONENT_VALUE to be ARG instead of "    // LCOV_EXCL_LINE
     736             :                << c->get_type()                                                                             // LCOV_EXCL_LINE
     737           0 :                << " on line "
     738           0 :                << c->get_position().get_line()
     739           0 :                << " in \""
     740           0 :                << c->get_position().get_filename()
     741             :                << "\".";                                                                                    // LCOV_EXCL_LINE
     742           0 :             if(c->is(node_type_t::IDENTIFIER))
     743             :             {
     744           0 :                 ss << " (identifier is \"" << escape_id(c->get_string()) << "\")";
     745             :             }
     746             :             throw csspp_exception_logic(ss.str());                                                          // LCOV_EXCL_LINE
     747             :         }
     748       42151 :         else if(c->empty() || !c->get_last_child()->is(node_type_t::PLACEHOLDER))
     749             :         {
     750             :             // TODO: if we compile out PLACEHOLDER nodes in the compiler
     751             :             //       then we can remove the test here... (on the line prior)
     752       42147 :             has_arg = true;
     753       42147 :             if(first)
     754             :             {
     755       42143 :                 first = false;
     756             :             }
     757             :             else
     758             :             {
     759           4 :                 f_impl->output_operator(",", g_flag_optional_space_after);
     760             :             }
     761       42147 :             output(c);
     762             :         }
     763             :     }
     764       42147 : }
     765             : 
     766          24 : void assembler::output_parenthesis(node::pointer_t n, int flags)
     767             : {
     768          24 :     if(flags == 0)
     769             :     {
     770             :         // we must have a space here otherwise the '(' transforms a
     771             :         // preceeding identifier in a function
     772             :         //
     773             :         // TODO: once our assembler is smarter we will know what is
     774             :         //       before and thus avoid the space if possible.
     775             :         //
     776          16 :         f_out << " ";
     777             :     }
     778          24 :     f_impl->output_operator("(", 0);
     779          24 :     size_t const max_children(n->size());
     780         156 :     for(size_t idx(0); idx < max_children; ++idx)
     781             :     {
     782         264 :         node::pointer_t child(n->get_child(idx));
     783         132 :         if(child->is(node_type_t::OPEN_PARENTHESIS))
     784             :         {
     785           8 :             if(idx != 0)
     786             :             {
     787           4 :                 f_out << " ";
     788             :             }
     789           8 :             output_parenthesis(child, 1);
     790             :         }
     791             :         else
     792             :         {
     793         124 :             output(child);
     794             :         }
     795             :     }
     796          24 :     f_impl->output_operator(")", flags == 0 ? g_flag_optional_space_after : 0);
     797          24 : }
     798             : 
     799          26 : void assembler::output_at_keyword(node::pointer_t n)
     800             : {
     801          26 :     f_out << "@" << n->get_string() << " ";
     802          26 :     bool no_block(true);
     803          26 :     size_t const max_children(n->size());
     804          26 :     if(max_children > 0)
     805             :     {
     806          52 :         if(n->get_string() == "-o-keyframes"
     807          26 :         || n->get_string() == "-webkit-keyframes"
     808          52 :         || n->get_string() == "keyframes")
     809             :         {
     810             :             // in this case we have one identifier followed by X frames
     811             :             // which need to appear between '{' ... '}'
     812             :             //
     813           4 :             output(n->get_child(0));
     814           4 :             f_impl->output_operator("{", g_flag_optional_space_before);
     815           4 :             f_impl->newline();
     816          20 :             for(size_t idx(1); idx < max_children; ++idx)
     817             :             {
     818          16 :                 output(n->get_child(idx));
     819             :             }
     820           4 :             f_impl->output_operator("}", 0);
     821           4 :             no_block = false;  // do not output ';'
     822             :         }
     823             :         else
     824             :         {
     825          88 :             for(size_t idx(0); idx < max_children; ++idx)
     826             :             {
     827         132 :                 node::pointer_t child(n->get_child(idx));
     828         132 :                 if(idx + 1 == max_children
     829          66 :                 && child->is(node_type_t::OPEN_CURLYBRACKET))
     830             :                 {
     831          18 :                     f_impl->newline();
     832          18 :                     no_block = false;  // do not output ';'
     833             :                 }
     834             :                 //if(child->is(node_type_t::COMPONENT_VALUE))
     835             :                 //{
     836             :                 //    f_impl->newline();
     837             :                 //    f_impl->output_operator("{", 0);
     838             :                 //    f_impl->newline();
     839             :                 //    output(child);
     840             :                 //    f_impl->output_operator("}", 0);
     841             :                 //    f_impl->newline();
     842             :                 //}
     843             :                 //else
     844          66 :                 if(child->is(node_type_t::OPEN_CURLYBRACKET))
     845             :                 {
     846             :                     // nearly like output(child), except that we do not add
     847             :                     // a ';' before the '}'
     848          18 :                     f_impl->output_operator("{", 0);
     849          18 :                     f_impl->newline();
     850          18 :                     size_t const max_sub_children(child->size());
     851          48 :                     for(size_t j(0); j < max_sub_children; ++j)
     852             :                     {
     853          30 :                         output(child->get_child(j));
     854             :                     }
     855          18 :                     f_impl->output_operator("}", 0);
     856          18 :                     f_impl->newline();
     857             :                 }
     858          48 :                 else if(child->is(node_type_t::ARG))
     859             :                 {
     860          16 :                     output(child);
     861          48 :                     if(idx + 1 < max_children
     862          64 :                     && n->get_child(idx + 1)->is(node_type_t::ARG))
     863             :                     {
     864           4 :                         f_impl->output_operator(",", g_flag_optional_space_after);
     865             :                     }
     866             :                 }
     867             :                 else
     868             :                 {
     869          32 :                     output(child);
     870             :                 }
     871             :             }
     872             :         }
     873             :     }
     874          26 :     if(no_block)
     875             :     {
     876           4 :         f_out << ";";
     877             :     }
     878             : 
     879             :     // extra newline after an @-keyword
     880          26 :     f_impl->newline();
     881          26 : }
     882             : 
     883        7540 : void assembler::output_comment(node::pointer_t n)
     884             : {
     885             :     // we take care of comments and don't give the impl's a chance to
     886             :     // do anything about this; (1) we force a newline before if we
     887             :     // already output something; (2) we force a newline at the end
     888             :     //
     889        7540 :     f_impl->newline_if_not_empty();
     890       15080 :     std::string const comment(n->get_string());
     891        7540 :     if(n->get_integer() == 0)
     892             :     {
     893             :         // note: a C++ comment is not valid in a .css file, so here we
     894             :         //       convert it
     895             :         //
     896          12 :         bool first(true);
     897          12 :         std::string::size_type start(0);
     898          12 :         std::string::size_type end(comment.find('\n'));
     899          28 :         while(end != std::string::npos)
     900             :         {
     901           8 :             if(first)
     902             :             {
     903           4 :                 first = false;
     904           4 :                 f_out << "/* ";
     905             :             }
     906             :             else
     907             :             {
     908           4 :                 f_out << " * ";
     909             :             }
     910           8 :             f_out << comment.substr(start, end - start) << std::endl;
     911           8 :             start = end + 1;
     912           8 :             end = comment.find('\n', start);
     913             :         }
     914          12 :         if(start < comment.size())
     915             :         {
     916          12 :             if(first)
     917             :             {
     918             :                 // write the whole thing on a single line
     919           8 :                 f_out << "/* "
     920          24 :                       << comment.substr(start)
     921           8 :                       << " */"
     922           8 :                       << std::endl;
     923             :             }
     924             :             else
     925             :             {
     926           8 :                 f_out << " * " << comment.substr(start) << std::endl
     927           4 :                       << " */" << std::endl;
     928             :             }
     929             :         }
     930             :     }
     931             :     else
     932             :     {
     933             :         // TODO: add the " * " on each line? (I don't think we remove
     934             :         //       those thus we would already have them if present in the
     935             :         //       source)
     936             :         //
     937        7528 :         f_out << "/* " << comment << " */" << std::endl;
     938             :     }
     939        7540 : }
     940             : 
     941          77 : void assembler::output_string(std::string const & str)
     942             : {
     943             :     // count the single and double quotes
     944          77 :     int sq(0);
     945          77 :     int dq(0);
     946         814 :     for(char const *s(str.c_str()); *s != '\0'; ++s)
     947             :     {
     948         737 :         if(*s == '\'')
     949             :         {
     950          41 :             ++sq;
     951             :         }
     952         696 :         else if(*s == '"')
     953             :         {
     954          41 :             ++dq;
     955             :         }
     956             :     }
     957             : 
     958             :     // more single quotes? if so use "..."
     959          77 :     if(sq >= dq)
     960             :     {
     961             :         // use " in this case
     962          72 :         f_out << '"';
     963         670 :         for(char const *s(str.c_str()); *s != '\0'; ++s)
     964             :         {
     965         598 :             if(*s == '"')
     966             :             {
     967          16 :                 f_out << "\\\"";
     968             :             }
     969             :             else
     970             :             {
     971         582 :                 f_out << *s;
     972             :             }
     973             :         }
     974          72 :         f_out << '"';
     975             :     }
     976             :     else
     977             :     {
     978             :         // use ' in this case
     979           5 :         f_out << '\'';
     980         144 :         for(char const *s(str.c_str()); *s != '\0'; ++s)
     981             :         {
     982         139 :             if(*s == '\'')
     983             :             {
     984           7 :                 f_out << "\\'";
     985             :             }
     986             :             else
     987             :             {
     988         132 :                 f_out << *s;
     989             :             }
     990             :         }
     991           5 :         f_out << '\'';
     992             :     }
     993          77 : }
     994             : 
     995          21 : void assembler::output_url(std::string const & str)
     996             : {
     997          21 :     f_out << "url";
     998          21 :     f_impl->output_operator("(", g_flag_optional_space_after);
     999             : 
    1000             :     //
    1001             :     // the URI can be output as is if it does not include one of:
    1002             :     //   '('
    1003             :     //   ')'
    1004             :     //   "'"
    1005             :     //   '"'
    1006             :     //   non-printable character (see lexer)
    1007             :     //
    1008          21 :     bool direct(true);
    1009         637 :     for(char const *s(str.c_str()); *s != '\0'; ++s)
    1010             :     {
    1011         620 :         char const c(*s);
    1012         620 :         if(c == '('
    1013         619 :         || c == ')'
    1014         619 :         || c == '\''
    1015         617 :         || c == '"'
    1016        1236 :         || lexer::is_non_printable(static_cast<wide_char_t>(c))) // this is UTF-8 compatible
    1017             :         {
    1018           4 :             direct = false;
    1019           4 :             break;
    1020             :         }
    1021             :     }
    1022          21 :     if(direct)
    1023             :     {
    1024             :         // we can output that one as is
    1025          17 :         f_out << str;
    1026             :     }
    1027             :     else
    1028             :     {
    1029             :         // output this URI as a string
    1030           4 :         output_string(str);
    1031             :     }
    1032             : 
    1033          21 :     f_impl->output_operator(")", g_flag_optional_space_before);
    1034          21 : }
    1035             : 
    1036             : } // namespace csspp
    1037             : 
    1038           4 : std::ostream & operator << (std::ostream & out, csspp::output_mode_t const type)
    1039             : {
    1040           4 :     switch(type)
    1041             :     {
    1042             :     case csspp::output_mode_t::COMPACT:
    1043           1 :         out << "COMPACT";
    1044           1 :         break;
    1045             : 
    1046             :     case csspp::output_mode_t::COMPRESSED:
    1047           1 :         out << "COMPRESSED";
    1048           1 :         break;
    1049             : 
    1050             :     case csspp::output_mode_t::EXPANDED:
    1051           1 :         out << "EXPANDED";
    1052           1 :         break;
    1053             : 
    1054             :     case csspp::output_mode_t::TIDY:
    1055           1 :         out << "TIDY";
    1056           1 :         break;
    1057             : 
    1058             :     }
    1059             : 
    1060           4 :     return out;
    1061           9 : }
    1062             : 
    1063             : // Local Variables:
    1064             : // mode: cpp
    1065             : // indent-tabs-mode: nil
    1066             : // c-basic-offset: 4
    1067             : // tab-width: 4
    1068             : // End:
    1069             : 
    1070             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.12