LCOV - code coverage report
Current view: top level - libreoffice/workdir/unxlngi6.pro/UnpackedTarball/orcus/include/orcus - sax_parser.hpp (source / functions) Hit Total Coverage
Test: libreoffice_filtered.info Lines: 0 225 0.0 %
Date: 2012-12-17 Functions: 0 103 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*************************************************************************
       2             :  *
       3             :  * Copyright (c) 2012 Kohei Yoshida
       4             :  *
       5             :  * Permission is hereby granted, free of charge, to any person
       6             :  * obtaining a copy of this software and associated documentation
       7             :  * files (the "Software"), to deal in the Software without
       8             :  * restriction, including without limitation the rights to use,
       9             :  * copy, modify, merge, publish, distribute, sublicense, and/or sell
      10             :  * copies of the Software, and to permit persons to whom the
      11             :  * Software is furnished to do so, subject to the following
      12             :  * conditions:
      13             :  *
      14             :  * The above copyright notice and this permission notice shall be
      15             :  * included in all copies or substantial portions of the Software.
      16             :  *
      17             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      18             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
      19             :  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
      20             :  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
      21             :  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
      22             :  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      23             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
      24             :  * OTHER DEALINGS IN THE SOFTWARE.
      25             :  *
      26             :  ************************************************************************/
      27             : 
      28             : #ifndef __ORCUS_SAX_PARSER_HPP__
      29             : #define __ORCUS_SAX_PARSER_HPP__
      30             : 
      31             : #include <exception>
      32             : #include <cassert>
      33             : #include <sstream>
      34             : 
      35             : #include "pstring.hpp"
      36             : #include "cell_buffer.hpp"
      37             : 
      38             : #define ORCUS_DEBUG_SAX_PARSER 0
      39             : 
      40             : #if ORCUS_DEBUG_SAX_PARSER
      41             : #include <iostream>
      42             : using std::cout;
      43             : using std::endl;
      44             : #endif
      45             : 
      46             : namespace orcus {
      47             : 
      48             : class malformed_xml_error : public std::exception
      49             : {
      50             : public:
      51           0 :     malformed_xml_error(const std::string& msg) : m_msg(msg) {}
      52           0 :     virtual ~malformed_xml_error() throw() {}
      53           0 :     virtual const char* what() const throw()
      54             :     {
      55           0 :         return m_msg.c_str();
      56             :     }
      57             : private:
      58             :     std::string m_msg;
      59             : };
      60             : 
      61             : /**
      62             :  * Element properties passed by sax_parser to its handler's open_element()
      63             :  * and close_element() calls.
      64             :  */
      65           0 : struct sax_parser_element
      66             : {
      67             :     pstring ns;            // element namespace (optional)
      68             :     pstring name;          // element name
      69             :     const char* begin_pos; // position of the opening brace '<'.
      70             :     const char* end_pos;   // position of the char after the closing brace '>'.
      71             : };
      72             : 
      73             : /**
      74             :  * Template-based sax parser that doesn't use function pointer for
      75             :  * callbacks for better performance, especially on large XML streams.
      76             :  */
      77             : template<typename _Handler>
      78             : class sax_parser
      79             : {
      80             : public:
      81             :     typedef _Handler handler_type;
      82             : 
      83             :     sax_parser(const char* content, const size_t size, handler_type& handler);
      84             :     ~sax_parser();
      85             : 
      86             :     void parse();
      87             : 
      88             : private:
      89             : 
      90             :     std::string indent() const;
      91             : 
      92           0 :     void next() { ++m_pos; ++m_char; }
      93             : 
      94           0 :     void nest_up() { ++m_nest_level; }
      95           0 :     void nest_down()
      96             :     {
      97           0 :         assert(m_nest_level > 0);
      98           0 :         --m_nest_level;
      99           0 :     }
     100             : 
     101           0 :     inline bool has_char() const { return m_pos < m_size; }
     102             : 
     103           0 :     inline size_t remains() const
     104             :     {
     105             : #if ORCUS_DEBUG_SAX_PARSER
     106             :         if (m_pos >= m_size)
     107             :             throw malformed_xml_error("xml stream ended prematurely.");
     108             : #endif
     109           0 :         return m_size - m_pos;
     110             :     }
     111             : 
     112           0 :     char cur_char() const
     113             :     {
     114             : #if ORCUS_DEBUG_SAX_PARSER
     115             :         if (m_pos >= m_size)
     116             :             throw malformed_xml_error("xml stream ended prematurely.");
     117             : #endif
     118           0 :         return *m_char;
     119             :     }
     120             : 
     121             :     char next_char()
     122             :     {
     123             :         next();
     124             : #if ORCUS_DEBUG_SAX_PARSER
     125             :         if (m_pos >= m_size)
     126             :             throw malformed_xml_error("xml stream ended prematurely.");
     127             : #endif
     128           0 :         return *m_char;
     129             :     }
     130             : 
     131             :     void blank();
     132             : 
     133             :     /**
     134             :      * Parse XML header that occurs at the beginning of every XML stream i.e.
     135             :      * <?xml version="..." encoding="..." ?>
     136             :      */
     137             :     void header();
     138             :     void body();
     139             :     void element();
     140             :     void element_open(const char* begin_pos);
     141             :     void element_close(const char* begin_pos);
     142             :     void special_tag();
     143             :     void comment();
     144             :     void content();
     145             :     void characters();
     146             :     void characters_with_encoded_char();
     147             :     void attribute();
     148             : 
     149             :     void parse_encoded_char();
     150             : 
     151             :     void name(pstring& str);
     152             : 
     153             :     /**
     154             :      * Parse attribute value.  Note that the retreived string may be stored in
     155             :      * the temporary cell buffer. Use the string immediately after this call
     156             :      * before the buffer becomes invalid.
     157             :      */
     158             :     void value(pstring& str);
     159             :     void value_with_encoded_char(pstring& str);
     160             : 
     161             :     static bool is_blank(char c);
     162             :     static bool is_alpha(char c);
     163             :     static bool is_name_char(char c);
     164             :     static bool is_numeric(char c);
     165             : 
     166             : private:
     167             :     cell_buffer m_cell_buf;
     168             :     const char* m_content;
     169             :     const char* m_char;
     170             :     const size_t m_size;
     171             :     size_t m_pos;
     172             :     size_t m_nest_level;
     173             :     bool m_root_elem_open:1;
     174             :     handler_type& m_handler;
     175             : };
     176             : 
     177             : template<typename _Handler>
     178           0 : sax_parser<_Handler>::sax_parser(
     179             :     const char* content, const size_t size, handler_type& handler) :
     180             :     m_content(content),
     181             :     m_char(content),
     182             :     m_size(size),
     183             :     m_pos(0),
     184             :     m_nest_level(0),
     185             :     m_root_elem_open(true),
     186           0 :     m_handler(handler)
     187             : {
     188           0 : }
     189             : 
     190             : template<typename _Handler>
     191           0 : sax_parser<_Handler>::~sax_parser()
     192             : {
     193           0 : }
     194             : 
     195             : template<typename _Handler>
     196           0 : void sax_parser<_Handler>::parse()
     197             : {
     198           0 :     m_pos = 0;
     199           0 :     m_nest_level = 0;
     200           0 :     m_char = m_content;
     201           0 :     header();
     202           0 :     blank();
     203           0 :     body();
     204           0 : }
     205             : 
     206             : template<typename _Handler>
     207             : ::std::string sax_parser<_Handler>::indent() const
     208             : {
     209             :     ::std::ostringstream os;
     210             :     for (size_t i = 0; i < m_nest_level; ++i)
     211             :         os << "  ";
     212             :     return os.str();
     213             : }
     214             : 
     215             : template<typename _Handler>
     216           0 : void sax_parser<_Handler>::blank()
     217             : {
     218             :     char c = cur_char();
     219           0 :     while (is_blank(c))
     220             :         c = next_char();
     221           0 : }
     222             : 
     223             : template<typename _Handler>
     224           0 : void sax_parser<_Handler>::header()
     225             : {
     226             :     char c = cur_char();
     227           0 :     if (c != '<' || next_char() != '?' || next_char() != 'x' || next_char() != 'm' || next_char() != 'l')
     228           0 :         throw malformed_xml_error("xml header must begin with '<?xml'.");
     229             : 
     230             :     next();
     231           0 :     blank();
     232           0 :     while (cur_char() != '?')
     233             :     {
     234           0 :         attribute();
     235           0 :         blank();
     236             :     }
     237           0 :     if (next_char() != '>')
     238           0 :         throw malformed_xml_error("xml header must end with '?>'.");
     239             : 
     240             :     next();
     241             : 
     242           0 :     m_handler.declaration();
     243           0 : }
     244             : 
     245             : template<typename _Handler>
     246           0 : void sax_parser<_Handler>::body()
     247             : {
     248           0 :     while (has_char())
     249             :     {
     250           0 :         if (cur_char() == '<')
     251             :         {
     252           0 :             element();
     253           0 :             if (!m_root_elem_open)
     254             :                 // Root element closed.  Stop parsing.
     255           0 :                 return;
     256             :         }
     257             :         else
     258           0 :             characters();
     259             :     }
     260             : }
     261             : 
     262             : template<typename _Handler>
     263           0 : void sax_parser<_Handler>::element()
     264             : {
     265           0 :     assert(cur_char() == '<');
     266             :     const char* pos = m_char;
     267             :     char c = next_char();
     268           0 :     switch (c)
     269             :     {
     270             :         case '/':
     271           0 :             element_close(pos);
     272           0 :         break;
     273             :         case '!':
     274           0 :             special_tag();
     275           0 :         break;
     276             :         default:
     277           0 :             element_open(pos);
     278             :     }
     279           0 : }
     280             : 
     281             : template<typename _Handler>
     282           0 : void sax_parser<_Handler>::element_open(const char* begin_pos)
     283             : {
     284           0 :     assert(is_alpha(cur_char()));
     285             : 
     286             :     sax_parser_element elem;
     287           0 :     elem.begin_pos = begin_pos;
     288             : 
     289           0 :     name(elem.name);
     290           0 :     if (cur_char() == ':')
     291             :     {
     292             :         // this element name is namespaced.
     293             :         elem.ns = elem.name;
     294             :         next();
     295           0 :         name(elem.name);
     296             :     }
     297             : 
     298             :     while (true)
     299             :     {
     300           0 :         blank();
     301             :         char c = cur_char();
     302           0 :         if (c == '/')
     303             :         {
     304             :             // Self-closing element: <element/>
     305           0 :             if (next_char() != '>')
     306           0 :                 throw malformed_xml_error("expected '/>' to self-close the element.");
     307             :             next();
     308           0 :             elem.end_pos = m_char;
     309           0 :             m_handler.start_element(elem);
     310           0 :             m_handler.end_element(elem);
     311             :             return;
     312             :         }
     313           0 :         else if (c == '>')
     314             :         {
     315             :             // End of opening element: <element>
     316             :             next();
     317           0 :             elem.end_pos = m_char;
     318             :             nest_up();
     319           0 :             m_handler.start_element(elem);
     320             :             return;
     321             :         }
     322             :         else
     323           0 :             attribute();
     324             :     }
     325             : }
     326             : 
     327             : template<typename _Handler>
     328           0 : void sax_parser<_Handler>::element_close(const char* begin_pos)
     329             : {
     330           0 :     assert(cur_char() == '/');
     331           0 :     nest_down();
     332             :     next();
     333             :     sax_parser_element elem;
     334           0 :     elem.begin_pos = begin_pos;
     335             : 
     336           0 :     name(elem.name);
     337           0 :     if (cur_char() == ':')
     338             :     {
     339             :         elem.ns = elem.name;
     340             :         next();
     341           0 :         name(elem.name);
     342             :     }
     343             : 
     344           0 :     if (cur_char() != '>')
     345           0 :         throw malformed_xml_error("expected '>' to close the element.");
     346             :     next();
     347           0 :     elem.end_pos = m_char;
     348             : 
     349           0 :     m_handler.end_element(elem);
     350           0 :     if (!m_nest_level)
     351           0 :         m_root_elem_open = false;
     352           0 : }
     353             : 
     354             : template<typename _Handler>
     355           0 : void sax_parser<_Handler>::special_tag()
     356             : {
     357           0 :     assert(cur_char() == '!');
     358             :     // This can be either <![CDATA, <!--, or <!DOCTYPE.
     359             :     size_t len = remains();
     360           0 :     if (len < 2)
     361           0 :         throw malformed_xml_error("special tag too short.");
     362             : 
     363           0 :     switch (next_char())
     364             :     {
     365             :         case '-':
     366             :         {
     367             :             // Possibly comment.
     368           0 :             if (next_char() != '-')
     369           0 :                 throw malformed_xml_error("comment expected.");
     370             : 
     371             :             len = remains();
     372           0 :             if (len < 3)
     373           0 :                 throw malformed_xml_error("malformed comment.");
     374             : 
     375             :             next();
     376           0 :             comment();
     377             :         }
     378             :         break;
     379             :         default:
     380             :             // TODO: Handle CDATA and DOCTYPE.
     381           0 :             throw malformed_xml_error("failed to parse special tag.");
     382             :     }
     383           0 : }
     384             : 
     385             : template<typename _Handler>
     386           0 : void sax_parser<_Handler>::comment()
     387             : {
     388             :     // Parse until we reach '-->'.
     389             :     size_t len = remains();
     390           0 :     assert(len > 3);
     391             :     char c = cur_char();
     392             :     size_t i = 0;
     393             :     bool hyphen = false;
     394           0 :     for (; i < len; ++i, c = next_char())
     395             :     {
     396           0 :         if (c == '-')
     397             :         {
     398           0 :             if (!hyphen)
     399             :                 // first hyphen.
     400             :                 hyphen = true;
     401             :             else
     402             :                 // second hyphen.
     403             :                 break;
     404             :         }
     405             :         else
     406             :             hyphen = false;
     407             :     }
     408             : 
     409           0 :     if (len - i < 2 || next_char() != '>')
     410           0 :         throw malformed_xml_error("'--' should not occur in comment other than in the closing tag.");
     411             : 
     412             :     next();
     413           0 : }
     414             : 
     415             : template<typename _Handler>
     416           0 : void sax_parser<_Handler>::characters()
     417             : {
     418           0 :     size_t first = m_pos;
     419           0 :     const char* p0 = m_char;
     420           0 :     for (; has_char(); next())
     421             :     {
     422           0 :         if (cur_char() == '<')
     423             :             break;
     424             : 
     425           0 :         if (cur_char() == '&')
     426             :         {
     427             :             // Text span with one or more encoded characters. Parse using cell buffer.
     428             :             m_cell_buf.reset();
     429           0 :             m_cell_buf.append(p0, m_pos-first);
     430           0 :             characters_with_encoded_char();
     431           0 :             return;
     432             :         }
     433             :     }
     434             : 
     435           0 :     if (m_pos > first)
     436             :     {
     437           0 :         size_t size = m_pos - first;
     438           0 :         pstring val(m_content + first, size);
     439           0 :         m_handler.characters(val);
     440             :     }
     441             : }
     442             : 
     443             : template<typename _Handler>
     444           0 : void sax_parser<_Handler>::characters_with_encoded_char()
     445             : {
     446           0 :     assert(cur_char() == '&');
     447           0 :     parse_encoded_char();
     448           0 :     assert(cur_char() != ';');
     449             : 
     450           0 :     size_t first = m_pos;
     451             : 
     452           0 :     while (has_char())
     453             :     {
     454           0 :         if (cur_char() == '&')
     455             :         {
     456           0 :             if (m_pos > first)
     457           0 :                 m_cell_buf.append(m_content+first, m_pos-first);
     458             : 
     459           0 :             parse_encoded_char();
     460           0 :             assert(cur_char() != ';');
     461           0 :             first = m_pos;
     462             :         }
     463             : 
     464           0 :         if (cur_char() == '<')
     465             :             break;
     466             : 
     467           0 :         if (cur_char() != '&')
     468             :             next();
     469             :     }
     470             : 
     471           0 :     if (m_pos > first)
     472           0 :         m_cell_buf.append(m_content+first, m_pos-first);
     473             : 
     474           0 :     if (m_cell_buf.empty())
     475           0 :         m_handler.characters(pstring());
     476             :     else
     477           0 :         m_handler.characters(pstring(m_cell_buf.get(), m_cell_buf.size()));
     478           0 : }
     479             : 
     480             : template<typename _Handler>
     481           0 : void sax_parser<_Handler>::attribute()
     482             : {
     483             :     pstring attr_ns_name, attr_name, attr_value;
     484           0 :     name(attr_name);
     485           0 :     if (cur_char() == ':')
     486             :     {
     487             :         // Attribute name is namespaced.
     488             :         attr_ns_name = attr_name;
     489             :         next();
     490           0 :         name(attr_name);
     491             :     }
     492             : 
     493             : #if ORCUS_DEBUG_SAX_PARSER
     494             :     cout << "attribute: ns='" << attr_ns_name << "', name='" << attr_name << "'" << endl;
     495             : #endif
     496             : 
     497             :     char c = cur_char();
     498           0 :     if (c != '=')
     499             :     {
     500           0 :         std::ostringstream os;
     501           0 :         os << "Attribute must begin with 'name=..'. (ns='" << attr_ns_name << "', name='" << attr_name << "')";
     502           0 :         throw malformed_xml_error(os.str());
     503             :     }
     504             : 
     505             :     next();
     506           0 :     value(attr_value);
     507             : 
     508           0 :     m_handler.attribute(attr_ns_name, attr_name, attr_value);
     509           0 : }
     510             : 
     511             : template<typename _Handler>
     512           0 : void sax_parser<_Handler>::parse_encoded_char()
     513             : {
     514           0 :     assert(cur_char() == '&');
     515             :     next();
     516             :     const char* p0 = m_char;
     517           0 :     for (; has_char(); next())
     518             :     {
     519           0 :         if (cur_char() != ';')
     520           0 :             continue;
     521             : 
     522           0 :         size_t n = m_char - p0;
     523           0 :         if (!n)
     524           0 :             throw malformed_xml_error("empty encoded character.");
     525             : 
     526             : #if ORCUS_DEBUG_SAX_PARSER
     527             :         cout << "sax_parser::parse_encoded_char: raw='" << std::string(p0, n) << "'" << endl;
     528             : #endif
     529             : 
     530             :         bool found = true;
     531           0 :         if (n == 2)
     532             :         {
     533           0 :             if (!std::strncmp(p0, "lt", 2))
     534           0 :                 m_cell_buf.append("<", 1);
     535           0 :             else if (!std::strncmp(p0, "gt", 2))
     536           0 :                 m_cell_buf.append(">", 1);
     537             :             else
     538             :                 found = false;
     539             :         }
     540           0 :         else if (n == 3)
     541             :         {
     542           0 :             if (!std::strncmp(p0, "amp", 3))
     543           0 :                 m_cell_buf.append("&", 1);
     544             :             else
     545             :                 found = false;
     546             :         }
     547           0 :         else if (n == 4)
     548             :         {
     549           0 :             if (!std::strncmp(p0, "apos", 4))
     550           0 :                 m_cell_buf.append("'", 1);
     551           0 :             else if (!std::strncmp(p0, "quot", 4))
     552           0 :                 m_cell_buf.append("\"", 1);
     553             :             else
     554             :                 found = false;
     555             :         }
     556             :         else
     557             :             found = false;
     558             : 
     559             :         // Move to the character past ';' before returning to the parent call.
     560             :         next();
     561             : 
     562           0 :         if (!found)
     563             :         {
     564             : #if ORCUS_DEBUG_SAX_PARSER
     565             :             cout << "sax_parser::parse_encoded_char: not a known encoding name. Use the original." << endl;
     566             : #endif
     567             :             // Unexpected encoding name. Use the original text.
     568           0 :             m_cell_buf.append(p0, m_char-p0);
     569             :         }
     570             : 
     571           0 :         return;
     572             :     }
     573             : 
     574           0 :     throw malformed_xml_error("error parsing encoded character: terminating character is not found.");
     575             : }
     576             : 
     577             : template<typename _Handler>
     578           0 : void sax_parser<_Handler>::name(pstring& str)
     579             : {
     580           0 :     size_t first = m_pos;
     581             :     char c = cur_char();
     582           0 :     if (!is_alpha(c))
     583             :     {
     584           0 :         ::std::ostringstream os;
     585           0 :         os << "name must begin with an alphabet, but got this instead '" << c << "'";
     586           0 :         throw malformed_xml_error(os.str());
     587             :     }
     588             : 
     589           0 :     while (is_alpha(c) || is_numeric(c) || is_name_char(c))
     590             :         c = next_char();
     591             : 
     592           0 :     size_t size = m_pos - first;
     593           0 :     str = pstring(m_content+first, size);
     594           0 : }
     595             : 
     596             : template<typename _Handler>
     597           0 : void sax_parser<_Handler>::value(pstring& str)
     598             : {
     599             :     char c = cur_char();
     600           0 :     if (c != '"')
     601           0 :         throw malformed_xml_error("attribute value must be quoted");
     602             : 
     603             :     c = next_char();
     604             :     size_t first = m_pos;
     605             :     const char* p0 = m_char;
     606             : 
     607           0 :     for (; c != '"'; c = next_char())
     608             :     {
     609           0 :         if (c == '&')
     610             :         {
     611             :             // This value contains one or more encoded characters.
     612             :             m_cell_buf.reset();
     613           0 :             m_cell_buf.append(p0, m_pos-first);
     614           0 :             value_with_encoded_char(str);
     615           0 :             return;
     616             :         }
     617             :     }
     618             : 
     619           0 :     str = pstring(p0, m_pos-first);
     620             : 
     621             :     // Skip the closing quote.
     622             :     next();
     623             : }
     624             : 
     625             : template<typename _Handler>
     626           0 : void sax_parser<_Handler>::value_with_encoded_char(pstring& str)
     627             : {
     628           0 :     assert(cur_char() == '&');
     629           0 :     parse_encoded_char();
     630           0 :     assert(cur_char() != ';');
     631             : 
     632           0 :     size_t first = m_pos;
     633             : 
     634           0 :     while (has_char())
     635             :     {
     636           0 :         if (cur_char() == '&')
     637             :         {
     638           0 :             if (m_pos > first)
     639           0 :                 m_cell_buf.append(m_content+first, m_pos-first);
     640             : 
     641           0 :             parse_encoded_char();
     642           0 :             assert(cur_char() != ';');
     643           0 :             first = m_pos;
     644             :         }
     645             : 
     646           0 :         if (cur_char() == '"')
     647             :             break;
     648             : 
     649           0 :         if (cur_char() != '&')
     650             :             next();
     651             :     }
     652             : 
     653           0 :     if (m_pos > first)
     654           0 :         m_cell_buf.append(m_content+first, m_pos-first);
     655             : 
     656           0 :     if (!m_cell_buf.empty())
     657           0 :         str = pstring(m_cell_buf.get(), m_cell_buf.size());
     658             : 
     659             :     // Skip the closing quote.
     660           0 :     assert(cur_char() == '"');
     661             :     next();
     662           0 : }
     663             : 
     664             : template<typename _Handler>
     665             : bool sax_parser<_Handler>::is_blank(char c)
     666             : {
     667           0 :     if (c == ' ')
     668             :         return true;
     669           0 :     if (c == 0x0A || c == 0x0D)
     670             :         // LF or CR
     671             :         return true;
     672             :     return false;
     673             : }
     674             : 
     675             : template<typename _Handler>
     676             : bool sax_parser<_Handler>::is_alpha(char c)
     677             : {
     678           0 :     if ('a' <= c && c <= 'z')
     679             :         return true;
     680           0 :     if ('A' <= c && c <= 'Z')
     681             :         return true;
     682             :     return false;
     683             : }
     684             : 
     685             : template<typename _Handler>
     686             : bool sax_parser<_Handler>::is_name_char(char c)
     687             : {
     688           0 :     switch (c)
     689             :     {
     690             :         case '-':
     691             :         case '_':
     692             :             return true;
     693             :     }
     694             : 
     695             :     return false;
     696             : }
     697             : 
     698             : template<typename _Handler>
     699             : bool sax_parser<_Handler>::is_numeric(char c)
     700             : {
     701           0 :     if ('0' <= c && c <= '9')
     702             :         return true;
     703             :     return false;
     704             : }
     705             : 
     706             : }
     707             : 
     708             : #endif

Generated by: LCOV version 1.10