LCOV - code coverage report
Current view: top level - home/snapwebsites/snapcpp/snapwebsites/snapdatabase/snapdatabase/data - xml.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 297 422 70.4 %
Date: 2019-12-15 17:13:15 Functions: 30 31 96.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2019  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/snapdatabase
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      19             : 
      20             : 
      21             : /** \file
      22             :  * \brief Database file implementation.
      23             :  *
      24             :  * Each table uses one or more files. Each file is handled by a dbfile
      25             :  * object and a corresponding set of blocks.
      26             :  */
      27             : 
      28             : // self
      29             : //
      30             : #include    "snapdatabase/data/xml.h"
      31             : 
      32             : #include    "snapdatabase/data/convert.h"
      33             : #include    "snapdatabase/exception.h"
      34             : 
      35             : 
      36             : // libutf8 lib
      37             : //
      38             : #include    <libutf8/libutf8.h>
      39             : 
      40             : 
      41             : // snapdev lib
      42             : //
      43             : #include    <snapdev/not_reached.h>
      44             : 
      45             : 
      46             : // boost lib
      47             : //
      48             : #include    <boost/algorithm/string.hpp>
      49             : 
      50             : 
      51             : // C++ lib
      52             : //
      53             : #include    <fstream>
      54             : #include    <iostream>
      55             : 
      56             : 
      57             : // C lib
      58             : //
      59             : #include    <string.h>
      60             : 
      61             : 
      62             : // last include
      63             : //
      64             : #include    <snapdev/poison.h>
      65             : 
      66             : 
      67             : 
      68             : namespace snapdatabase
      69             : {
      70             : 
      71             : 
      72             : 
      73             : namespace
      74             : {
      75             : 
      76             : 
      77        1014 : bool is_alpha(char c)
      78             : {
      79         888 :     return (c >= 'a' && c <= 'z')
      80         126 :         || (c >= 'A' && c <= 'Z')
      81        1140 :         || c == '_';
      82             : }
      83             : 
      84             : 
      85         122 : bool is_digit(char c)
      86             : {
      87          72 :     return (c >= '0' && c <= '9')
      88         244 :         || c == '-';
      89             : }
      90             : 
      91             : 
      92          97 : bool is_space(char c)
      93             : {
      94             :     return c == ' '
      95          97 :         || c == '\t'
      96          97 :         || c == '\v'
      97          97 :         || c == '\f'
      98          97 :         || c == '\n'
      99         194 :         || c == '\r';
     100             : }
     101             : 
     102             : 
     103          46 : bool is_token(std::string const s)
     104             : {
     105          46 :     if(s.empty())
     106             :     {
     107           0 :         return false;
     108             :     }
     109             : 
     110          46 :     if(!is_alpha(s[0]))
     111             :     {
     112           0 :         return false;
     113             :     }
     114             : 
     115          46 :     std::string::size_type const max(s.length());
     116         334 :     for(std::string::size_type idx(1); idx < max; ++idx)
     117             :     {
     118         288 :         char const c(s[idx]);
     119         576 :         if(!is_alpha(c)
     120          15 :         && !is_digit(c)
     121         288 :         && c != '-')
     122             :         {
     123           0 :             return false;
     124             :         }
     125             :     }
     126          46 :     if(s[max - 1] == '-')
     127             :     {
     128           0 :         return false;
     129             :     }
     130             : 
     131          46 :     return true;
     132             : }
     133             : 
     134             : 
     135             : 
     136             : enum class token_t
     137             : {
     138             :     TOK_CLOSE_TAG,
     139             :     TOK_EMPTY_TAG,
     140             :     TOK_END_TAG,
     141             :     TOK_EOF,
     142             :     TOK_EQUAL,
     143             :     TOK_IDENTIFIER,
     144             :     TOK_OPEN_TAG,
     145             :     TOK_PROCESSOR,
     146             :     TOK_STRING,
     147             :     TOK_TEXT
     148             : };
     149             : 
     150             : 
     151           7 : class xml_parser
     152             : {
     153             : public:
     154             :                         xml_parser(std::string const & filename, xml_node::pointer_t & root);
     155             : 
     156             : private:
     157             :     void                read_xml(xml_node::pointer_t & root);
     158             :     token_t             get_token(bool parsing_attributes);
     159             :     void                unescape_entities();
     160             :     int                 getc();
     161             :     void                ungetc(int c);
     162             : 
     163             :     std::string         f_filename = std::string();
     164             :     std::ifstream       f_in;
     165             :     size_t              f_ungetc_pos = 0;
     166             :     int                 f_ungetc[4] = { '\0' };
     167             :     int                 f_line = 1;
     168             :     std::string         f_value = std::string();
     169             : };
     170             : 
     171             : 
     172           9 : xml_parser::xml_parser(std::string const & filename, xml_node::pointer_t & root)
     173             :     : f_filename(filename)
     174          11 :     , f_in(filename)
     175             : {
     176           9 :     if(!f_in.is_open())
     177             :     {
     178           0 :         int const e(errno);
     179           0 :         throw file_not_found(std::string("Could not open XML table file \"")
     180           0 :                            + filename
     181           0 :                            + "\": " + strerror(e) + ".");
     182             :     }
     183             : 
     184           9 :     read_xml(root);
     185           7 : }
     186             : 
     187             : 
     188             : /** \brief
     189             :  *
     190             :  * This function reads the XML but it does not verify the Schema format.
     191             :  * It does verify the XML syntax fairly strongly.
     192             :  *
     193             :  * \param[in] root  A reference to the root pointer where the results are saved.
     194             :  */
     195           9 : void xml_parser::read_xml(xml_node::pointer_t & root)
     196             : {
     197           9 :     token_t tok(get_token(false));
     198             : 
     199          22 :     auto skip_empty = [&]()
     200             :     {
     201          30 :         while(tok == token_t::TOK_TEXT)
     202             :         {
     203           8 :             boost::trim(f_value);
     204           4 :             if(!f_value.empty())
     205             :             {
     206             :                 throw unexpected_token(
     207             :                           "File \""
     208           0 :                         + f_filename
     209           0 :                         + "\" cannot include text data before the root tag.");
     210             :             }
     211           4 :             tok = get_token(false);
     212             :         }
     213          27 :     };
     214             : 
     215          46 :     auto read_tag_attributes = [&](xml_node::pointer_t & tag)
     216             :     {
     217             :         for(;;)
     218             :         {
     219         184 :             tok = get_token(true);
     220          46 :             if(tok == token_t::TOK_END_TAG
     221          14 :             || tok == token_t::TOK_EMPTY_TAG)
     222             :             {
     223          66 :                 return tok;
     224             :             }
     225          13 :             if(tok != token_t::TOK_IDENTIFIER)
     226             :             {
     227           0 :                 throw invalid_xml("Expected the end of the tag (>) or an attribute name.");
     228             :             }
     229          26 :             std::string const name(f_value);
     230          13 :             tok = get_token(true);
     231          13 :             if(tok != token_t::TOK_EQUAL)
     232             :             {
     233           0 :                 throw invalid_xml("Expected the '=' character between the attribute name and value.");
     234             :             }
     235          13 :             tok = get_token(true);
     236          13 :             if(tok != token_t::TOK_STRING)
     237             :             {
     238           0 :                 throw invalid_xml("Expected a value of the attribute after the '=' sign.");
     239             :             }
     240          13 :             if(!tag->attribute(name).empty())
     241             :             {
     242           0 :                 throw invalid_xml("Attribute \"" + name + "\" defined twice. We do not allow such.");
     243             :             }
     244          26 :             tag->set_attribute(name, f_value);
     245          13 :         }
     246           9 :     };
     247             : 
     248           9 :     skip_empty();
     249           9 :     if(tok == token_t::TOK_PROCESSOR)
     250             :     {
     251           4 :         tok = get_token(false);
     252             :     }
     253           9 :     skip_empty();
     254             : 
     255             :     // now we have to have the root tag
     256           9 :     if(tok != token_t::TOK_OPEN_TAG)
     257             :     {
     258             :         throw unexpected_token(
     259             :                   "File \""
     260           2 :                 + f_filename
     261           3 :                 + "\" cannot be empty or include anything other than a processor tag and comments before the root tag.");
     262             :     }
     263           8 :     root = std::make_shared<xml_node>(f_value);
     264           8 :     if(read_tag_attributes(root) == token_t::TOK_EMPTY_TAG)
     265             :     {
     266             :         throw unexpected_token(
     267             :                   "File \""
     268           2 :                 + f_filename
     269           3 :                 + "\" root tag cannot be an empty tag.");
     270             :     }
     271           7 :     tok = get_token(false);
     272             : 
     273           7 :     xml_node::pointer_t parent(root);
     274         149 :     while(tok != token_t::TOK_EOF)
     275             :     {
     276          78 :         switch(tok)
     277             :         {
     278          25 :         case token_t::TOK_OPEN_TAG:
     279             :             {
     280          50 :                 xml_node::pointer_t child(std::make_shared<xml_node>(f_value));
     281          25 :                 parent->append_child(child);
     282          25 :                 if(read_tag_attributes(child) == token_t::TOK_END_TAG)
     283             :                 {
     284          25 :                     parent = child;
     285          25 :                 }
     286             :             }
     287          25 :             break;
     288             : 
     289          32 :         case token_t::TOK_CLOSE_TAG:
     290          32 :             if(parent->tag_name() != f_value)
     291             :             {
     292             :                 throw unexpected_token(
     293             :                           "Unexpected token name \""
     294           0 :                         + f_value
     295           0 :                         + "\" in this closing tag. Expected \""
     296           0 :                         + parent->tag_name()
     297           0 :                         + "\" instead.");
     298             :             }
     299          32 :             parent = parent->parent();
     300          32 :             if(parent == nullptr)
     301             :             {
     302             :                 for(;;)
     303             :                 {
     304           7 :                     tok = get_token(false);
     305           7 :                     switch(tok)
     306             :                     {
     307           7 :                     case token_t::TOK_EOF:
     308             :                         // it worked, we're done
     309             :                         //
     310           7 :                         return;
     311             : 
     312           0 :                     case token_t::TOK_TEXT:
     313           0 :                         skip_empty();
     314           0 :                         break;
     315             : 
     316           0 :                     case token_t::TOK_PROCESSOR:
     317             :                         // completely ignore those
     318           0 :                         break;
     319             : 
     320           0 :                     default:
     321             :                         throw unexpected_token(
     322             :                                   "We reached the end of the XML file, but still found a token of type "
     323           0 :                                 + std::to_string(static_cast<int>(tok))
     324           0 :                                 + " instead of the end of the file.");
     325             : 
     326             :                     }
     327             :                 }
     328             :             }
     329          25 :             break;
     330             : 
     331          21 :         case token_t::TOK_TEXT:
     332          21 :             parent->append_text(f_value);
     333          21 :             break;
     334             : 
     335           0 :         case token_t::TOK_EOF:
     336             :         case token_t::TOK_EMPTY_TAG:
     337             :         case token_t::TOK_END_TAG:
     338             :         case token_t::TOK_EQUAL:
     339             :         case token_t::TOK_IDENTIFIER:
     340             :         case token_t::TOK_PROCESSOR:
     341             :         case token_t::TOK_STRING:
     342           0 :             throw snapdatabase_logic_error("Received an unexpected token in the switch handler.");
     343             : 
     344             :         }
     345          71 :         tok = get_token(false);
     346             :     }
     347             : }
     348             : 
     349             : 
     350         174 : token_t xml_parser::get_token(bool parsing_attributes)
     351             : {
     352         174 :     f_value.clear();
     353             : 
     354             :     for(;;)
     355             :     {
     356         186 :         int c(getc());
     357         186 :         switch(c)
     358             :         {
     359           8 :         case EOF:
     360           8 :             return token_t::TOK_EOF;
     361             : 
     362          13 :         case ' ':
     363             :         case '\t':
     364             :         case '\v':
     365             :         case '\f':
     366             :         case '\n':
     367          13 :             if(parsing_attributes)
     368             :             {
     369           8 :                 continue;
     370             :             }
     371           5 :             break;
     372             : 
     373          73 :         case '<':
     374          73 :             c = getc();
     375          73 :             switch(c)
     376             :             {
     377          72 :             case '?':
     378             :                 // we do not parse the processor entry, we do not care about
     379             :                 // it at the moment
     380             :                 for(;;)
     381             :                 {
     382         140 :                     c = getc();
     383          72 :                     if(c == EOF)
     384             :                     {
     385           0 :                         throw unexpected_eof("Found an unexpected sequence of character in a processor (\"<?...?>\") sequence.");
     386             :                     }
     387           0 :                     while(c == '?')
     388             :                     {
     389           4 :                         c = getc();
     390           4 :                         if(c == '>')
     391             :                         {
     392           4 :                             return token_t::TOK_PROCESSOR;
     393             :                         }
     394           0 :                         f_value += '?';
     395             :                     }
     396          68 :                     f_value += static_cast<char>(c);
     397             :                 }
     398             :                 snap::NOTREACHED();
     399             :                 return token_t::TOK_PROCESSOR;
     400             : 
     401           4 :             case '!':
     402           4 :                 c = getc();
     403           4 :                 if(is_alpha(c))
     404             :                 {
     405             :                     // of course, this may be anything other than an element but still something we don't support
     406             :                     //
     407           0 :                     throw invalid_xml("Found an element definition (such as an \"<!ELEMENT...>\" sequence, which is not supported.");
     408             :                 }
     409           4 :                 if(c == '[')
     410             :                 {
     411             :                     // <![CDATA[ ... or throw
     412             :                     //
     413           0 :                     char const * expected = "CDATA[";
     414           0 :                     for(int j(0); j < 6; ++j)
     415             :                     {
     416           0 :                         if(getc() != expected[j])
     417             :                         {
     418           0 :                             throw invalid_xml("Found an unexpected sequence of character in a \"<![CDATA[...\" sequence.");
     419             :                         }
     420             :                     }
     421             :                     for(;;)
     422             :                     {
     423           0 :                         c = getc();
     424           0 :                         if(c == EOF)
     425             :                         {
     426           0 :                             throw unexpected_eof("Found EOF while parsing a \"<![CDATA[...]]>\" sequence.");
     427             :                         }
     428           0 :                         if(c == ']')
     429             :                         {
     430           0 :                             c = getc();
     431           0 :                             if(c == ']')
     432             :                             {
     433           0 :                                 c = getc();
     434           0 :                                 while(c == ']')
     435             :                                 {
     436           0 :                                     f_value += ']';
     437           0 :                                     c = getc();
     438             :                                 }
     439           0 :                                 if(c == '>')
     440             :                                 {
     441             :                                     // this is just like some text
     442             :                                     // except we do not convert entities
     443             :                                     //
     444           0 :                                     return token_t::TOK_TEXT;
     445             :                                 }
     446           0 :                                 f_value += "]]";
     447           0 :                                 f_value += static_cast<char>(c);
     448             :                             }
     449             :                             else
     450             :                             {
     451           0 :                                 f_value += ']';
     452           0 :                                 f_value += static_cast<char>(c);
     453             :                             }
     454             :                         }
     455             :                         else
     456             :                         {
     457           0 :                             f_value += static_cast<char>(c);
     458             :                         }
     459             :                     }
     460           4 :                 }
     461           4 :                 if(c == '-')
     462             :                 {
     463           4 :                     c = getc();
     464           4 :                     if(c == '-')
     465             :                     {
     466           4 :                         bool found(false);
     467         162 :                         while(!found)
     468             :                         {
     469          79 :                             c = getc();
     470          79 :                             if(c == EOF)
     471             :                             {
     472           0 :                                 throw unexpected_eof("Found EOF while parsing a comment (\"<!--...-->\") sequence.");
     473             :                             }
     474          79 :                             if(c == '-')
     475             :                             {
     476           5 :                                 c = getc();
     477           5 :                                 while(c == '-')
     478             :                                 {
     479           4 :                                     c = getc();
     480           4 :                                     if(c == '>')
     481             :                                     {
     482           4 :                                         found = true;
     483           4 :                                         break;
     484             :                                     }
     485             :                                 }
     486             :                             }
     487             :                         }
     488           4 :                         continue;
     489             :                     }
     490             :                 }
     491             :                 throw invalid_token(
     492           0 :                           std::string("Character '")
     493           0 :                         + static_cast<char>(c)
     494           0 :                         + "' was not expected after a \"<!\" sequence.");
     495             : 
     496          32 :             case '/':
     497          32 :                 c = getc();
     498          32 :                 while(is_space(c))
     499             :                 {
     500           0 :                     c = getc();
     501             :                 }
     502          32 :                 if(!is_alpha(c))
     503             :                 {
     504           0 :                     if(c == EOF)
     505             :                     {
     506           0 :                         throw unexpected_eof("Expected a tag name after \"</\", not EOF.");
     507             :                     }
     508             :                     throw invalid_token(
     509           0 :                               std::string("Character '")
     510           0 :                             + static_cast<char>(c)
     511           0 :                             + "' is not valid for a tag name.");
     512             :                 }
     513             :                 for(;;)
     514             :                 {
     515         496 :                     f_value += static_cast<char>(c);
     516         264 :                     c = getc();
     517         528 :                     if(!is_alpha(c)
     518         264 :                     && !is_digit(c))
     519             :                     {
     520          32 :                         break;
     521             :                     }
     522             :                 }
     523          32 :                 while(is_space(c))
     524             :                 {
     525           0 :                     c = getc();
     526             :                 }
     527          32 :                 if(c != '>')
     528             :                 {
     529           0 :                     if(c == EOF)
     530             :                     {
     531           0 :                         throw unexpected_eof("Expected '>', not EOF.");
     532             :                     }
     533             :                     throw invalid_xml(
     534           0 :                               std::string("Found an unexpected '")
     535           0 :                             + static_cast<char>(c)
     536           0 :                             + "' in a closing tag, expected '>' instead.");
     537             :                 }
     538          32 :                 return token_t::TOK_CLOSE_TAG;
     539             : 
     540          33 :             }
     541             : 
     542             :             // in this case we need to read the name only, the attributes
     543             :             // will be read by the parser instead of the lexer
     544             :             //
     545          33 :             while(is_space(c))
     546             :             {
     547           0 :                 c = getc();
     548             :             }
     549          33 :             if(!is_alpha(c))
     550             :             {
     551           0 :                 if(c == EOF)
     552             :                 {
     553           0 :                     throw unexpected_eof("Expected a tag name after \"</\", not EOF.");
     554             :                 }
     555             :                 throw invalid_token(
     556           0 :                           std::string("Character '")
     557           0 :                         + static_cast<char>(c)
     558           0 :                         + "' is not valid for a tag name.");
     559             :             }
     560             :             for(;;)
     561             :             {
     562         505 :                 f_value += static_cast<char>(c);
     563         269 :                 c = getc();
     564         538 :                 if(!is_alpha(c)
     565          47 :                 && !is_digit(c)
     566         302 :                 && c != '-')
     567             :                 {
     568          33 :                     break;
     569             :                 }
     570             :             }
     571          33 :             if(isspace(c))
     572             :             {
     573           0 :                 do
     574             :                 {
     575           5 :                     c = getc();
     576             :                 }
     577           5 :                 while(isspace(c));
     578             :             }
     579          28 :             else if(c != '>' && c != '/')
     580             :             {
     581             :                 throw invalid_token(
     582           0 :                           std::string("Character '")
     583           0 :                         + static_cast<char>(c)
     584           0 :                         + "' is not valid right after a tag name.");
     585             :             }
     586          33 :             ungetc(c);
     587          33 :             return token_t::TOK_OPEN_TAG;
     588             : 
     589          32 :         case '>':
     590          32 :             if(parsing_attributes)
     591             :             {
     592          32 :                 return token_t::TOK_END_TAG;
     593             :             }
     594           0 :             break;
     595             : 
     596           1 :         case '/':
     597           1 :             if(parsing_attributes)
     598             :             {
     599           1 :                 c = getc();
     600           1 :                 if(c == '>')
     601             :                 {
     602           1 :                     return token_t::TOK_EMPTY_TAG;
     603             :                 }
     604           0 :                 ungetc(c);
     605           0 :                 c = '/';
     606             :             }
     607           0 :             break;
     608             : 
     609          13 :         case '=':
     610          13 :             if(parsing_attributes)
     611             :             {
     612          13 :                 return token_t::TOK_EQUAL;
     613             :             }
     614           0 :             break;
     615             : 
     616          13 :         case '"':
     617             :         case '\'':
     618          13 :             if(parsing_attributes)
     619             :             {
     620          13 :                 int quote(c);
     621             :                 for(;;)
     622             :                 {
     623         261 :                     c = getc();
     624         137 :                     if(c == quote)
     625             :                     {
     626          13 :                         unescape_entities();
     627          13 :                         return token_t::TOK_STRING;
     628             :                     }
     629         124 :                     if(c == '>')
     630             :                     {
     631           0 :                         throw invalid_token("Character '>' not expected inside a tag value. Please use \"&gt;\" instead.");
     632             :                     }
     633         124 :                     f_value += static_cast<char>(c);
     634             :                 }
     635           0 :             }
     636           0 :             break;
     637             : 
     638             :         }
     639             : 
     640          38 :         if(parsing_attributes
     641          38 :         && is_alpha(c))
     642             :         {
     643             :             for(;;)
     644             :             {
     645         117 :                 f_value += static_cast<char>(c);
     646          65 :                 c = getc();
     647         130 :                 if(!is_alpha(c)
     648          14 :                 && !is_digit(c)
     649          78 :                 && c != '-')
     650             :                 {
     651          13 :                     ungetc(c);
     652          13 :                     return token_t::TOK_IDENTIFIER;
     653             :                 }
     654             :             }
     655             :         }
     656             : 
     657             :         for(;;)
     658             :         {
     659         185 :             f_value += static_cast<char>(c);
     660         105 :             c = getc();
     661         105 :             if(c == '<'
     662          80 :             || c == EOF)
     663             :             {
     664          25 :                 ungetc(c);
     665          25 :                 unescape_entities();
     666          25 :                 return token_t::TOK_TEXT;
     667             :             }
     668             :         }
     669          12 :     }
     670             : }
     671             : 
     672             : 
     673          38 : void xml_parser::unescape_entities()
     674             : {
     675          38 :     for(std::string::size_type pos(0);;)
     676             :     {
     677          50 :         pos = f_value.find('&', pos);
     678          50 :         if(pos == std::string::npos)
     679             :         {
     680          76 :             break;
     681             :         }
     682          12 :         std::string::size_type end(f_value.find(';', pos + 1));
     683          12 :         if(end == std::string::npos)
     684             :         {
     685             :             // generate an error here?
     686             :             //
     687           0 :             break;
     688             :         }
     689          24 :         std::string name(f_value.substr(pos + 1, end - pos - 1));
     690          12 :         if(name == "amp")
     691             :         {
     692           1 :             f_value.replace(pos, end - pos + 1, 1, '&');
     693           1 :             ++pos;
     694             :         }
     695          11 :         else if(name == "quot")
     696             :         {
     697           4 :             f_value.replace(pos, end - pos + 1, 1, '"');
     698           4 :             ++pos;
     699             :         }
     700           7 :         else if(name == "lt")
     701             :         {
     702           1 :             f_value.replace(pos, end - pos + 1, 1, '<');
     703           1 :             ++pos;
     704             :         }
     705           6 :         else if(name == "gt")
     706             :         {
     707           2 :             f_value.replace(pos, end - pos + 1, 1, '>');
     708           2 :             ++pos;
     709             :         }
     710           4 :         else if(name == "apos")
     711             :         {
     712           1 :             f_value.replace(pos, end - pos + 1, 1, '\'');
     713           1 :             ++pos;
     714             :         }
     715           3 :         else if(name.empty())
     716             :         {
     717           0 :             throw invalid_entity("the name of an entity cannot be empty ('&;' is not valid XML).");
     718             :         }
     719           3 :         else if(name[0] == '#')
     720             :         {
     721           3 :             if(name.length() == 1)
     722             :             {
     723           0 :                 throw invalid_entity("a numeric entity must have a number ('&#; is not valid XML).");
     724             :             }
     725           6 :             if(name[1] == 'x'
     726           3 :             || name[1] == 'X')
     727             :             {
     728           2 :                 name[0] = '0';
     729             :             }
     730             :             else
     731             :             {
     732           1 :                 name[0] = ' ';
     733             :             }
     734             :             // TODO: enforce base 10 or 16
     735             :             //
     736           3 :             char32_t unicode(convert_to_int(name, 32));
     737           6 :             std::string const utf8(libutf8::to_u8string(unicode));
     738           3 :             f_value.replace(pos, end - pos + 1, utf8);
     739           3 :             pos += utf8.length();
     740             :         }
     741             :         else
     742             :         {
     743             :             throw invalid_entity(
     744             :                       "Unsupported entity ('&"
     745           0 :                     + name
     746           0 :                     + ";').");
     747             :         }
     748          12 :     }
     749          38 : }
     750             : 
     751             : 
     752        1309 : int xml_parser::getc()
     753             : {
     754        1309 :     if(f_ungetc_pos > 0)
     755             :     {
     756          71 :         --f_ungetc_pos;
     757             : //std::cerr << "re-getc() - '" << static_cast<char>(f_ungetc[f_ungetc_pos]) << "'\n";
     758          71 :         return f_ungetc[f_ungetc_pos];
     759             :     }
     760             : 
     761        1238 :     int c(f_in.get());
     762        1238 :     if(c == '\r')
     763             :     {
     764           0 :         ++f_line;
     765           0 :         c = f_in.get();
     766           0 :         if(c != '\n')
     767             :         {
     768           0 :             ungetc(c);
     769           0 :             c = '\n';
     770             :         }
     771             :     }
     772        1238 :     else if(c == '\n')
     773             :     {
     774          10 :         ++f_line;
     775             :     }
     776             : 
     777             : //if(c == EOF)
     778             : //{
     779             : //std::cerr << "getc() - 'EOF'\n";
     780             : //}
     781             : //else
     782             : //{
     783             : //std::cerr << "getc() - '" << static_cast<char>(c) << "'\n";
     784             : //}
     785        1238 :     return c;
     786             : }
     787             : 
     788             : 
     789          71 : void xml_parser::ungetc(int c)
     790             : {
     791          71 :     if(c != EOF)
     792             :     {
     793          71 :         if(f_ungetc_pos >= sizeof(f_ungetc) / sizeof(f_ungetc[0]))
     794             :         {
     795           0 :             throw snapdatabase_logic_error("Somehow the f_ungetc buffer was overflowed.");
     796             :         }
     797             : 
     798          71 :         f_ungetc[f_ungetc_pos] = c;
     799          71 :         ++f_ungetc_pos;
     800             :     }
     801          71 : }
     802             : 
     803             : 
     804             : 
     805             : } // empty namespace
     806             : 
     807             : 
     808             : 
     809          33 : xml_node::xml_node(std::string const & name)
     810          33 :     : f_name(name)
     811             : {
     812          33 :     if(!is_token(name))
     813             :     {
     814           0 :         throw invalid_token("\"" + name + "\" is not a valid token as a tag name.");
     815             :     }
     816          33 : }
     817             : 
     818             : 
     819         114 : std::string const & xml_node::tag_name() const
     820             : {
     821         114 :     return f_name;
     822             : }
     823             : 
     824             : 
     825          24 : std::string xml_node::text() const
     826             : {
     827          24 :     return f_text;
     828             : }
     829             : 
     830             : 
     831          21 : void xml_node::append_text(std::string const & text)
     832             : {
     833          21 :     f_text += text;
     834          21 : }
     835             : 
     836             : 
     837           5 : xml_node::attribute_map_t xml_node::all_attributes() const
     838             : {
     839           5 :     return f_attributes;
     840             : }
     841             : 
     842             : 
     843          39 : std::string xml_node::attribute(std::string const & name) const
     844             : {
     845          39 :     auto const it(f_attributes.find(name));
     846          39 :     if(it == f_attributes.end())
     847             :     {
     848          26 :         return std::string();
     849             :     }
     850          13 :     return it->second;
     851             : }
     852             : 
     853             : 
     854          13 : void xml_node::set_attribute(std::string const & name, std::string const & value)
     855             : {
     856          13 :     if(!is_token(name))
     857             :     {
     858           0 :         throw invalid_token("\"" + name + "\" is not a valid token as an attribute name.");
     859             :     }
     860          13 :     f_attributes[name] = value;
     861          13 : }
     862             : 
     863             : 
     864          25 : void xml_node::append_child(pointer_t n)
     865             : {
     866          50 :     if(n->f_next != nullptr
     867          50 :     || n->f_previous.lock() != nullptr)
     868             :     {
     869           0 :         throw node_already_in_tree("Somehow you are trying to add a child xml_node of a xml_node that was already added to a tree of nodes.");
     870             :     }
     871             : 
     872          50 :     auto l(last_child());
     873          25 :     if(l == nullptr)
     874             :     {
     875           7 :         f_child = n;
     876             :     }
     877             :     else
     878             :     {
     879          18 :         l->f_next = n;
     880          18 :         n->f_previous = l;
     881             :     }
     882             : 
     883          25 :     n->f_parent = shared_from_this();
     884          25 : }
     885             : 
     886             : 
     887          42 : xml_node::pointer_t xml_node::parent() const
     888             : {
     889          42 :     auto result(f_parent.lock());
     890          42 :     return result;
     891             : }
     892             : 
     893             : 
     894          16 : xml_node::pointer_t xml_node::first_child() const
     895             : {
     896          16 :     return f_child;
     897             : }
     898             : 
     899             : 
     900          35 : xml_node::pointer_t xml_node::last_child() const
     901             : {
     902          35 :     if(f_child == nullptr)
     903             :     {
     904          15 :         return xml_node::pointer_t();
     905             :     }
     906             : 
     907          40 :     pointer_t l(f_child);
     908         100 :     while(l->f_next != nullptr)
     909             :     {
     910          40 :         l = l->f_next;
     911             :     }
     912             : 
     913          20 :     return l;
     914             : }
     915             : 
     916             : 
     917          34 : xml_node::pointer_t xml_node::next() const
     918             : {
     919          34 :     return f_next;
     920             : }
     921             : 
     922             : 
     923          10 : xml_node::pointer_t xml_node::previous() const
     924             : {
     925          10 :     return f_previous.lock();
     926             : }
     927             : 
     928             : 
     929           0 : std::ostream & operator << (std::ostream & out, xml_node const & n)
     930             : {
     931           0 :     out << '<';
     932           0 :     out << n.tag_name();
     933           0 :     for(auto const & a : n.all_attributes())
     934             :     {
     935             :         out << a.first
     936             :             << "=\""
     937             :             << a.second
     938           0 :             << '"';
     939             :     }
     940           0 :     auto child(n.first_child());
     941           0 :     bool empty(child == nullptr);
     942           0 :     if(empty)
     943             :     {
     944           0 :         out << '/';
     945             :     }
     946           0 :     out << '>';
     947           0 :     if(!empty)
     948             :     {
     949           0 :         out << '\n';
     950           0 :         while(child != nullptr)
     951             :         {
     952           0 :             out << child;       // recursive call
     953           0 :             child = child->next();
     954             :         }
     955           0 :         out << '\n';
     956             :     }
     957           0 :     if(!n.text().empty())
     958             :     {
     959           0 :         out << n.text();
     960           0 :         if(!empty)
     961             :         {
     962           0 :             out << '\n';
     963             :         }
     964             :     }
     965           0 :     if(!empty)
     966             :     {
     967             :         out << "</"
     968           0 :             << n.tag_name()
     969           0 :             << '>';
     970             :     }
     971             : 
     972           0 :     return out;
     973             : }
     974             : 
     975             : 
     976             : 
     977             : 
     978          11 : xml::xml(std::string const & filename)
     979             : {
     980           9 :     xml_parser p(filename, f_root);
     981           7 : }
     982             : 
     983             : 
     984           7 : xml_node::pointer_t xml::root()
     985             : {
     986           7 :     return f_root;
     987             : }
     988             : 
     989             : 
     990             : 
     991           6 : } // namespace snapdatabase
     992             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13