home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 275 / DPCS0111DVD.ISO / Toolkit / Audio-Visual / VirtualDub / Source / VirtualDub-1.9.10-src.7z / src / Lina / source / main.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2009-09-14  |  30.2 KB  |  1,059 lines

  1. //    Lina - HTML compiler for VirtualDub help system
  2. //    Copyright (C) 1998-2003 Avery Lee
  3. //
  4. //    This program is free software; you can redistribute it and/or modify
  5. //    it under the terms of the GNU General Public License as published by
  6. //    the Free Software Foundation; either version 2 of the License, or
  7. //    (at your option) any later version.
  8. //
  9. //    This program is distributed in the hope that it will be useful,
  10. //    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. //    GNU General Public License for more details.
  13. //
  14. //    You should have received a copy of the GNU General Public License
  15. //    along with this program; if not, write to the Free Software
  16. //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17.  
  18. #pragma warning(disable: 4786)
  19.  
  20. #include <sys/stat.h>
  21. #include <direct.h>
  22. #include <stdio.h>
  23. #include <stdarg.h>
  24. #include <ctype.h>
  25. #include <string>
  26. #include <list>
  27. #include <map>
  28. #include <set>
  29. #include <vector>
  30. #include <utility>
  31. #include "document.h"
  32. #include "parser.h"
  33.  
  34. ///////////////////////////////////////////////////////////////////////////
  35.  
  36. std::list<std::pair<std::string, bool> > g_truncateURLs;
  37. std::list<std::string> g_htmlHelpFiles;
  38.  
  39. ///////////////////////////////////////////////////////////////////////////
  40.  
  41. struct Context {
  42.     TreeDocument *mpDocument;
  43.     std::list<const TreeNode *> stack;
  44.     std::list<const TreeNode *> invocation_stack;
  45.     std::list<TreeNode *> construction_stack;
  46.     int pre_count;
  47.     int cdata_count;
  48.     bool eat_next_space;
  49.     bool holding_space;
  50.  
  51.     Context() : pre_count(0), cdata_count(0), eat_next_space(true), holding_space(false) {}
  52.  
  53.     const TreeNode *find_tag(std::string name) {
  54.         std::list<const TreeNode *>::reverse_iterator it(invocation_stack.rbegin()), itEnd(invocation_stack.rend());
  55.         const TreeNode *t = NULL;
  56.         
  57.         for(; it!=itEnd; ++it) {
  58.             t = (*it)->Child(name);
  59.             if (t)
  60.                 break;
  61.             if (!name.empty() && name[0]=='/')
  62.                 break;
  63.         }
  64.  
  65.         return t;
  66.     }
  67. };
  68.  
  69. void output_tag(Context& ctx, std::string *out, const TreeNode& tag);
  70. void output_tag_contents(Context& ctx, std::string *out, const TreeNode& tag);
  71.  
  72. //////////////////////////////////////////////////////////////
  73.  
  74. std::string g_outputDir;
  75.  
  76. typedef std::map<std::string, std::string> tFileCopies;
  77. tFileCopies g_fileCopies;
  78.  
  79. //////////////////////////////////////////////////////////////
  80.  
  81. void error(const Context& ctx, const char *format, ...) {
  82.     va_list val;
  83.  
  84.     std::list<const TreeNode *>::const_reverse_iterator it(ctx.stack.rbegin()), itEnd(ctx.stack.rend());
  85.  
  86.     printf("%s(%d): Error! ", (*it)->mpLocation->mName.c_str(), (*it)->mLineno);
  87.  
  88.     va_start(val, format);
  89.     vprintf(format, val);
  90.     va_end(val);
  91.     putchar('\n');
  92.  
  93.     int indent = 3;
  94.     for(++it; it!=itEnd; ++it) {
  95.         const TreeNode& tag = **it;
  96.         printf("%*c%s(%d): while processing tag <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  97.         indent += 3;
  98.     }
  99.  
  100.     indent = 3;
  101.     for(it=ctx.invocation_stack.rbegin(), itEnd=ctx.invocation_stack.rend(); it!=itEnd; ++it) {
  102.         const TreeNode& tag = **it;
  103.         printf("%*c%s(%d): while invoked from tag <%s> (%d children)\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str(), tag.mChildren.size());
  104.         indent += 3;
  105.     }
  106.  
  107.     exit(10);
  108. }
  109.  
  110. //////////////////////////////////////////////////////////////
  111.  
  112. std::string create_output_filename(const std::string& name) {
  113.     std::string filename(g_outputDir);
  114.  
  115.     if (!filename.empty()) {
  116.         char c = filename[filename.size()-1];
  117.         if (c != '/' && c != '\\')
  118.             filename += '/';
  119.     }
  120.  
  121.     filename += name;
  122.  
  123.     return filename;
  124. }
  125.  
  126. void construct_path(const std::string& dstfile) {
  127.     int idx = -1;
  128.  
  129.     for(;;) {
  130.         int pos = dstfile.find_first_of("\\/", idx+1);
  131.  
  132.         if (pos == std::string::npos)
  133.             break;
  134.  
  135.         std::string partialpath(dstfile.substr(0, pos));
  136.         struct _stat buffer;
  137.  
  138.         if (-1 == _stat(partialpath.c_str(), &buffer)) {
  139.             printf("creating: %s\n", partialpath.c_str());
  140.             _mkdir(partialpath.c_str());
  141.         }
  142.  
  143.         idx = pos;
  144.     }
  145. }
  146.  
  147. void copy_file(const std::string& dstfile, const std::string& srcfile) {
  148.     printf("copying: %s -> %s\n", srcfile.c_str(), dstfile.c_str());
  149.  
  150.     FILE *fs = fopen(srcfile.c_str(), "rb");
  151.  
  152.     if (!fs)
  153.         error("couldn't open source file \"%s\"", srcfile.c_str());
  154.  
  155.     std::string filename(g_outputDir);
  156.  
  157.     if (!filename.empty()) {
  158.         char c = filename[filename.size()-1];
  159.         if (c != '/' && c != '\\')
  160.             filename += '/';
  161.     }
  162.  
  163.     filename += dstfile;
  164.  
  165.     construct_path(filename);
  166.  
  167.     FILE *fd = fopen(filename.c_str(), "wb");
  168.     if (!fd)
  169.         error("couldn't create \"%s\"", filename.c_str());
  170.  
  171.     fseek(fs, 0, SEEK_END);
  172.     std::vector<char> data(ftell(fs));
  173.     fseek(fs, 0, SEEK_SET);
  174.     if (1 != fread(&data.front(), data.size(), 1, fs))
  175.         error("couldn't read from \"%s\"", srcfile.c_str());
  176.     fclose(fs);
  177.  
  178.     if (1 != fwrite(&data.front(), data.size(), 1, fd) || fclose(fd))
  179.         error("couldn't write to \"%s\"", dstfile.c_str());
  180. }
  181.  
  182. bool is_true(const std::string& name) {
  183.     return name.empty() || name[0]=='y' || name[0]=='Y';
  184. }
  185.  
  186. void dump_parse_tree(const TreeNode& tag, int indent = 0) {
  187.     if (tag.mbIsText) {
  188.     } else if (tag.mChildren.empty()) {
  189.         printf("%*c<%s/>\n", indent, ' ', tag.mName.c_str());
  190.     } else {
  191.         printf("%*c<%s>\n", indent, ' ', tag.mName.c_str());
  192.  
  193.         std::list<TreeNode *>::const_iterator it(tag.mChildren.begin()), itEnd(tag.mChildren.end());
  194.         for(; it!=itEnd; ++it) {
  195.             dump_parse_tree(**it, indent+3);
  196.         }
  197.  
  198.         printf("%*c</%s>\n", indent, ' ', tag.mName.c_str());
  199.     }
  200. }
  201.  
  202. ////////////////////////////////////////////////////////////////////////////////
  203.  
  204. void output_tag_attributes(std::string& out, const TreeNode& tag) {
  205.     std::list<TreeAttribute>::const_iterator itAtt(tag.mAttribs.begin()), itAttEnd(tag.mAttribs.end());
  206.     bool is_anchor = (tag.mName == "a");
  207.     
  208.     for(; itAtt!=itAttEnd; ++itAtt) {
  209.         const TreeAttribute& att = *itAtt;
  210.  
  211.         out += ' ';
  212.         out += att.mName;
  213.  
  214.         if (!att.mbNoValue) {
  215.             std::string::const_iterator its(att.mValue.begin()), itsEnd(att.mValue.end());
  216.             for(;its!=itsEnd; ++its)
  217.                 if (!issafevaluechar(*its))
  218.                     break;
  219.  
  220.             std::string value(att.mValue);
  221.  
  222.             if (is_anchor && att.mName == "href") {
  223.                 std::list<std::pair<std::string, bool> >::const_iterator it(g_truncateURLs.begin()), itEnd(g_truncateURLs.end());
  224.  
  225.                 for(; it!=itEnd; ++it) {
  226.                     const std::pair<std::string, bool>& entry = *it;
  227.  
  228.                     if (value.length() >= entry.first.length() && !value.compare(0, entry.first.length(), entry.first)) {
  229.                         if (entry.second) {
  230.                             int l = value.length();
  231.  
  232.                             while(l>0) {
  233.                                 char c = value[--l];
  234.  
  235.                                 if (c == '/' || c == ':')
  236.                                     break;
  237.                                 if (c == '.') {
  238.                                     if (value.substr(l+1, std::string::npos) == "html")
  239.                                         value.erase(l, std::string::npos);
  240.                                     break;
  241.                                 }
  242.                             }
  243.                             printf("truncated link: %s\n", value.c_str());
  244.                         }
  245.                         break;
  246.                     }
  247.                 }
  248.             }
  249.  
  250.             if (att.mValue.empty() || its!=itsEnd) {
  251.                 out += "=\"";
  252.                 out += value;
  253.                 out += '"';
  254.             } else {
  255.                 out += '=';
  256.                 out += value;
  257.             }
  258.         }
  259.     }
  260. }
  261.  
  262. void output_tag_contents(Context& ctx, std::string *out, const TreeNode& tag) {
  263.     static int recursion_depth = 0;
  264.  
  265.     ++recursion_depth;
  266.  
  267.     if (recursion_depth > 64)
  268.         error(ctx, "recursion exceeded limits");
  269.  
  270.     std::list<TreeNode *>::const_iterator it(tag.mChildren.begin()), itEnd(tag.mChildren.end());
  271.     for(; it!=itEnd; ++it) {
  272.         output_tag(ctx, out, **it);
  273.     }
  274.  
  275.     --recursion_depth;
  276. }
  277.  
  278. void output_standard_tag(Context& ctx, std::string *out, const TreeNode& tag) {
  279.     if (!tag.mbIsText && tag.mName == "pre")
  280.         ++ctx.pre_count;
  281.  
  282.     if (out && (tag.mbIsControl || !tag.mbIsText)) {
  283.         if (ctx.holding_space && ctx.cdata_count) {
  284.             if (!ctx.eat_next_space) {
  285.                 *out += ' ';
  286.             }
  287.             ctx.eat_next_space = false;
  288.             ctx.holding_space = false;
  289.         }
  290.     }
  291.  
  292.     if (!ctx.construction_stack.empty()) {
  293.         TreeNode *new_tag = ctx.mpDocument->AllocNode();
  294.  
  295.         new_tag->mpLocation        = tag.mpLocation;
  296.         new_tag->mLineno        = tag.mLineno;
  297.         new_tag->mName            = tag.mName;
  298.         new_tag->mAttribs        = tag.mAttribs;
  299.         new_tag->mbIsText        = tag.mbIsText;
  300.         new_tag->mbIsControl    = tag.mbIsControl;
  301.  
  302.         ctx.construction_stack.back()->mChildren.push_back(new_tag);
  303.         ctx.construction_stack.push_back(new_tag);
  304.  
  305.         output_tag_contents(ctx, out, tag);
  306.  
  307.         ctx.construction_stack.pop_back();
  308.     } else if (tag.mbIsText) {
  309.         if (out) {
  310.             if (tag.mbIsControl) {
  311.                 *out += tag.mName;
  312.             } else if (ctx.cdata_count) {
  313.                 if (ctx.pre_count) {
  314.                     *out += tag.mName;
  315.                 } else {
  316.                     std::string::const_iterator it(tag.mName.begin()), itEnd(tag.mName.end());
  317.  
  318.                     for(; it!=itEnd; ++it) {
  319.                         const char c = *it;
  320.  
  321.                         if (isspace(c)) {
  322.                             ctx.holding_space = true;
  323.                         } else {
  324.                             if (ctx.eat_next_space)
  325.                                 ctx.eat_next_space = false;
  326.                             else if (ctx.holding_space)
  327.                                 *out += ' ';
  328.  
  329.                             ctx.holding_space = false;
  330.                             *out += c;
  331.                         }
  332.                     }
  333.                 }
  334.             } else {
  335.                 std::string::const_iterator it(tag.mName.begin()), itEnd(tag.mName.end());
  336.  
  337.                 for(; it!=itEnd; ++it) {
  338.                     const char c = *it;
  339.  
  340.                     if (!isspace(c))
  341.                         error(ctx, "inline text not allowed");
  342.                 }
  343.             }
  344.         }
  345.     } else {
  346.         bool cdata = tag.SupportsCDATA();
  347.  
  348.         if (cdata) {
  349.             if (!ctx.cdata_count) {
  350.                 ctx.holding_space = false;
  351.                 ctx.eat_next_space = true;
  352.             }
  353.             ++ctx.cdata_count;
  354.         }
  355.  
  356.         if (!out) {
  357.             output_tag_contents(ctx, out, tag);
  358.         } else if (tag.mChildren.empty()) {
  359.             *out += '<';
  360.             *out += tag.mName;
  361.             output_tag_attributes(*out, tag);
  362.             *out += '>';
  363.         } else {
  364.             *out += '<';
  365.             *out += tag.mName;
  366.             output_tag_attributes(*out, tag);
  367.             *out += '>';
  368.  
  369.             output_tag_contents(ctx, out, tag);
  370.  
  371.             *out += "</";
  372.             *out += tag.mName;
  373.             *out += '>';
  374.         }
  375.  
  376.         if (cdata)
  377.             --ctx.cdata_count;
  378.     }
  379.     if (!tag.mbIsText && tag.mName == "pre")
  380.         --ctx.pre_count;
  381. }
  382.  
  383. std::string HTMLize(const std::string& s) {
  384.     std::string::const_iterator it(s.begin()), itEnd(s.end());
  385.     std::string t;
  386.  
  387.     for(; it!=itEnd; ++it) {
  388.         char c = *it;
  389.  
  390.         switch(c) {
  391.         case '"':    t.append("""); break;
  392.         case '<':    t.append("<"); break;
  393.         case '>':    t.append(">"); break;
  394.         case '&':    t.append("&"); break;
  395.         default:    t += c; break;
  396.         }
  397.     }
  398.  
  399.     return t;
  400. }
  401.  
  402. void output_source_tags(Context& ctx, std::string *out, const TreeNode& tag) {
  403.     std::string s;
  404.  
  405.     if (tag.mbIsText)
  406.         s = tag.mName;
  407.     else if (tag.mChildren.empty()) {
  408.         s = '<';
  409.         s += tag.mName;
  410.         output_tag_attributes(s, tag);
  411.         s += '>';
  412.     } else {
  413.         s = '<';
  414.         s += tag.mName;
  415.         output_tag_attributes(s, tag);
  416.         s += '>';
  417.  
  418.         out->append(HTMLize(s));
  419.  
  420.         out->append("<ul marker=none>");
  421.  
  422.         std::list<TreeNode *>::const_iterator itBegin(tag.mChildren.begin()), it(itBegin), itEnd(tag.mChildren.end());
  423.         for(; it!=itEnd; ++it) {
  424.         out->append("<li>");
  425.             output_source_tags(ctx, out, **it);
  426.         out->append("</li>");
  427.         }
  428.  
  429.         out->append("</ul>");
  430.  
  431.         s = "</";
  432.         s += tag.mName;
  433.         s += '>';
  434.     }
  435.  
  436.     out->append(HTMLize(s));
  437.  
  438.     if (!tag.mbIsText)
  439.         out->append("<br>");
  440. }
  441.  
  442. void dump_stack(Context& ctx) {
  443.     std::list<const TreeNode *>::reverse_iterator it(ctx.stack.rbegin()), itEnd(ctx.stack.rend());
  444.  
  445.     printf("Current execution stack:\n");
  446.     int indent = 3;
  447.     for(++it; it!=itEnd; ++it) {
  448.         const TreeNode& tag = **it;
  449.         printf("%*c%s(%d): processing <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  450.         indent += 3;
  451.     }
  452.  
  453.     indent = 3;
  454.     std::list<TreeNode *>::reverse_iterator it2(ctx.construction_stack.rbegin()), it2End(ctx.construction_stack.rend());
  455.     for(; it2!=it2End; ++it2) {
  456.         const TreeNode& tag = **it2;
  457.         printf("%*c%s(%d): while creating tag <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  458.         indent += 3;
  459.     }
  460.  
  461.     indent = 3;
  462.     for(it=ctx.invocation_stack.rbegin(), itEnd=ctx.invocation_stack.rend(); it!=itEnd; ++it) {
  463.         const TreeNode& tag = **it;
  464.         printf("%*c%s(%d): while invoked from tag <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  465.         indent += 3;
  466.     }
  467. }
  468.  
  469. void output_toc_children(FILE *f, const TreeNode& node);
  470.  
  471. void output_toc_node(FILE *f, const TreeNode& node) {
  472.     if (node.mName != "tocnode")
  473.         error("Invalid node <%s> found during HTML help TOC generation", node.mName.c_str());
  474.  
  475.     const TreeAttribute *attrib = node.Attrib("name");
  476.  
  477.     if (!attrib || attrib->mbNoValue)
  478.         error("<tocnode> must have NAME attribute");
  479.  
  480.     const TreeAttribute *target = node.Attrib("target");
  481.  
  482.     fputs("<LI><OBJECT type=\"text/sitemap\">\n", f);
  483.     fprintf(f, "<param name=\"Name\" value=\"%s\">\n", HTMLize(attrib->mValue).c_str());
  484.     if (target && !target->mbNoValue)
  485.         fprintf(f, "<param name=\"Local\" value=\"%s\">\n", HTMLize(target->mValue).c_str());
  486.     fputs("</OBJECT>\n", f);
  487.  
  488.     output_toc_children(f, node);
  489. }
  490.  
  491. void output_toc_children(FILE *f, const TreeNode& node) {
  492.     bool nodesFound = false;
  493.  
  494.     TreeNode::Children::const_iterator it(node.mChildren.begin()), itEnd(node.mChildren.end());
  495.     for(; it!=itEnd; ++it) {
  496.         const TreeNode& childNode = **it;
  497.  
  498.         if (!childNode.mbIsText) {
  499.             if (!nodesFound) {
  500.                 nodesFound = true;
  501.                 fputs("<UL>\n", f);
  502.             }
  503.  
  504.             output_toc_node(f, childNode);
  505.         }
  506.     }
  507.  
  508.     if (nodesFound)
  509.         fputs("</UL>\n", f);
  510. }
  511.  
  512. void output_toc(FILE *f, const TreeNode& root) {
  513.     fputs(    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
  514.             "<HTML>\n"
  515.             "<HEAD>\n"
  516.             "<!-- Sitemap 1.0 -->\n"
  517.             "</HEAD><BODY>\n"
  518.             "<OBJECT type=\"text/site properties\">\n"
  519.             "\t<param name=\"ImageType\" value=\"Folder\">\n"
  520.             "</OBJECT>\n"
  521.         , f);
  522.  
  523.     output_toc_children(f, root);
  524.  
  525.     fputs(    "</BODY>\n"
  526.             "</HTML>\n"
  527.         , f);
  528. }
  529.  
  530. void output_special_tag(Context& ctx, std::string *out, const TreeNode& tag) {
  531.     if (tag.mName == "lina:fireball") {
  532.         const TreeAttribute *a1 = tag.Attrib("src");
  533.         const TreeAttribute *a2 = tag.Attrib("dst");
  534.  
  535.         if (!a1 || !a2)
  536.             error(ctx, "<lina:fireball> requires SRC and DST attributes");
  537.  
  538.         g_fileCopies[a2->mValue] = a1->mValue;
  539.     } else if (tag.mName == "lina:write") {
  540.         const TreeAttribute *a = tag.Attrib("file");
  541.  
  542.         if (!a)
  543.             error(ctx, "<lina:write> must specify FILE");
  544.  
  545.         std::string s;
  546.  
  547.         std::list<TreeNode *> tempStack;
  548.         ctx.construction_stack.swap(tempStack);
  549.         int cdataCount = ctx.cdata_count;
  550.         int preCount = ctx.pre_count;
  551.         ctx.cdata_count = 0;
  552.         ctx.pre_count = 0;
  553.         bool bHoldingSpace = ctx.holding_space;
  554.         bool bEatNextSpace = ctx.eat_next_space;
  555.         ctx.holding_space = false;
  556.         ctx.eat_next_space = true;
  557.         output_tag_contents(ctx, &s, tag);
  558.         ctx.holding_space = bHoldingSpace;
  559.         ctx.eat_next_space = bEatNextSpace;
  560.         ctx.pre_count = cdataCount;
  561.         ctx.cdata_count = preCount;
  562.         ctx.construction_stack.swap(tempStack);
  563.  
  564.         std::string filename(create_output_filename(a->mValue));
  565.  
  566.         FILE *f = fopen(filename.c_str(), "wb");
  567.         if (!f)
  568.             error(ctx, "couldn't create \"%s\"", a->mValue.c_str());
  569.         fwrite(s.data(), s.length(), 1, f);
  570.         fclose(f);
  571.  
  572.         printf("created file: %s\n", a->mValue.c_str());
  573.     } else if (tag.mName == "lina:body") {
  574.  
  575. //        printf("outputting:\n");
  576. //        dump_parse_tree(*ctx.invocation_stack.back(), 4);
  577.  
  578.         output_tag_contents(ctx, out, *ctx.invocation_stack.back());
  579.     } else if (tag.mName == "lina:tag") {
  580.         const TreeAttribute *a = tag.Attrib("name");
  581.         if (!a)
  582.             error(ctx, "<lina:tag> must have NAME attribute");
  583.  
  584.         ctx.construction_stack.push_back(ctx.mpDocument->AllocNode());
  585.         TreeNode *new_tag = ctx.construction_stack.back();
  586.  
  587.         new_tag->mpLocation = tag.mpLocation;
  588.         new_tag->mLineno = tag.mLineno;
  589.         new_tag->mName = a->mValue;
  590.         new_tag->mbIsText = false;
  591.         new_tag->mbIsControl = false;
  592.  
  593.         // compatibility
  594.         if (!new_tag->mName.compare(0, 2, "w:"))
  595.             new_tag->mName.replace(0, 2, "lina:");
  596.  
  597.         output_tag_contents(ctx, NULL, tag);
  598.  
  599.         ctx.construction_stack.pop_back();
  600.         output_tag(ctx, out, *new_tag);
  601.     } else if (tag.mName == "lina:arg") {
  602.         if (!out && ctx.construction_stack.empty())
  603.             error(ctx, "<lina:arg> can only be used in an output context");
  604.         const TreeAttribute *a = tag.Attrib("name");
  605.         if (!a)
  606.             error(ctx, "<lina:arg> must have NAME attribute");
  607.  
  608.         if (ctx.invocation_stack.empty())
  609.             error(ctx, "<lina:arg> can only be used during macro expansion");
  610.  
  611.         std::list<const TreeNode *>::const_iterator it(ctx.invocation_stack.end());
  612.         --it;
  613.  
  614.         int levels = 1;
  615.         const char *name = a->mValue.c_str();
  616.         while(*name == '^') {
  617.             ++levels;
  618.             ++name;
  619.  
  620.             if (it == ctx.invocation_stack.begin())
  621.                 error(ctx, "Number of up-scope markers in name exceeds macro nesting level");
  622.  
  623.             --it;
  624.         }
  625.  
  626.         const TreeNode& macrotag = **it;
  627.         const TreeAttribute *a2 = macrotag.Attrib(name);
  628.         if (!a2)
  629.             error(ctx, "macro invocation <%s> does not have an attribute \"%s\"", macrotag.mName.c_str(), name);
  630.  
  631.         if (out) {
  632.             *out += a2->mValue;
  633.  
  634.             ctx.eat_next_space = false;
  635.             ctx.holding_space = false;
  636.         } else {
  637.             TreeNode *t = ctx.mpDocument->AllocNode();
  638.  
  639.             t->mpLocation = tag.mpLocation;
  640.             t->mLineno = tag.mLineno;
  641.             t->mbIsControl = false;
  642.             t->mbIsText = true;
  643.             t->mName = a2->mValue;
  644.  
  645.             ctx.construction_stack.back()->mChildren.push_back(t);
  646.         }
  647.     } else if (tag.mName == "lina:if-arg") {
  648.         const TreeAttribute *a = tag.Attrib("name");
  649.         if (!a)
  650.             error(ctx, "<lina:if-arg> must have NAME attribute");
  651.  
  652.         if (ctx.invocation_stack.empty())
  653.             error(ctx, "<lina:if-arg> can only be used during macro expansion");
  654.  
  655.         const TreeNode& macrotag = *ctx.invocation_stack.back();
  656.         const TreeAttribute *a2 = macrotag.Attrib(a->mValue);
  657.         if (a2)
  658.             output_tag_contents(ctx, out, tag);
  659.     } else if (tag.mName == "lina:if-not-arg") {
  660.         const TreeAttribute *a = tag.Attrib("name");
  661.         if (!a)
  662.             error(ctx, "<lina:if-not-arg> must have NAME attribute");
  663.  
  664.         if (ctx.invocation_stack.empty())
  665.             error(ctx, "<lina:if-not-arg> can only be used during macro expansion");
  666.  
  667.         const TreeNode& macrotag = *ctx.invocation_stack.back();
  668.         const TreeAttribute *a2 = macrotag.Attrib(a->mValue);
  669.         if (!a2)
  670.             output_tag_contents(ctx, out, tag);
  671.     } else if (tag.mName == "lina:attrib") {
  672.         if (ctx.construction_stack.empty())
  673.             error(ctx, "<lina:attrib> can only be used in a <lina:tag> element");
  674.  
  675.         const TreeAttribute *a = tag.Attrib("name");
  676.         if (!a)
  677.             error(ctx, "<lina:attrib> must have NAME attribute");
  678.  
  679.         std::string s;
  680.         std::list<TreeNode *> tempStack;
  681.         ctx.construction_stack.swap(tempStack);
  682.         ++ctx.cdata_count;
  683.         ++ctx.pre_count;
  684.         bool bHoldingSpace = ctx.holding_space;
  685.         bool bEatNextSpace = ctx.eat_next_space;
  686.         ctx.holding_space = false;
  687.         ctx.eat_next_space = true;
  688.         output_tag_contents(ctx, &s, tag);
  689.         ctx.holding_space = bHoldingSpace;
  690.         ctx.eat_next_space = bEatNextSpace;
  691.         --ctx.pre_count;
  692.         --ctx.cdata_count;
  693.         ctx.construction_stack.swap(tempStack);
  694.  
  695.         TreeNode *t = ctx.construction_stack.back();
  696.         TreeAttribute new_att;
  697.         if (tag.Attrib("novalue")) {
  698.             new_att.mbNoValue = true;
  699.         } else {
  700.             new_att.mbNoValue = false;
  701.             new_att.mValue = s;
  702.         }
  703.         new_att.mName = a->mValue;
  704.         t->mAttribs.push_back(new_att);
  705.     } else if (tag.mName == "lina:pull") {
  706.         if (ctx.invocation_stack.empty())
  707.             error(ctx, "<lina:pull> can only be used during macro expansion");
  708.  
  709.         const TreeAttribute *a = tag.Attrib("name");
  710.         if (!a)
  711.             error(ctx, "<lina:pull> must have NAME attribute");
  712.  
  713.         const TreeNode *t = ctx.find_tag(a->mValue);
  714.         
  715.         if (!t)
  716.             error(ctx, "cannot find tag <%s> referenced in <lina:pull>", a->mValue.c_str());
  717.  
  718.         output_tag_contents(ctx, out, *t);        
  719.     } else if (tag.mName == "lina:for-each") {
  720.         const TreeAttribute *a = tag.Attrib("name");
  721.         if (!a)
  722.             error(ctx, "<lina:for-each> must have NAME attribute");
  723.         
  724.         std::string node_name;
  725.         const TreeNode *parent;
  726.         if (ctx.invocation_stack.empty()) {
  727.             if (!a->mValue.empty() && a->mValue[0] == '/')
  728.                 parent = ctx.mpDocument->mpRoot->ResolvePath(a->mValue.substr(1), node_name);
  729.             else
  730.                 error(ctx, "path must be absolute if not in macro context");
  731.         } else {
  732.             std::list<const TreeNode *>::reverse_iterator it(ctx.invocation_stack.rbegin()), itEnd(ctx.invocation_stack.rend());
  733.             
  734.             for(; it!=itEnd; ++it) {
  735.                 parent = (*it)->ResolvePath(a->mValue, node_name);
  736.                 if(parent)
  737.                     break;
  738.                 if (!a->mValue.empty() && a->mValue[0] == '/')
  739.                     break;
  740.             }
  741.         }
  742.  
  743.         if (!parent)
  744.             error(ctx, "cannot resolve path \"%s\"", a->mValue.c_str());
  745.  
  746.         std::list<TreeNode *>::const_iterator it2(parent->mChildren.begin()), it2End(parent->mChildren.end());
  747.  
  748.         ctx.invocation_stack.push_back(NULL);
  749.         for(; it2!=it2End; ++it2) {
  750.             if ((*it2)->mName == node_name) {
  751.                 ctx.invocation_stack.back() = *it2;
  752.                 output_tag_contents(ctx, out, tag);
  753.             }
  754.         }
  755.         ctx.invocation_stack.pop_back();
  756.     } else if (tag.mName == "lina:apply") {
  757.         const TreeAttribute *a = tag.Attrib("name");
  758.         if (!a)
  759.             error(ctx, "<lina:apply> must have NAME attribute");
  760.  
  761.         std::map<std::string, TreeNode *>::const_iterator it(ctx.mpDocument->mMacros.find(a->mValue));
  762.  
  763.         if (it == ctx.mpDocument->mMacros.end())
  764.             error(ctx, "macro \"%s\" undeclared", a->mValue.c_str());
  765.         
  766.         std::list<TreeNode *>::const_iterator it2(tag.mChildren.begin()), it2End(tag.mChildren.end());
  767.  
  768.         ctx.invocation_stack.push_back(NULL);
  769.         for(; it2!=it2End; ++it2) {
  770.             if (!(*it2)->mbIsText) {
  771.                 ctx.invocation_stack.back() = *it2;
  772.                 output_tag_contents(ctx, out, *(*it).second);
  773.             }
  774.         }
  775.         ctx.invocation_stack.pop_back();
  776.     } else if (tag.mName == "lina:if-present") {
  777.         if (ctx.invocation_stack.empty())
  778.             error(ctx, "<lina:if-present> can only be used during macro expansion");
  779.         const TreeAttribute *a = tag.Attrib("name");
  780.         if (!a)
  781.             error(ctx, "<lina:if-present> must have NAME attribute");
  782.  
  783.         const TreeNode *t = ctx.find_tag(a->mValue);
  784.         if (t)
  785.             output_tag_contents(ctx, out, tag);
  786.     } else if (tag.mName == "lina:if-not-present") {
  787.         if (ctx.invocation_stack.empty())
  788.             error(ctx, "<lina:if-not-present> can only be used during macro expansion");
  789.         const TreeAttribute *a = tag.Attrib("name");
  790.         if (!a)
  791.             error(ctx, "<lina:if-not-present> must have NAME attribute");
  792.  
  793.         const TreeNode *t = ctx.find_tag(a->mValue);
  794.         if (!t)
  795.             output_tag_contents(ctx, out, tag);
  796.     } else if (tag.mName == "lina:pre") {
  797.         ++ctx.pre_count;
  798.         ++ctx.cdata_count;
  799.         if (!out)
  800.             output_standard_tag(ctx, out, tag);
  801.         else {
  802.             output_tag_contents(ctx, out, tag);
  803.         }
  804.         --ctx.cdata_count;
  805.         --ctx.pre_count;
  806.     } else if (tag.mName == "lina:cdata") {
  807.         ++ctx.cdata_count;
  808.         if (!out)
  809.             output_standard_tag(ctx, out, tag);
  810.         else
  811.             output_tag_contents(ctx, out, tag);
  812.         --ctx.cdata_count;
  813.     } else if (tag.mName == "lina:delay") {
  814.         std::list<TreeNode *>::const_iterator it(tag.mChildren.begin()), itEnd(tag.mChildren.end());
  815.         for(; it!=itEnd; ++it) {
  816.             output_standard_tag(ctx, out, **it);
  817.         }
  818.     } else if (tag.mName == "lina:dump-stack") {
  819.         dump_stack(ctx);
  820.     } else if (tag.mName == "lina:replace") {
  821.         const TreeAttribute *a = tag.Attrib("from");
  822.         if (!a || a->mbNoValue)
  823.             error(ctx, "<lina:replace> must have FROM attribute");
  824.         const TreeAttribute *a2 = tag.Attrib("to");
  825.         if (!a2 || a2->mbNoValue)
  826.             error(ctx, "<lina:replace> must have TO attribute");
  827.  
  828.         const std::string& x = a->mValue;
  829.         const std::string& y = a2->mValue;
  830.  
  831.         std::string s, t;
  832.         std::string::size_type i = 0;
  833.  
  834.         output_tag_contents(ctx, &s, tag);
  835.  
  836.         for(;;) {
  837.             std::string::size_type j = s.find(x, i);
  838.             if (j != i)
  839.                 t.append(s, i, j-i);
  840.             if (j == std::string::npos)
  841.                 break;
  842.             t.append(y);
  843.             i = j + x.size();
  844.         }
  845.  
  846.         TreeNode *new_tag = ctx.mpDocument->AllocNode();
  847.  
  848.         new_tag->mpLocation = tag.mpLocation;
  849.         new_tag->mLineno = tag.mLineno;
  850.         new_tag->mbIsText = true;
  851.         new_tag->mbIsControl = false;
  852.         new_tag->mName = t;
  853.  
  854.         output_tag(ctx, out, *new_tag);
  855.     } else if (tag.mName == "lina:set-option") {
  856.         const TreeAttribute *a_name = tag.Attrib("name");
  857.         if (!a_name)
  858.             error(ctx, "<lina:set-option> must have NAME attribute");
  859.  
  860.         if (a_name->mValue == "link-truncate") {
  861.             const TreeAttribute *a_val = tag.Attrib("baseurl");
  862.             if (!a_val || a_val->mbNoValue)
  863.                 error(ctx, "option \"link-truncate\" requires BASEURL attribute");
  864.  
  865.             bool bTruncate = !tag.Attrib("notruncate");
  866.  
  867.             g_truncateURLs.push_back(std::make_pair(a_val->mValue, bTruncate));
  868.         } else if (a_name->mValue == "output-dir") {
  869.             const TreeAttribute *a_val = tag.Attrib("target");
  870.             if (!a_val || a_val->mbNoValue)
  871.                 error(ctx, "option \"output-dir\" requires TARGET attribute");
  872.  
  873.             g_outputDir = a_val->mValue;
  874.         } else if (a_name->mValue == "tag-info") {
  875.             const TreeAttribute *a_tagname = tag.Attrib("tag");
  876.             if (!a_tagname || a_tagname->mbNoValue)
  877.                 error(ctx, "option \"tag-info\" requires TAG attribute");
  878.  
  879.             const TreeAttribute *a_cdata = tag.Attrib("cdata");
  880.  
  881.             if (!a_cdata || a_cdata->mbNoValue)
  882.                 error(ctx, "option \"tag-info\" requires CDATA attribute");
  883.  
  884.             TreeNode::SetSupportsCDATA(a_tagname->mValue, is_true(a_cdata->mValue));
  885.         } else
  886.             error(ctx, "option \"%s\" unknown\n", a_name->mValue.c_str());
  887.  
  888.     } else if (tag.mName == "lina:data") {
  889.         // do nothing
  890.     } else if (tag.mName == "lina:source") {
  891.         if (out) {
  892.             std::list<TreeNode *>::const_iterator itBegin(tag.mChildren.begin()), it(itBegin), itEnd(tag.mChildren.end());
  893.             for(; it!=itEnd; ++it) {
  894.                 output_source_tags(ctx, out, **it);
  895.             }
  896.         }
  897.     } else if (tag.mName == "lina:htmlhelp-toc") {
  898.         const TreeAttribute *a_val = tag.Attrib("file");
  899.         if (!a_val || a_val->mbNoValue)
  900.             error(ctx, "<lina:htmlhelp-toc> requires FILE attribute");
  901.  
  902.         const std::string filename(create_output_filename(a_val->mValue));
  903.  
  904.         // build new tag with TOC contents
  905.         ctx.construction_stack.push_back(ctx.mpDocument->AllocNode());
  906.         TreeNode *new_tag = ctx.construction_stack.back();
  907.  
  908.         new_tag->mpLocation = tag.mpLocation;
  909.         new_tag->mLineno = tag.mLineno;
  910.         new_tag->mName = a_val->mValue;
  911.         new_tag->mbIsText = false;
  912.         new_tag->mbIsControl = false;
  913.  
  914.         output_tag_contents(ctx, NULL, tag);
  915.  
  916.         ctx.construction_stack.pop_back();
  917.         output_tag(ctx, out, *new_tag);
  918.  
  919.         FILE *f = fopen(filename.c_str(), "wb");
  920.         if (!f)
  921.             error(ctx, "couldn't create htmlhelp toc \"%s\"", a_val->mValue.c_str());
  922.         output_toc(f, *new_tag);
  923.         fclose(f);
  924.  
  925.     } else if (tag.mName == "lina:htmlhelp-project") {
  926.         const TreeAttribute *file_val = tag.Attrib("file");
  927.         if (!file_val || file_val->mbNoValue)
  928.             error(ctx, "<lina:htmlhelp-project> requires FILE attribute");
  929.  
  930.         const TreeAttribute *output_val = tag.Attrib("output");
  931.         if (!output_val || output_val->mbNoValue)
  932.             error(ctx, "<lina:htmlhelp-project> requires OUTPUT attribute");
  933.  
  934.         const TreeAttribute *toc_val = tag.Attrib("toc");
  935.         if (!toc_val || toc_val->mbNoValue)
  936.             error(ctx, "<lina:htmlhelp-project> requires TOC attribute");
  937.  
  938.         const TreeAttribute *title_val = tag.Attrib("title");
  939.         if (!title_val || title_val->mbNoValue)
  940.             error(ctx, "<lina:htmlhelp-project> requires TITLE attribute");
  941.  
  942.         const std::string filename(create_output_filename(file_val->mValue));
  943.  
  944.         FILE *f = fopen(filename.c_str(), "wb");
  945.         if (!f)
  946.             error(ctx, "couldn't create htmlhelp project \"%s\"", file_val->mValue.c_str());
  947.         fprintf(f,
  948.             "[OPTIONS]\n"
  949.             "Auto Index=Yes\n"
  950.             "Compatibility=1.1 or later\n"
  951.             "Compiled file=%s\n"
  952.             "Contents file=%s\n"
  953.             "Default topic=index.html\n"
  954.             "Display compile progress=no\n"
  955.             "Full-text search=Yes\n"
  956.             , output_val->mValue.c_str()
  957.             , toc_val->mValue.c_str()
  958.             );
  959.  
  960.  
  961.         const TreeAttribute *fullstop_val = tag.Attrib("fullstop");
  962.         if (fullstop_val && !fullstop_val->mbNoValue)
  963.             fprintf(f, "Full text search stop list file=%s\n", fullstop_val->mValue.c_str());
  964.  
  965.         fprintf(f,
  966.             "Language=0x0409 English (United States)\n"
  967.             "Title=%s\n"
  968.             "\n"
  969.             "[FILES]\n"
  970.             , title_val->mValue.c_str()
  971.             );
  972.  
  973.         std::list<std::string>::const_iterator it(g_htmlHelpFiles.begin()), itEnd(g_htmlHelpFiles.end());
  974.         for(; it!=itEnd; ++it) {
  975.             fprintf(f, "%s\n", (*it).c_str());
  976.         }
  977.  
  978.         fclose(f);        
  979.     } else if (tag.mName == "lina:htmlhelp-addfile") {
  980.         const TreeAttribute *file_val = tag.Attrib("file");
  981.         if (!file_val || file_val->mbNoValue)
  982.             error(ctx, "<lina:htmlhelp-addfile> requires FILE attribute");
  983.  
  984.         g_htmlHelpFiles.push_back(file_val->mValue);
  985.     } else {
  986.         std::string macroName(tag.mName, 5, std::string::npos);
  987.         std::map<std::string, TreeNode *>::const_iterator it = ctx.mpDocument->mMacros.find(macroName);
  988.  
  989.         if (it == ctx.mpDocument->mMacros.end())
  990.             error(ctx, "macro <lina:%s> not found", macroName.c_str());
  991.  
  992. //        dump_stack(ctx);
  993. //        printf("executing macro: %s (%s:%d)\n", tag.mName.c_str(), tag.mLocation->name.c_str(), tag.mLineno);
  994.  
  995.         ctx.invocation_stack.push_back(&tag);
  996.         output_tag_contents(ctx, out, *(*it).second);
  997.         ctx.invocation_stack.pop_back();
  998.  
  999. //        printf("exiting macro: %s (%s:%d)\n", tag.mName.c_str(), tag.mLocation->name.c_str(), tag.mLineno);
  1000.     }
  1001. }
  1002.  
  1003. void output_tag(Context& ctx, std::string *out, const TreeNode& tag) {
  1004.     ctx.stack.push_back(&tag);
  1005.  
  1006.     if (!tag.mbIsText && !tag.mName.compare(0,5,"lina:")) {
  1007.         output_special_tag(ctx, out, tag);
  1008.     } else {
  1009.         output_standard_tag(ctx, out, tag);
  1010.     }
  1011.  
  1012.     ctx.stack.pop_back();
  1013. }
  1014.  
  1015. int main(int argc, char **argv) {
  1016.     TreeNode::SetSupportsCDATA("p",        true);
  1017.     TreeNode::SetSupportsCDATA("h1",        true);
  1018.     TreeNode::SetSupportsCDATA("h2",        true);
  1019.     TreeNode::SetSupportsCDATA("h3",        true);
  1020.     TreeNode::SetSupportsCDATA("h4",        true);
  1021.     TreeNode::SetSupportsCDATA("h5",        true);
  1022.     TreeNode::SetSupportsCDATA("h6",        true);
  1023.     TreeNode::SetSupportsCDATA("td",        true);
  1024.     TreeNode::SetSupportsCDATA("th",        true);
  1025.     TreeNode::SetSupportsCDATA("li",        true);
  1026.     TreeNode::SetSupportsCDATA("style",    true);
  1027.     TreeNode::SetSupportsCDATA("script",    true);
  1028.     TreeNode::SetSupportsCDATA("title",    true);
  1029.     TreeNode::SetSupportsCDATA("div",        true);
  1030.     TreeNode::SetSupportsCDATA("dt",        true);
  1031.     TreeNode::SetSupportsCDATA("dd",        true);
  1032.  
  1033.     TreeDocument doc;
  1034.     TreeParser parser(&doc);
  1035.  
  1036.     parser.ParseFile(argv[1]);
  1037.  
  1038. //    dump_parse_tree(*doc.mpRoot);
  1039.  
  1040.     Context ctx;
  1041.  
  1042.     ctx.mpDocument = &doc;
  1043.  
  1044.     output_tag(ctx, NULL, *doc.mpRoot);
  1045.  
  1046.     // copy files
  1047.  
  1048.     tFileCopies::const_iterator it(g_fileCopies.begin()), itEnd(g_fileCopies.end());
  1049.  
  1050.     for(; it!=itEnd; ++it) {
  1051.         const tFileCopies::value_type& info = *it;
  1052.  
  1053.         copy_file(info.first, info.second);
  1054.     }
  1055.  
  1056.     printf("No errors.\n");
  1057.     return 0;
  1058. }
  1059.