Pages

Tuesday, May 16, 2017

A CSS styling tool

I've been writing some code for my blog posts and every time I need to insert some excerpts of these codings it's a somewhat painful task to format it the precise way I'd like to. So I reached a threshold point where it would be worthy to set aside some time to look for some tool to help me achieve my goal. Fortunately, NetBeans, the IDE I've been using more frequently leads me half-way to where I want, by providing a Print to HTML... option:


But, unfortunately, this option does a nice job that goes beyond what I need. And the problem is that by going beyond it turns itself useless to address my current needs. It's true that it offers a tiny bit of flexibility:


But it is insufficient. The problem is that the syntax-highlighting is formatted via CSS internal styles of type text/css within a HTML comment within the well-known <style> tag. What is supposed to reflect the syntax-highlighting is within <span> tags of specific classes. Hence, I can't use it like that (not even portions of it) right away here in Blogger as I want:

<!DOCTYPE ... > 
<html>
<
head
> 
<title>framework.hxx</title
<meta ...
<style type="text/css">
<
!--
body {...}
pre {...}
table {...}
.highlight-caret-row {...}
.ST1 {...}
.preprocessor {...}
.comment {...}
.literal {...}
.pragma-omp-keyword-directive {...}
.ST0 {...}
--> 

</style>
<
/head

<body>
...
11
<span class="literal">virtual</span>
  ~
<span class="ST1">file</span>() {}
...


That is, not even portions of the above produce:
(unless with a completely inviable bit-by-bit editing)

11 virtual ~file() {}

