1 // =================================
   2 // Copyright (c) 2020 Seppo Laakko
   3 // Distributed under the MIT license
   4 // =================================
   5 
   6 #include <gendoc/gendoc/Project.hpp>
   7 #include <gendoc/html/Html.hpp>
   8 #include <sngcpp/pp/PP.hpp>
   9 #include <sngcpp/lexer/CppLexer.hpp>
  10 #include <sngcpp/parser/SourceFile.hpp>
  11 #include <sngcpp/symbols/SymbolCreator.hpp>
  12 #include <sngcpp/binder/VirtualBinder.hpp>
  13 #include <sngcpp/binder/StatementBinder.hpp>
  14 #include <sngcpp/ast/Writer.hpp>
  15 #include <sngcpp/ast/Reader.hpp>
  16 #include <sngxml/xpath/XPathEvaluate.hpp>
  17 #include <sngxml/xpath/XPathDebug.hpp>
  18 #include <sngxml/dom/Parser.hpp>
  19 #include <sngxml/dom/Document.hpp>
  20 #include <sngxml/dom/Element.hpp>
  21 #include <soulng/util/BinaryWriter.hpp>
  22 #include <soulng/util/Path.hpp>
  23 #include <soulng/util/System.hpp>
  24 #include <soulng/util/TextUtils.hpp>
  25 #include <soulng/util/Unicode.hpp>
  26 #include <soulng/util/Util.hpp>
  27 #include <boost/filesystem.hpp>
  28 #include <boost/process.hpp>
  29 #include <iostream>
  30 #include <fstream>
  31 
  32 namespace gendoc {
  33 
  34 const int inlineCodeLimit = 10;
  35 
  36 using namespace soulng::util;
  37 using namespace soulng::unicode;
  38 
  39 Import::Import(const std::u32string& name_const std::string& relativePath_) : name(name_)relativePath(relativePath_)
  40 {
  41 }
  42 
  43 Project::Project(const std::string& docFilePath_) :
  44     docFilePath(docFilePath_)rootDir(Path::GetDirectoryName(docFilePath))doc(sngxml::dom::ReadDocument(docFilePath))contentXmlUpToDate(false)astGenerated(false)thisProjectIndex(-1)
  45     childrenRead(false)
  46 {
  47 }
  48 
  49 void Project::Process(bool verbosebool rebuildbool single)
  50 {
  51     ReadName();
  52     RunChildren(verboserebuildfalsesingle);
  53     GenerateAst(verboserebuildtrue);
  54     GenerateContent(verboserebuildfalsesingle);
  55 }
  56 
  57 void Project::Clean(bool verbosebool single)
  58 {
  59     ReadName();
  60     if (verbose)
  61     {
  62         std::cout << "cleaning " << ToUtf8(name) << "..." << std::endl;
  63     }
  64     RunChildren(verbosefalsetruesingle);
  65     SetAstFilePath();
  66     SetContentXmlFilePath();
  67     SetHtmlFilePath();
  68     boost::filesystem::remove(astFilePath);
  69     boost::filesystem::remove(contentXmlFilePath);
  70     boost::filesystem::remove(htmlFilePath);
  71     boost::filesystem::remove(Path::Combine(rootDir"status.msg"));
  72     boost::filesystem::remove_all(Path::Combine(rootDir"html"));
  73     if (verbose)
  74     {
  75         std::cout << "> " << ToUtf8(name) << " cleaned" << std::endl;
  76     }
  77 }
  78 
  79 void Project::ReadChildren()
  80 {
  81     if (childrenRead) return;
  82     childrenRead = true;
  83     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/child"doc.get());
  84     if (result)
  85     {
  86         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
  87         {
  88             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
  89             for (int i = 0; i < nodeSet->Length(); ++i)
  90             {
  91                 sngxml::dom::Node* node = (*nodeSet)[i];
  92                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
  93                 {
  94                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
  95                     std::u32string project = element->GetAttribute(U"project");
  96                     if (!project.empty())
  97                     {
  98                         std::string childFilePath = Path::Combine(Path::Combine(rootDirToUtf8(project))"gendoc.xml");
  99                         children.push_back(childFilePath);
 100                         std::u32string childProject = project;
 101                         std::unique_ptr<sngxml::dom::Document> childDoc = sngxml::dom::ReadDocument(childFilePath);
 102                         std::unique_ptr<sngxml::xpath::XPathObject> childResult = sngxml::xpath::Evaluate(U"/gendoc/project"childDoc.get());
 103                         if (childResult)
 104                         {
 105                             if (childResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 106                             {
 107                                 sngxml::xpath::XPathNodeSet* childNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(childResult.get());
 108                                 for (int i = 0; i < childNodeSet->Length(); ++i)
 109                                 {
 110                                     sngxml::dom::Node* childNode = (*childNodeSet)[i];
 111                                     if (childNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
 112                                     {
 113                                         sngxml::dom::Element* childElement = static_cast<sngxml::dom::Element*>(childNode);
 114                                         std::u32string childName = childElement->GetAttribute(U"name");
 115                                         if (!childName.empty())
 116                                         {
 117                                             childProject.append(1'/').append(childName).append(U".html");
 118                                             childProjects.push_back(std::move(childProject));
 119                                             childNames.push_back(childName);
 120                                             break;
 121                                         }
 122                                     }
 123                                 }
 124                             }
 125                         }
 126                     }
 127                 }
 128             }
 129         }
 130     }
 131 }
 132 
 133 void Project::RunChildren(bool verbosebool rebuildbool cleanbool single)
 134 {
 135     ReadChildren();
 136     if (children.empty()) return;
 137     if (clean)
 138     {
 139         if (verbose)
 140         {
 141             std::cout << ToUtf8(name) << "> cleaning " << children.size() << " child projects..." << std::endl;
 142         }
 143         if (single)
 144         {
 145             RunChildrenSingle(verbosefalsecleanfalsefalse);
 146         }
 147         else
 148         {
 149             RunChildrenWithFlags(verbosefalse"l");
 150         }
 151         if (verbose)
 152         {
 153             std::cout << ToUtf8(name) << "> " << children.size() << " child projects cleaned." << std::endl;
 154         }
 155     }
 156     else
 157     {
 158         if (verbose)
 159         {
 160             std::cout << ToUtf8(name) << "> building " << children.size() << " child projects..." << std::endl;
 161             for (const auto& child : children)
 162             {
 163                 boost::filesystem::remove(GetFullPath(Path::Combine(Path::GetDirectoryName(child)"status.msg")));
 164             }
 165             std::cout << ToUtf8(name) << "> generating ASTs..." << std::endl;
 166         }
 167         if (single)
 168         {
 169             RunChildrenSingle(verboserebuildfalsetruefalse);
 170         }
 171         else
 172         {
 173             RunChildrenWithFlags(verboserebuild"a");
 174         }
 175         if (verbose)
 176         {
 177             std::cout << ToUtf8(name) << "> ASTs ready." << std::endl;
 178             std::cout << ToUtf8(name) << "> generating content..." << std::endl;
 179         }
 180         if (single)
 181         {
 182             RunChildrenSingle(verboserebuildfalsefalsetrue);
 183         }
 184         else
 185         {
 186             RunChildrenWithFlags(verboserebuild"ce");
 187         }
 188         if (verbose)
 189         {
 190             std::cout << ToUtf8(name) << "> content ready." << std::endl;
 191             for (const auto& child : children)
 192             {
 193                 std::string statusMessageFilePath = GetFullPath(Path::Combine(Path::GetDirectoryName(child)"status.msg"));
 194                 if (boost::filesystem::exists(statusMessageFilePath))
 195                 {
 196                     std::string statusMessage = soulng::util::ReadFile(statusMessageFilePath);
 197                     std::cout << statusMessage;
 198                 }
 199             }
 200             std::cout << ToUtf8(name) << "> " << children.size() << " child projects built or up-to-date." << std::endl;
 201         }
 202     }
 203 }
 204 
 205 void Project::RunChildrenSingle(bool verbosebool rebuildbool cleanbool astbool content)
 206 {
 207     for (int i = 0; i < children.size(); ++i)
 208     {
 209         Project project(children[i]);
 210         if (clean)
 211         {
 212             project.Clean(verbosefalse);
 213         }
 214         else if (ast)
 215         {
 216             project.GenerateAst(verboserebuildfalse);
 217         }
 218         else if (content)
 219         {
 220             project.GenerateContent(verboserebuildfalsefalse);
 221         }
 222     }
 223 }
 224 
 225 void Project::RunChildrenWithFlags(bool verbosebool rebuildconst std::string& flags)
 226 {
 227     std::string f = "-";
 228     if (verbose)
 229     {
 230         f.append("v");
 231     }
 232     if (rebuild)
 233     {
 234         f.append("r");
 235     }
 236     f.append(flags);
 237     std::vector<boost::process::ipstream> is;
 238     std::vector<boost::process::ipstream> es;
 239     std::vector<boost::process::child> childProcesses;
 240     for (int i = 0; i < children.size(); ++i)
 241     {
 242         if (verbose)
 243         {
 244             std::cout << ToUtf8(name) + ">> " << children[i] << std::endl;
 245         }
 246         is.push_back(boost::process::ipstream());
 247         es.push_back(boost::process::ipstream());
 248         boost::process::child child = boost::process::child(GetPathToExecutable()fchildren[i]boost::process::std_out > is[i]boost::process::std_err > es[i]);
 249         childProcesses.push_back(std::move(child));
 250     }
 251     bool running = true;
 252     while (running)
 253     {
 254         running = false;
 255         for (int i = 0; i < childProcesses.size(); ++i)
 256         {
 257             if (childProcesses[i].running())
 258             {
 259                 std::string line;
 260                 if (std::getline(is[i]line) && !line.empty())
 261                 {
 262                     std::cout << line << std::endl;
 263                 }
 264                 running = true;
 265             }
 266         }
 267     }
 268     for (int i = 0; i < childProcesses.size(); ++i)
 269     {
 270         childProcesses[i].wait();
 271         int result = childProcesses[i].exit_code();
 272         if (result != 0)
 273         {
 274             std::string line;
 275             while (std::getline(es[i]line) && !line.empty())
 276             {
 277                 std::cerr << line << std::endl;
 278             }
 279             throw std::runtime_error("child returned exit code " + std::to_string(result));
 280         }
 281     }
 282 }
 283 
 284 void Project::GenerateAst(bool verbosebool rebuildbool readAst)
 285 {
 286     ReadName();
 287     ReadChildren();
 288     if (!children.empty()) return;
 289     ReadTop();
 290     ReadParentNameAndSiblings();
 291     ReadFilter();
 292     ReadFilePaths(verbose);
 293     ReadImports();
 294     ReadIncludePath();
 295     SetAstFilePath();
 296     SetContentXmlFilePath();
 297     if (BuildAst(verboserebuild))
 298     {
 299         WriteAst(verbose);
 300     }
 301     else if (readAst)
 302     {
 303         ReadAst(verbose);
 304     }
 305     astGenerated = true;
 306 }
 307 
 308 void Project::GenerateContent(bool verbosebool rebuildbool endMessagebool single)
 309 {
 310     try
 311     {
 312         if (!astGenerated)
 313         {
 314             ReadName();
 315             ReadChildren();
 316             ReadTop();
 317             ReadParentNameAndSiblings();
 318             if (children.empty())
 319             {
 320                 ReadFilter();
 321                 ReadFilePaths(verbose);
 322                 ReadImports();
 323                 ReadIncludePath();
 324                 SetAstFilePath();
 325                 SetContentXmlFilePath();
 326                 ReadAst(verbose);
 327                 astGenerated = true;
 328             }
 329             else
 330             {
 331                 RunChildren(verboserebuildfalsesingle);
 332                 SetAstFilePath();
 333                 SetContentXmlFilePath();
 334             }
 335         }
 336         if (children.empty())
 337         {
 338             ImportAsts(verbose);
 339             BuildSymbolTable();
 340             GenerateContentXml(verboserebuild);
 341         }
 342         MakeDirectories();
 343         GenerateStyleSheet();
 344         if (!rebuild)
 345         {
 346             upToDate = true;
 347         }
 348         else
 349         {
 350             upToDate = false;
 351         }
 352         GenerateHtmlCodeFiles(verboserebuild);
 353         ReadGrammarXmlFiles(verbose);
 354         GenerateHtmlContent(verboserebuild);
 355         if (endMessage)
 356         {
 357             std::ofstream endFile(GetFullPath(Path::Combine(rootDir"status.msg")));
 358             if (upToDate)
 359             {
 360                 endFile << ToUtf8(name) << ": up-to-date" << std::endl;
 361             }
 362             else
 363             {
 364                 endFile << ToUtf8(name) << ": built" << std::endl;
 365             }
 366         }
 367         else
 368         {
 369             if (upToDate)
 370             {
 371                 if (verbose)
 372                 {
 373                     std::cout << ToUtf8(name) << "> up-to-date" << std::endl;
 374                 }
 375             }
 376             else
 377             {
 378                 if (verbose)
 379                 {
 380                     std::cout << ToUtf8(name) << "> built" << std::endl;
 381                 }
 382             }
 383         }
 384     }
 385     catch (const std::exception& ex;)
 386     {
 387         if (endMessage)
 388         {
 389             std::ofstream endFile(GetFullPath(Path::Combine(rootDir"status.msg")));
 390             endFile << ToUtf8(name) << ": " << ex.what() << std::endl;
 391         }
 392         throw ;
 393     }
 394 }
 395 
 396 void Project::SetAstFilePath()
 397 {
 398     astFilePath = Path::Combine(rootDirToUtf8(name) + ".ast");
 399 }
 400 
 401 void Project::SetContentXmlFilePath()
 402 {
 403     contentXmlFilePath = Path::Combine(rootDirToUtf8(name) + ".xml");
 404 }
 405 
 406 void Project::SetHtmlFilePath()
 407 {
 408     htmlFilePath = Path::Combine(rootDirToUtf8(name) + ".html");
 409 }
 410 
 411 void Project::ReadName()
 412 {
 413     if (!name.empty()) return;
 414     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project"doc.get());
 415     if (result)
 416     {
 417         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 418         {
 419             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 420             for (int i = 0; i < nodeSet->Length(); ++i)
 421             {
 422                 sngxml::dom::Node* node = (*nodeSet)[i];
 423                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 424                 {
 425                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 426                     if (name.empty())
 427                     {
 428                         name = element->GetAttribute(U"name");
 429                     }
 430                     else
 431                     {
 432                         throw std::runtime_error("project name already set");
 433                     }
 434                 }
 435             }
 436         }
 437     }
 438     if (name.empty())
 439     {
 440         throw std::runtime_error("project name not set in '" + docFilePath + "'");
 441     }
 442 }
 443 
 444 void Project::ReadTop()
 445 {
 446     std::u32string top;
 447     std::string parentXmlFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir"..")"gendoc.xml"));
 448     while (boost::filesystem::exists(parentXmlFilePath))
 449     {
 450         top = top + U"../";
 451         std::string topXmlFilePath = parentXmlFilePath;
 452         parentXmlFilePath = GetFullPath(Path::Combine(Path::Combine(Path::GetDirectoryName(parentXmlFilePath)"..")"gendoc.xml"));
 453         if (!boost::filesystem::exists(parentXmlFilePath))
 454         {
 455             std::unique_ptr<sngxml::dom::Document> topDoc = sngxml::dom::ReadDocument(topXmlFilePath);
 456             std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project"topDoc.get());
 457             if (result)
 458             {
 459                 if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 460                 {
 461                     sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 462                     for (int i = 0; i < nodeSet->Length(); ++i)
 463                     {
 464                         sngxml::dom::Node* node = (*nodeSet)[i];
 465                         if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 466                         {
 467                             sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 468                             std::u32string topName = element->GetAttribute(U"name");
 469                             if (!topName.empty())
 470                             {
 471                                 top.append(topName).append(U".html");
 472                                 topLink = top;
 473                                 break;
 474                             }
 475                         }
 476                     }
 477                 }
 478             }
 479         }
 480     }
 481 }
 482 
 483 void Project::ReadParentNameAndSiblings()
 484 {
 485     std::string parentXmlFilePath = GetFullPath(Path::Combine(Path::Combine(rootDir"..")"gendoc.xml"));
 486     if (boost::filesystem::exists(parentXmlFilePath))
 487     {
 488         std::unique_ptr<sngxml::dom::Document> parentDoc = sngxml::dom::ReadDocument(parentXmlFilePath);
 489         std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project"parentDoc.get());
 490         if (result)
 491         {
 492             if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 493             {
 494                 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 495                 for (int i = 0; i < nodeSet->Length(); ++i)
 496                 {
 497                     sngxml::dom::Node* node = (*nodeSet)[i];
 498                     if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 499                     {
 500                         sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 501                         if (parentName.empty())
 502                         {
 503                             parentName = element->GetAttribute(U"name");
 504                             std::unique_ptr<sngxml::xpath::XPathObject> siblingResult = sngxml::xpath::Evaluate(U"/gendoc/child"parentDoc.get());
 505                             if (siblingResult)
 506                             {
 507                                 if (siblingResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 508                                 {
 509                                     sngxml::xpath::XPathNodeSet* siblingNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(siblingResult.get());
 510                                     for (int i = 0; i < siblingNodeSet->Length(); ++i)
 511                                     {
 512                                         sngxml::dom::Node* siblingNode = (*siblingNodeSet)[i];
 513                                         if (siblingNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
 514                                         {
 515                                             sngxml::dom::Element* siblingElement = static_cast<sngxml::dom::Element*>(siblingNode);
 516                                             std::u32string siblingDirName = siblingElement->GetAttribute(U"project");
 517                                             if (!siblingDirName.empty())
 518                                             {
 519                                                 std::u32string siblingProject = U"../" + siblingDirName;
 520                                                 std::string siblingXmlFilePath = GetFullPath(Path::Combine(Path::Combine(rootDirToUtf8(siblingProject))"gendoc.xml"));
 521                                                 std::unique_ptr<sngxml::dom::Document> siblingDoc = sngxml::dom::ReadDocument(siblingXmlFilePath);
 522                                                 std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project"siblingDoc.get());
 523                                                 if (result)
 524                                                 {
 525                                                     if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 526                                                     {
 527                                                         sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 528                                                         for (int j = 0; j < nodeSet->Length(); ++j)
 529                                                         {
 530                                                             sngxml::dom::Node* node = (*nodeSet)[j];
 531                                                             if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 532                                                             {
 533                                                                 sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 534                                                                 std::u32string siblingName = element->GetAttribute(U"name");
 535                                                                 if (!siblingName.empty())
 536                                                                 {
 537                                                                     if (siblingName == name)
 538                                                                     {
 539                                                                         thisProjectIndex = i;
 540                                                                     }
 541                                                                     siblingProject.append(1'/').append(siblingName).append(U".html");
 542                                                                     siblingProjects.push_back(siblingProject);
 543                                                                     break;
 544                                                                 }
 545                                                             }
 546                                                         }
 547                                                     }
 548                                                 }
 549                                             }
 550                                         }
 551                                     }
 552                                 }
 553                             }
 554                         }
 555                         else
 556                         {
 557                             throw std::runtime_error("project parent name already set");
 558                         }
 559                     }
 560                 }
 561             }
 562         }
 563     }
 564 }
 565 
 566 void Project::ReadFilter()
 567 {
 568     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/filter/file"doc.get());
 569     if (result)
 570     {
 571         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 572         {
 573             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 574             for (int i = 0; i < nodeSet->Length(); ++i)
 575             {
 576                 sngxml::dom::Node* node = (*nodeSet)[i];
 577                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 578                 {
 579                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 580                     std::u32string excludePattern = element->GetAttribute(U"exclude");
 581                     if (!excludePattern.empty())
 582                     {
 583                         Filter filter(contextFilter::Type::excludeexcludePattern);
 584                         filters.Add(filter);
 585                     }
 586                     else
 587                     {
 588                         std::u32string includePattern = element->GetAttribute(U"include");
 589                         if (!includePattern.empty())
 590                         {
 591                             Filter filter(contextFilter::Type::includeincludePattern);
 592                             filters.Add(filter);
 593                         }
 594                     }
 595                 }
 596             }
 597         }
 598     }
 599 }
 600 
 601 void Project::ReadFilePaths(bool verbose)
 602 {
 603     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/vcxproject"doc.get());
 604     if (result)
 605     {
 606         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 607         {
 608             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 609             for (int i = 0; i < nodeSet->Length(); ++i)
 610             {
 611                 sngxml::dom::Node* node = (*nodeSet)[i];
 612                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 613                 {
 614                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 615                     std::u32string fileAttribute = element->GetAttribute(U"file");
 616                     if (!fileAttribute.empty())
 617                     {
 618                         std::string vcxprojectFilePath = GetFullPath(Path::Combine(rootDirToUtf8(fileAttribute)));
 619                         if (verbose)
 620                         {
 621                             std::cout << ToUtf8(name) + "> " << vcxprojectFilePath << std::endl;
 622                         }
 623                         projectRoot = Path::GetDirectoryName(vcxprojectFilePath);
 624                         std::unique_ptr<sngxml::dom::Document> vcxprojectDoc = sngxml::dom::ReadDocument(vcxprojectFilePath);
 625                         std::unique_ptr<sngxml::xpath::XPathObject> hppFilesResult = sngxml::xpath::Evaluate(U"/Project/ItemGroup/ClInclude"vcxprojectDoc.get());
 626                         if (hppFilesResult)
 627                         {
 628                             if (hppFilesResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 629                             {
 630                                 sngxml::xpath::XPathNodeSet* hppFileNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(hppFilesResult.get());
 631                                 for (int i = 0; i < hppFileNodeSet->Length(); ++i)
 632                                 {
 633                                     sngxml::dom::Node* hppFileNode = (*hppFileNodeSet)[i];
 634                                     sngxml::dom::Element* hppFileElement = static_cast<sngxml::dom::Element*>(hppFileNode);
 635                                     std::u32string hppFileName = hppFileElement->GetAttribute(U"Include");
 636                                     if (!hppFileName.empty())
 637                                     {
 638                                         File hppFile(hppFileNameGetFullPath(Path::Combine(projectRootToUtf8(hppFileName))));
 639                                         filters.Apply(hppFile);
 640                                         if (hppFile.Included())
 641                                         {
 642                                             filePaths.push_back(hppFile);
 643                                             headerFiles.AddProjectHeaderFile(hppFile.Path());
 644                                         }
 645                                     }
 646                                 }
 647                             }
 648                         }
 649                         std::unique_ptr<sngxml::xpath::XPathObject> cppFilesResult = sngxml::xpath::Evaluate(U"/Project/ItemGroup/ClCompile"vcxprojectDoc.get());
 650                         if (cppFilesResult)
 651                         {
 652                             if (cppFilesResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 653                             {
 654                                 sngxml::xpath::XPathNodeSet* cppFileNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(cppFilesResult.get());
 655                                 for (int i = 0; i < cppFileNodeSet->Length(); ++i)
 656                                 {
 657                                     sngxml::dom::Node* cppFileNode = (*cppFileNodeSet)[i];
 658                                     sngxml::dom::Element* cppFileElement = static_cast<sngxml::dom::Element*>(cppFileNode);
 659                                     std::u32string cppFileName = cppFileElement->GetAttribute(U"Include");
 660                                     if (!cppFileName.empty())
 661                                     {
 662                                         File cppFile(cppFileNameGetFullPath(Path::Combine(projectRootToUtf8(cppFileName))));
 663                                         filters.Apply(cppFile);
 664                                         if (cppFile.Included())
 665                                         {
 666                                             filePaths.push_back(cppFile);
 667                                         }
 668                                     }
 669                                 }
 670                             }
 671                         }
 672                         std::unique_ptr<sngxml::xpath::XPathObject> otherFilesResult = sngxml::xpath::Evaluate(U"/Project/ItemGroup/None"vcxprojectDoc.get());
 673                         if (otherFilesResult)
 674                         {
 675                             if (otherFilesResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 676                             {
 677                                 sngxml::xpath::XPathNodeSet* otherFileNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(otherFilesResult.get());
 678                                 for (int i = 0; i < otherFileNodeSet->Length(); ++i)
 679                                 {
 680                                     sngxml::dom::Node* otherFileNode = (*otherFileNodeSet)[i];
 681                                     sngxml::dom::Element* otherFileElement = static_cast<sngxml::dom::Element*>(otherFileNode);
 682                                     std::u32string otherFileName = otherFileElement->GetAttribute(U"Include");
 683                                     if (!otherFileName.empty())
 684                                     {
 685                                         File otherFile(otherFileNameGetFullPath(Path::Combine(projectRootToUtf8(otherFileName))));
 686                                         if (Path::GetExtension(otherFile.Path()) == ".parser" || Path::GetExtension(otherFile.Path()) == ".lexer")
 687                                         {
 688                                             filters.Apply(otherFile);
 689                                             if (otherFile.Included())
 690                                             {
 691                                                 filePaths.push_back(otherFile);
 692                                             }
 693                                         }
 694                                     }
 695                                 }
 696                             }
 697                         }
 698                     }
 699                 }
 700             }
 701         }
 702     }
 703 }
 704 
 705 void Project::ReadImports()
 706 {
 707     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/imports/import"doc.get());
 708     if (result)
 709     {
 710         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 711         {
 712             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 713             for (int i = 0; i < nodeSet->Length(); ++i)
 714             {
 715                 sngxml::dom::Node* node = (*nodeSet)[i];
 716                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 717                 {
 718                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 719                     std::string relativeImportDirPath = ToUtf8(element->GetAttribute(U"project"));
 720                     if (!relativeImportDirPath.empty())
 721                     {
 722                         std::string absoluteImportFilePath = GetFullPath(Path::Combine(Path::Combine(rootDirrelativeImportDirPath)"gendoc.xml"));
 723                         std::unique_ptr<sngxml::dom::Document> importDoc = sngxml::dom::ReadDocument(absoluteImportFilePath);
 724                         std::unique_ptr<sngxml::xpath::XPathObject> importResult = sngxml::xpath::Evaluate(U"/gendoc/project"importDoc.get());
 725                         if (importResult)
 726                         {
 727                             if (importResult->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 728                             {
 729                                 sngxml::xpath::XPathNodeSet* importNodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(importResult.get());
 730                                 for (int i = 0; i < importNodeSet->Length(); ++i)
 731                                 {
 732                                     sngxml::dom::Node* importNode = (*importNodeSet)[i];
 733                                     if (importNode->GetNodeType() == sngxml::dom::NodeType::elementNode)
 734                                     {
 735                                         sngxml::dom::Element* importElement = static_cast<sngxml::dom::Element*>(importNode);
 736                                         std::u32string importProjectName = importElement->GetAttribute(U"name");
 737                                         Import* importProject = new Import(importProjectNamerelativeImportDirPath);
 738                                         importMap[importProjectName] = importProject;
 739                                         imports.push_back(std::unique_ptr<Import>(importProject));
 740                                     }
 741                                 }
 742                             }
 743                         }
 744                     }
 745                 }
 746             }
 747         }
 748     }
 749 }
 750 
 751 void Project::ReadIncludePath()
 752 {
 753     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/include"doc.get());
 754     if (result)
 755     {
 756         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
 757         {
 758             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
 759             for (int i = 0; i < nodeSet->Length(); ++i)
 760             {
 761                 sngxml::dom::Node* node = (*nodeSet)[i];
 762                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
 763                 {
 764                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
 765                     std::u32string pathAttribute = element->GetAttribute(U"path");
 766                     if (!pathAttribute.empty())
 767                     {
 768                         if (includePath.empty())
 769                         {
 770                             includePath = Path::MakeCanonical(ToUtf8(pathAttribute));
 771                         }
 772                         else
 773                         {
 774                             throw std::runtime_error("include path already set");
 775                         }
 776                     }
 777                 }
 778             }
 779         }
 780     }
 781 }
 782 
 783 bool Project::BuildAst(bool verbosebool rebuild)
 784 {
 785     if (!rebuild && boost::filesystem::exists(astFilePath))
 786     {
 787         bool upToDate = true;
 788         for (const File& file : filePaths)
 789         {
 790             if (boost::filesystem::last_write_time(file.Path()) >= boost::filesystem::last_write_time(astFilePath))
 791             {
 792                 upToDate = false;
 793                 break;
 794             }
 795         }
 796         if (upToDate)
 797         {
 798             return false;
 799         }
 800     }
 801     sngcpp::pp::EvaluationContext evaluationContext;
 802     int fileIndex = 0;
 803     for (const File& file : filePaths)
 804     {
 805         if (file.Included())
 806         {
 807             std::unique_ptr<sngcpp::ast::SourceFileNode> sourceFile;
 808             std::unique_ptr<CppLexer> lexer;
 809             if (verbose)
 810             {
 811                 std::cout << ToUtf8(name) + "> " << file.Path() << std::endl;
 812             }
 813             std::string ext = Path::GetExtension(file.Path());
 814             if (ext == ".parser" || ext == ".lexer")
 815             {
 816                 std::unique_ptr<std::u32string> content(new std::u32string(ToUtf32(soulng::util::ReadFile(file.Path()))));
 817                 sourceFile.reset(new sngcpp::ast::SourceFileNode(soulng::lexer::Span()file.Path()ToUtf8(file.Name())name));
 818                 sourceFile->SetSourceFileIndex(fileIndex);
 819                 sourceFile->SetText(std::move(*content));
 820             }
 821             else
 822             {
 823                 sngcpp::pp::PP pp(evaluationContext);
 824                 pp.root = projectRoot;
 825                 pp.includePath = Split(includePath';');
 826                 pp.projectHeaderFileSet = &headerFiles;
 827                 pp.Define(sngcpp::pp::ndebug);
 828                 if (verbose)
 829                 {
 830                     pp.verbose = true;
 831                 }
 832                 Preprocess(file.Path()&pp);
 833                 std::unique_ptr<std::u32string> content(new std::u32string(std::move(pp.ctext)));
 834                 lexer.reset(new CppLexer(content->c_str()content->c_str() + content->length()file.Path()fileIndex));
 835                 lexer->SetSeparatorChar('\n');
 836                 sourceFile.reset(new sngcpp::ast::SourceFileNode(lexer->GetSpan()file.Path()ToUtf8(file.Name())name));
 837                 sourceFile->SetSourceFileIndex(fileIndex);
 838                 SourceFileParser::Parse(*lexersourceFile.get());
 839                 sourceFile->SetHeaderFilePaths(std::move(pp.headerFilePaths));
 840                 sourceFile->SetText(std::move(*content));
 841                 sourceFile->ComputeLineStarts();
 842             }
 843             if (ast)
 844             {
 845                 soulng::lexer::Span span = ast->GetSpan();
 846                 ast.reset(new sngcpp::ast::SourceFileSequenceNode(spanast.release()sourceFile.release()));
 847             }
 848             else
 849             {
 850                 ast.reset(sourceFile.release());
 851             }
 852             ++fileIndex;
 853             lexers.push_back(std::unique_ptr<soulng::lexer::Lexer>(lexer.release()));
 854         }
 855     }
 856     return true;
 857 }
 858 
 859 void Project::WriteAst(bool verbose)
 860 {
 861     if (!ast) return;
 862     BinaryWriter binaryWriter(astFilePath);
 863     std::vector< soulng::lexer::Lexer*> lexerPtrs;
 864     for (const auto& lexer : lexers)
 865     {
 866         lexerPtrs.push_back(lexer.get());
 867     }
 868     sngcpp::ast::Writer writer(lexerPtrsbinaryWriter);
 869     ast->Write(writer);
 870     if (verbose)
 871     {
 872         std::cout << ToUtf8(name) + "==> " << astFilePath << std::endl;
 873     }
 874 }
 875 
 876 void Project::ReadAst(bool verbose)
 877 {
 878     if (!boost::filesystem::exists(astFilePath)) return;
 879     if (verbose)
 880     {
 881         std::cout << ToUtf8(name) + "> " << astFilePath << std::endl;
 882     }
 883     BinaryReader binaryReader(astFilePath);
 884     sngcpp::ast::Reader reader(binaryReader);
 885     ast.reset(reader.ReadNode());
 886 }
 887 
 888 void Project::ImportAsts(bool verbose)
 889 {
 890     for (const auto& importProject : imports)
 891     {
 892         std::string importAstFilePath = GetFullPath(Path::Combine(Path::Combine(rootDirimportProject->RelativePath())ToUtf8(importProject->Name()) + ".ast"));
 893         importAstFilePaths.push_back(importAstFilePath);
 894         if (verbose)
 895         {
 896             std::cout << ToUtf8(name) + "> " << importAstFilePath << std::endl;
 897         }
 898         BinaryReader binaryReader(importAstFilePath);
 899         sngcpp::ast::Reader reader(binaryReader);
 900         importAsts.push_back(std::unique_ptr<sngcpp::ast::Node>(reader.ReadNode()));
 901     }
 902 }
 903 
 904 void Project::BuildSymbolTable()
 905 {
 906     if (!ast) return;
 907     symbolTable.SetGendocMode();
 908     sngcpp::ast::ResolveSourceFiles(ast.get()sourceFiles);
 909     for (auto& sourceFile : sourceFiles)
 910     {
 911         sourceFileMap[sourceFile->SourceFilePath()] = sourceFile;
 912     }
 913     int n = imports.size();
 914     for (int i = 0; i < n; ++i)
 915     {
 916         Import* importProject = imports[i].get();
 917         std::std::unique_ptr<sngcpp::ast::Node>&importAst=importAsts[i];
 918         sngcpp::symbols::SymbolCreator symbolCreator(symbolTableimportProject->Name());
 919         importAst->Accept(symbolCreator);
 920         importSourceFiles.push_back(std::vector<sngcpp::ast::SourceFileNode*>());
 921         sngcpp::ast::ResolveSourceFiles(importAst.get()importSourceFiles.back());
 922         for (auto& sourceFile : importSourceFiles.back())
 923         {
 924             sourceFileMap[sourceFile->SourceFilePath()] = sourceFile;
 925         }
 926         sngcpp::binder::TypeBinder typeBinder(symbolTablethis);
 927         importAst->Accept(typeBinder);
 928     }
 929     sngcpp::symbols::SymbolCreator symbolCreator(symbolTablename);
 930     ast->Accept(symbolCreator);
 931     sngcpp::binder::TypeBinder typeBinder(symbolTablethis);
 932     ast->Accept(typeBinder);
 933     sngcpp::binder::ResolveOverrideSets(typeBinder.ClassesHavingVirtualFunctions());
 934     std::vector<std::std::unique_ptr<sngcpp::binder::BoundSourceFile>>boundSourceFiles=typeBinder.SourceFiles();
 935     std::unordered_map<sngcpp::ast::SourceFileNode*sngcpp::binder::BoundSourceFile*> boundSourceFileMap = typeBinder.SourceFileMap();
 936     sngcpp::binder::StatementBinder statementBinder(symbolTableboundSourceFileMap);
 937     ast->Accept(statementBinder);
 938 }
 939 
 940 void Project::GenerateContentXml(bool verbosebool rebuild)
 941 {
 942     if (!boost::filesystem::exists(astFilePath)) return;
 943     contentXmlUpToDate = true;
 944     if (rebuild)
 945     {
 946         contentXmlUpToDate = false;
 947     }
 948     else
 949     {
 950         if (boost::filesystem::exists(contentXmlFilePath))
 951         {
 952             if (boost::filesystem::last_write_time(astFilePath) > boost::filesystem::last_write_time(contentXmlFilePath))
 953             {
 954                 contentXmlUpToDate = false;
 955             }
 956             else
 957             {
 958                 for (const std::string& importAstFilePath : importAstFilePaths)
 959                 {
 960                     if (boost::filesystem::last_write_time(importAstFilePath) > boost::filesystem::last_write_time(contentXmlFilePath))
 961                     {
 962                         contentXmlUpToDate = false;
 963                         break;
 964                     }
 965                 }
 966                 if (contentXmlUpToDate)
 967                 {
 968                     for (const auto& sourceFileVec : importSourceFiles)
 969                     {
 970                         for (const auto& sourceFile : sourceFileVec)
 971                         {
 972                             if (boost::filesystem::last_write_time(sourceFile->SourceFilePath()) > boost::filesystem::last_write_time(contentXmlFilePath))
 973                             {
 974                                 contentXmlUpToDate = false;
 975                                 break;
 976                             }
 977                         }
 978                         if (!contentXmlUpToDate)
 979                         {
 980                             break;
 981                         }
 982                     }
 983                 }
 984             }
 985         }
 986         else
 987         {
 988             contentXmlUpToDate = false;
 989         }
 990     }
 991     if (contentXmlUpToDate)
 992     {
 993         ReadContentXml(verbose);
 994     }
 995     else
 996     {
 997         WriteContentXml(verbose);
 998     }
 999 }
1000 
1001 void Project::WriteContentXml(bool verbose)
1002 {
1003     contentXml = symbolTable.ToDomDocument();
1004     std::ofstream xmlFile(contentXmlFilePath);
1005     CodeFormatter formatter(xmlFile);
1006     formatter.SetIndentSize(1);
1007     contentXml->Write(formatter);
1008     if (verbose)
1009     {
1010         std::cout << ToUtf8(name) + "==> " << contentXmlFilePath << std::endl;
1011     }
1012 }
1013 
1014 void Project::ReadContentXml(bool verbose)
1015 {
1016     if (verbose)
1017     {
1018         std::cout << ToUtf8(name) + "> " << contentXmlFilePath << std::endl;
1019     }
1020     contentXml = sngxml::dom::ReadDocument(contentXmlFilePath);
1021 }
1022 
1023 void Project::MakeDirectories()
1024 {
1025     gendoc::html::MakeDirectories(rootDirhtmlDircontentDirfileDirstyleDir);
1026 }
1027 
1028 void Project::GenerateStyleSheet()
1029 {
1030     gendoc::html::GenerateStyleSheet(styleDirstyleDirNamestyleFileName);
1031 }
1032 
1033 void Project::GenerateHtmlCodeFiles(bool verbosebool rebuild)
1034 {
1035     for (auto& sourceFile : sourceFiles)
1036     {
1037         sourceFile->SetHtmlSourceFilePath(Path::Combine(contentDirToUtf8(sourceFile->Id())));
1038     }
1039     bool htmlCodeUpToDate = true;
1040     gendoc::html::GenerateHtmlCodeFiles(namesourceFilesstyleDirNamestyleFileNamesymbolTableinlineCodeLimitinlineCodeMapverboserebuildhtmlCodeUpToDatethis);
1041     if (!htmlCodeUpToDate)
1042     {
1043         upToDate = false;
1044     }
1045 }
1046 
1047 void Project::ReadGrammarXmlFiles(bool verbose)
1048 {
1049     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/grammars"doc.get());
1050     if (result)
1051     {
1052         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
1053         {
1054             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
1055             int n = nodeSet->Length();
1056             for (int i = 0; i < n; ++i)
1057             {
1058                 sngxml::dom::Node* node = (*nodeSet)[i];
1059                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
1060                 {
1061                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
1062                     std::u32string grammarsFileAttribute = element->GetAttribute(U"file");
1063                     if (!grammarsFileAttribute.empty())
1064                     {
1065                         grammarsXmlFilePath = GetFullPath(Path::Combine(rootDirPath::MakeCanonical(ToUtf8(grammarsFileAttribute))));
1066                         grammarsXmlDoc = sngxml::dom::ReadDocument(grammarsXmlFilePath);
1067                         std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/project/grammar"grammarsXmlDoc.get());
1068                         if (result)
1069                         {
1070                             if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
1071                             {
1072                                 sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
1073                                 int n = nodeSet->Length();
1074                                 for (int i = 0; i < n; ++i)
1075                                 {
1076                                     sngxml::dom::Node* node = (*nodeSet)[i];
1077                                     if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
1078                                     {
1079                                         sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
1080                                         std::u32string grammarFileAttribute = element->GetAttribute(U"file");
1081                                         if (!grammarFileAttribute.empty())
1082                                         {
1083                                             std::u32string grammarHtmlFileName = ToUtf32(Path::Combine("html/content"
1084                                                 Path::GetFileNameWithoutExtension(Path::MakeCanonical(ToUtf8(grammarFileAttribute))) + ".html"));
1085                                             std::string grammarXmlFilePath = GetFullPath(Path::Combine(rootDirPath::MakeCanonical(ToUtf8(grammarFileAttribute))));
1086                                             std::unique_ptr<sngxml::dom::Document> grammarXmlDoc = sngxml::dom::ReadDocument(grammarXmlFilePath);
1087                                             std::u32string grammarTitle = grammarXmlDoc->DocumentElement()->GetAttribute(U"title");
1088                                             gendoc::html::Grammar grammar(grammarHtmlFileNamegrammarTitle);
1089                                             grammars.push_back(std::move(grammar));
1090                                         }
1091                                     }
1092                                 }
1093                             }
1094                         }
1095                     }
1096                 }
1097             }
1098         }
1099     }
1100 }
1101 
1102 void Project::ReadDocumentationXml(bool verbosebool& rebuild)
1103 {
1104     std::unique_ptr<sngxml::xpath::XPathObject> result = sngxml::xpath::Evaluate(U"/gendoc/project/documentation"doc.get());
1105     if (result)
1106     {
1107         if (result->Type() == sngxml::xpath::XPathObjectType::nodeSet)
1108         {
1109             sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(result.get());
1110             int n = nodeSet->Length();
1111             for (int i = 0; i < n; ++i)
1112             {
1113                 sngxml::dom::Node* node = (*nodeSet)[i];
1114                 if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
1115                 {
1116                     sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
1117                     std::u32string documentationFileAttribute = element->GetAttribute(U"file");
1118                     if (!documentationFileAttribute.empty())
1119                     {
1120                         documentationXmlFileName = GetFullPath(Path::Combine(rootDirPath::MakeCanonical(ToUtf8(documentationFileAttribute))));
1121                         if (boost::filesystem::exists(documentationXmlFileName))
1122                         {
1123                             documentationXml = sngxml::dom::ReadDocument(documentationXmlFileName);
1124                             if (boost::filesystem::exists(contentXmlFilePath))
1125                             {
1126                                 if (boost::filesystem::last_write_time(documentationXmlFileName) > boost::filesystem::last_write_time(contentXmlFilePath))
1127                                 {
1128                                     rebuild = true;
1129                                 }
1130                             }
1131                         }
1132                         else
1133                         {
1134                             std::cerr << "Documentation XML file '" + documentationXmlFileName + "' does not exist." << std::endl;
1135                         }
1136                     }
1137                 }
1138             }
1139         }
1140     }
1141 }
1142 
1143 void Project::GenerateHtmlContent(bool verbosebool rebuild)
1144 {
1145     ReadDocumentationXml(verboserebuild);
1146     if (contentXml)
1147     {
1148         bool htmlContentUpToDate = true;
1149         std::u32string top;
1150         if (!topLink.empty())
1151         {
1152             top = U"../../";
1153             top.append(topLink);
1154         }
1155         gendoc::html::GenerateContent(namecontentXml.get()contentDirstyleDirNamestyleFileNameinlineCodeMapcontentXmlFilePathverboserebuildhtmlContentUpToDatethistop
1156             documentationXmlFileNamedocumentationXml.get());
1157         if (!htmlContentUpToDate)
1158         {
1159             upToDate = false;
1160         }
1161     }
1162     bool moduleFileUpToDate = true;
1163     std::u32string prevProject;
1164     std::u32string nextProject;
1165     if (thisProjectIndex != -1)
1166     {
1167         if (thisProjectIndex > 0)
1168         {
1169             prevProject = siblingProjects[thisProjectIndex - 1];
1170         }
1171         if (thisProjectIndex < siblingProjects.size() - 1)
1172         {
1173             nextProject = siblingProjects[thisProjectIndex + 1];
1174         }
1175     }
1176     gendoc::html::GenerateModuleHtml(nameparentNameprevProjectnextProjectchildProjectschildNamescontentXml.get()contentDirstyleDirNamestyleFileNamesourceFiles
1177         contentXmlFilePathverboserebuildmoduleFileUpToDatethistopLinkdocumentationXmlFileNamedocumentationXml.get()grammars);
1178     if (!moduleFileUpToDate)
1179     {
1180         upToDate = false;
1181     }
1182 }
1183 
1184 std::string Project::ResolveContentFilePath(const std::u32string& currentProjectNameconst std::u32string& projectNameconst std::string& relativeContentDirPathconst std::u32string& symbolId)
1185 {
1186     if (projectName.empty())
1187     {
1188         std::cerr << "warning: import for project name is empty" << std::endl;
1189         return Path::Combine(relativeContentDirPathToUtf8(symbolId) + ".html");
1190     }
1191     else if (currentProjectName == projectName)
1192     {
1193         return Path::Combine(relativeContentDirPathToUtf8(symbolId) + ".html");
1194     }
1195     else
1196     {
1197         auto it = importMap.find(projectName);
1198         if (it != importMap.cend())
1199         {
1200             Import* imp = it->second;
1201             return Path::Combine(Path::Combine(Path::Combine(Path::Combine(relativeContentDirPath"../..")imp->RelativePath())"html/content")ToUtf8(symbolId) + ".html");
1202         }
1203         else
1204         {
1205             std::cerr << "warning: import for project name " + ToUtf8(projectName) + " not found" << std::endl;
1206             return Path::Combine(relativeContentDirPathToUtf8(symbolId) + ".html");
1207         }
1208     }
1209 }
1210 
1211 sngcpp::ast::SourceFileNode* Project::GetSourceFile(const std::string& sourceFilePath) const
1212 {
1213     auto it = sourceFileMap.find(sourceFilePath);
1214     if (it != sourceFileMap.cend())
1215     {
1216         return it->second;
1217     }
1218     else
1219     {
1220         return nullptr;
1221     }
1222 }
1223 
1224 } // namespace gendoc