I've got stuck to it for a while, a little sad I should say. Then I tried to surf the Web for possible help from others that could have had similar (or even the same) issue. It's true I've found them! But as for the "solutions", none would do it, unless perhaps after a long way installing a bunch of stuff far, far away, from where I stand and all that would render it inviable to me. So, I started trying to accept the facts and limitations. BUT, as a "willing to be" C++ programmer I had all the power of C++ at my fingertips. And that was when I decided to craft my own, yes, little, specific, C++ tool for achieving my goal and which I present below. Please, understand, it's the very first version of it, a bit crude one could honestly say, and with room for many improvements and optimizations, specially with respect to what tools from the C++ STL would be better suited for each task. Sometimes, or maybe most of the times, there is more than one way of doing something with the C++ STL. The code below was born on-the-fly, the opposite way as well-planed code should come into light, but anyway, I was yet a little skeptical about the intent. I view it today as a tiny (the tip of the iceberg) endeavor in exploring STL. BUT, at the same time it is also a strategy to "kick-off" and/or "get going" for later devising better approaches and so on. And that's all about it. Here's the code which shall speak for itself:
     
  1 #include <cstdlib>
  2 
  3 #include <locale>
  4 #include <string>
  5 #include <sstream>
  6 #include <iostream>
  7 #include <iterator>
  8 #include <algorithm>
  9 #include <map>
 10 
 11 std::string load()
 12 {
 13   std::string file;
 14   std::string line;
 15 
 16   while ( std::getline( std::cin, line ) )
 17     file += line + "\n";
 18 
 19   return file;
 20 }
 21 
 22 using iterator = std::string::iterator;
 23 using index = std::string::size_type;
 24 
 25 const std::string keyword_style { "style" };
 26 const std::string keyword_class { "class" };
 27 const std::string keyword_pre   { "pre" };
 28 
 29 std::map< const std::string, std::string > map;
 30 
 31 iterator find
    (
      const std::string & keyword,
      const iterator p, const iterator q
    )
 32 {
 33   return std::search( p, q, keyword.begin(), keyword.end() );
 34 }
 35 
 36 std::string trim( const std::string & source )
 37 {
 38   std::stringstream rv;
 39 
 40   std::copy_if
 41   (
 42     source.begin(), source.end(),
 43     std::ostream_iterator< char >( rv ),
 44     [] ( char c ) { return !std::isspace( c, std::locale() ); }
 45   );
 46 
 47   return rv.str();
 48 }
 49 
 50 std::string nobg( const std::string & source )
 51 {
 52   const std::string bg_color { "background-color" };
 53   std::string rv { source };
 54 
 55   index bg_b = rv.find( bg_color );
 56   if ( bg_b != std::string::npos )
 57   {
 58     index bg_e = rv.find( ';', bg_b + 1 );
 59     if ( bg_e != std::string::npos )
 60       return rv.erase( bg_b, bg_e - bg_b + 1 );
 61     else
 62       return rv.erase( bg_b, std::string::npos - bg_b );
 63   }
 64 
 65   return rv;
 66 }
 67 
 68 void load_styles( const iterator sb, const iterator se )
 69 {
 70     // Styles' stream: " target { style : value ; ... } ... "
 71     std::stringstream ss;
 72     std::copy( sb, se, std::ostream_iterator< char >( ss ) );
 73 
 74     std::string target;
 75     while ( std::getline( ss, target, '{' ) )
 76     {
 77       target = trim( target );
 78 
 79       // Skip trimmed "targets" that had only \n
 80       if ( target.size() > 0 )
 81       {
 82         // This program is special, not general,
 83         // so I treat only this special case.
 84         if ( target[0] == '.' )
 85           target = target.substr( 1 );
 86 
 87         std::string styles;
 88         if ( std::getline( ss, styles, '}' ) )
 89           map[ target ] = nobg( trim( styles ) );
 90       }
 91     }
 92 }
 93 
 94 void scan_styles( iterator & q, const iterator e )
 95 {
 96   // [q-1:e] == >...
 97 
 98   const std::string keyword_style_end { "</style" };
 99   const std::string comment_beg { "<!--" };
100   const std::string comment_end { "-->" };
101 
102   // Where is </style...> ?
103   iterator se = find( keyword_style_end, q, e );
104   if ( se != e )
105   {
106     // Remember from where to later skip past > in </style...>
107     iterator kwse = se + keyword_style_end.size();
108 
109     // Remember where styles begin.
110     iterator sb = q;
111 
112     // Is there an optional <!-- ?
113     iterator c = find( comment_beg, sb, se );
114     if ( c != se )
115     {
116       // Skip the <!--
117       // In this case the styles are within comments
118       sb = c + comment_beg.size();
119 
120       // Is there a --> ?
121       c = find( comment_end, sb, se );
122       if ( c != se )
123         // Remember where styles end
124         // Actually it would be c-1
125         // but algorithms are always [x:y)
126         // so c is OK as I'll get [sb:c) == [sb:c-1]
127         se = c;
128       else
129         // If there's a <!-- there must be a -->
130         ::exit( EXIT_FAILURE );
131     }
132 
133     load_styles( sb, se );
134 
135     // Now must commit the consumption of input
136     // regarding what has been processed earlier here above
137     q = std::find( kwse, e, '>' );
138     if ( q == e )
139       // Missing > in the closing tag </style
140       ::exit( EXIT_FAILURE );
141 
142     q++;
143   }
144   else
145     // No matching </style...>
146     ::exit( EXIT_FAILURE );
147 }
148 
149 void process_class( const iterator k, const iterator q )
150 {
151   // [k:q) == class...>
152 
153   // Where is the value "..." as in class="..." ?
154   iterator vb = std::find( k + keyword_class.size(), q, '"');
155   if ( vb == q )
156     // There can't be a class attribute without a value
157     ::exit( EXIT_FAILURE );
158 
159   // vb == "
160 
161   iterator ve = std::find( vb+1, q, '"' );
162   if ( ve == q )
163     // If there was an opening " there must be a closing ".
164     ::exit( EXIT_FAILURE );
165 
166   // ve == "
167 
168   // [vb,ve] == "...xyz..."
169 
170   // [vb+1, ve) == [vb+1, ve-1] == ...xyz...
171 
172   // Get map key to be later inlined
173   std::stringstream key;
174   std::copy
      (
        vb+1, ve,
        std::ostream_iterator< char >( key )
      );
175 
176   //
177   // The next 2 lines will effectively replace
178   // class...=..."..." for style="..." in the output
179   //
180 
181   std::cout << "style=\"" << map[ key.str() ] << "\"";
182 
183   // [ve+1,q) == "...>
184   std::copy
      (
        ve+1, q,
        std::ostream_iterator< char >( std::cout )
      );
185 }
186 
187 void process_pre( const iterator k, const iterator q )
188 {
189   // [k:q) == pre...>
190 
191   // Outputs in pre style="..."> where it was pre...>
192   std::cout << "pre style=\"" << map[ "pre" ] << "\">";
193 }
194 
195 int main()
196 {
197   std::string input { load() };
198 
199   auto p = input.begin();
200   auto e = input.end();
201 
202   // I'll be walking over tags
203   p = std::find( p, e, '<' );
204   while ( p != e )
205   {
206     // Where the current tag ends?
207     auto q = std::find( p, e, '>' );
208 
209     if ( q == e )
210       // Every tag marker must be well formed.
211       ::exit( EXIT_FAILURE );
212 
213     // Move next to > of the opening tag
214     // [q-1:e] == >...
215     ++q;
216 
217     // For now I don't care about closing tags
218     if ( p[1] != '/' )
219     {
220       // Select and process what matters
221       iterator k;
222 
223       if ( ( k = find( keyword_style, p, q ) ) != q )
224       {
225         // q is passed by reference
226         // q will move next to > in </style...>
227         // </style...> is the only special closing tag
228         scan_styles( q, e );
229       }
230       else if ( ( k = find( keyword_class, p, q ) ) != q )
231       {
232         // [p:k] == <...c
233         std::copy
            (
              p, k,
              std::ostream_iterator< char >( std::cout )
            );
234 
235         // [k:q-1] == class...>
236         process_class( k, q );
237       }
238       else if ( ( k = find( keyword_pre, p, q ) ) != q )
239       {
240         // [p:k] == <...p
241         std::copy
            (
              p, k,
              std::ostream_iterator< char >( std::cout )
            );
242 
243         // [k:q-1] == pre...>
244         process_pre( k, q );
245       }
246       else
247       {
248         // q has already moved next to > in <...>
249         // [p:q) == <...>
250         std::copy
            (
              p, q,
              std::ostream_iterator< char >( std::cout )
            );
251       }
252     }
253     else
254     {
255       // Nothing that matters, </...>, just skip
256       // [p:q) == <...>
257       std::copy
          (
            p, q,
            std::ostream_iterator< char >( std::cout )
          );
258     }
259 
260     // Going ahead to the next tag
261     // q points next to > in any previous <...>
262     p = q;
263     p = std::find( p, e, '<' );
264 
265     // [q-1:p] == >...<
266     std::copy
        (
          q, p, 
          std::ostream_iterator< char >( std::cout ) 
        );
267   }
268 
269   return EXIT_SUCCESS;
270 }

The issue with this kind of C++ STL programming is that  dealing with iterators required a lot of comments, otherwise one can very easily get messed up. But, imagine how worse than that things could be if dealing with plain pointers as in legacy C coding style...
 
After an easy and simple build of the above code, the program can be used as follows:
  1. Save the Print to HTML file from NetBeans where the program lives
  2. Pipe the file in through the program and redirect its output
For instance:
(inliner happens to be the name I gave to the program)

$ cat code.html |./inliner >./code_inline_styled.html

The kind of formatted code that's output can be seen above throughout this post. I pasted-in the in-line-styled code and then applied the Courier font (to try staying consistent with all my previous postings).