ARTS  2.3.1285(git:92a29ea9-dirty)
docserver.cc
Go to the documentation of this file.
1 /* Copyright (C) 2012 Oliver Lemke <olemke@core-dump.info>
2 
3  This program is free software; you can redistribute it and/or modify it
4  under the terms of the GNU General Public License as published by the
5  Free Software Foundation; either version 2, or (at your option) any
6  later version.
7 
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12 
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16  USA. */
17 
26 #include "docserver.h"
27 
28 #ifdef ENABLE_DOCSERVER
29 
30 #include <stdint.h>
31 #include <algorithm>
32 #include <map>
33 #include <sstream>
34 #include <string>
35 #include <vector>
36 #include "agenda_record.h"
37 #include "auto_version.h"
38 #include "global_data.h"
39 #include "libmicrohttpd/microhttpd.h"
40 #include "libmicrohttpd/platform.h"
41 #include "messages.h"
42 #include "methods.h"
43 #include "workspace_ng.h"
44 
45 #define DOCSERVER_NAME "ARTS built-in documentation server"
46 
47 #define DS_ERROR_404 "Page not found"
48 
49 static int ahc_echo(void* cls,
50  struct MHD_Connection* connection,
51  const char* url,
52  const char* method,
53  const char* version,
54  const char* upload_data,
55  size_t* upload_data_size,
56  void** ptr);
57 
59 
65  ostringstream& token,
66  const String& indent,
67  size_t linelen) {
68  if (indent.length() + curline.str().length() + token.str().length() >
69  linelen) {
70  get_os() << curline.str() << endl << indent;
71  curline.str("");
72  }
73  curline << token.str();
74  token.str("");
75 }
76 
78 
86 string Docserver::new_page(const string& url) {
87  string surl = url;
88 
89  if (surl.find(get_baseurl()) == 0) surl.erase(0, get_baseurl().size());
90 
91  split_tokens(surl);
92 
93  while (tokens.size() && tokens[tokens.size() - 1] == "") tokens.pop_back();
94 
95  while (tokens.size() && tokens[0] == "") tokens.erase(tokens.begin());
96 
97  string content_type = "text/html; charset=utf-8";
98  if (tokens.size() && tokens[tokens.size() - 1] == "styles.css") {
99  insert_stylesheet();
100  content_type = "text/css; charset=utf-8";
101  } else {
102  switch (tokens.size()) {
103  case 0:
104  case 1:
105  insert_index();
106  break;
107  case 2:
108  insert_doc();
109  break;
110  default:
111  insert_error(DS_ERROR_404);
112  }
113  }
114 
115  return content_type;
116 }
117 
119 
127 void Docserver::split_tokens(const string& s) {
128  tokens.clear();
129 
130  stringstream ss(s);
131  string item;
132  while (getline(ss, item, '/')) tokens.push_back(item);
133 }
134 
136 
146 string Docserver::html_escape_char(const char ch) {
147  string ret;
148 
149  switch (ch) {
150  case '<':
151  ret.append("&lt;");
152  break;
153  case '>':
154  ret.append("&gt;");
155  break;
156  default:
157  ret.append(1, ch);
158  }
159 
160  return ret;
161 }
162 
164 
173 string Docserver::html_escape_string(const string& s) {
174  string ret;
175 
176  for (string::const_iterator it = s.begin(); it != s.end(); it++)
177  ret.append(html_escape_char(*it));
178 
179  return ret;
180 }
181 
183 
188 void Docserver::begin_content() {
189  get_os() << "<div class=\"content\">" << endl;
190 }
191 
193 
198 void Docserver::end_content() { get_os() << "</div>" << endl; }
199 
201 
208 void Docserver::begin_page(string title) {
209  if (title.length()) title += " - ";
210 
211  title += DOCSERVER_NAME;
212 
213  get_os()
214  << "<!DOCTYPE html>" << endl
215  << "<html lang=\"en\">" << endl
216  << "<head>" << endl
217  << "<title>" << title << "</title>" << endl
218  << "<meta charset=\"utf-8\">" << endl
219  << "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />"
220  << endl
221  << "<link rel=\"stylesheet\" href=\"" << mbaseurl << "/styles.css\">"
222  << endl
223  << "</head>" << endl
224  << "<body>" << endl;
225 }
226 
228 
233 void Docserver::end_page() {
234  get_os() << "<div class=\"footer\">Page generated by " << ARTS_FULL_VERSION
235  << "</div>\n"
236  << endl;
237 
238  get_os() << "</body>" << endl << "</html>";
239 }
240 
242 
251 String Docserver::insert_agenda_link(const String& aname) {
252  ostringstream ret;
253  ret << "<a href=\"" << mbaseurl << "/agendas/" << aname << "\">" << aname
254  << "</a>";
255  return ret.str();
256 }
257 
259 
268 String Docserver::insert_group_link(const String& gname) {
269  ostringstream ret;
270  ret << "<a href=\"" << mbaseurl << "/groups/" << gname << "\">" << gname
271  << "</a>";
272  return ret.str();
273 }
274 
276 
285 String Docserver::insert_wsm_link(const String& mname) {
286  ostringstream ret;
287  ret << "<a href=\"" << mbaseurl << "/methods/" << mname << "\">" << mname
288  << "</a>";
289  return ret.str();
290 }
291 
293 
302 String Docserver::insert_wsv_link(const String& vname) {
303  ostringstream ret;
304 
305  // Find wsv id:
306  map<String, Index>::const_iterator it = Workspace::WsvMap.find(vname);
307  if (it != Workspace::WsvMap.end()) {
308  if (is_agenda_group_id(Workspace::wsv_data[it->second].Group()))
309  ret << "<a href=\"" << mbaseurl << "/agendas/" << vname << "\">" << vname
310  << "</a>";
311  else
312  ret << "<a href=\"" << mbaseurl << "/variables/" << vname << "\">"
313  << vname << "</a>";
314  }
315 
316  return ret.str();
317 }
318 
320 
325 void Docserver::list_agendas() {
326  Index i;
327 
328  get_os() << "<h2>Agendas</h2>" << endl;
329 
330  get_os() << "<div class=\"firstcol\">" << endl << "<ul>" << endl;
331 
332  Index hitcount = 0;
333  for (i = 0; i < Workspace::wsv_data.nelem(); ++i)
334  if (is_agenda_group_id(Workspace::wsv_data[i].Group())) hitcount++;
335 
336  Index hitcount2 = 0;
337  for (i = 0; i < Workspace::wsv_data.nelem(); ++i) {
339  get_os() << "<li>" << insert_agenda_link(Workspace::wsv_data[i].Name())
340  << "</li>" << endl;
341  hitcount2++;
342 
343  if (hitcount2 == hitcount / 2)
344  get_os() << "</ul>" << endl
345  << "</div>" << endl
346  << "<div class=\"secondcol\">" << endl
347  << "<ul>" << endl;
348  }
349  }
350 
351  get_os() << "</ul>" << endl << "</div>" << endl;
352 }
353 
355 
360 void Docserver::list_groups() {
362  Index i;
363 
364  get_os() << "<h2>Workspace Groups</h2>" << endl;
365 
366  get_os() << "<div class=\"firstcol\">" << endl << "<ul>" << endl;
367  for (i = 0; i < wsv_group_names.nelem(); ++i) {
368  get_os() << "<li>" << insert_group_link(wsv_group_names[i]) << "</li>"
369  << endl;
370 
371  if (i + 1 == wsv_group_names.nelem() / 2)
372  get_os() << "</ul>" << endl
373  << "</div>" << endl
374  << "<div class=\"secondcol\">" << endl
375  << "<ul>" << endl;
376  }
377 
378  get_os() << "</ul>" << endl << "</div>" << endl;
379 }
380 
382 
387 void Docserver::list_methods() {
389  Index i;
390 
391  get_os() << "<h2>Workspace Methods</h2>" << endl;
392 
393  get_os() << "<div class=\"firstcol\">" << endl << "<ul>" << endl;
394  for (i = 0; i < md_data_raw.nelem(); ++i) {
395  get_os() << "<li>" << insert_wsm_link(md_data_raw[i].Name()) << "</li>"
396  << endl;
397 
398  if (i + 1 == md_data_raw.nelem() / 2)
399  get_os() << "</ul>" << endl
400  << "</div>" << endl
401  << "<div class=\"secondcol\">" << endl
402  << "<ul>" << endl;
403  }
404 
405  get_os() << "</ul>" << endl << "</div>" << endl;
406 }
407 
409 
414 void Docserver::list_variables() {
415  Index i;
416 
417  get_os() << "<h2>Workspace Variables</h2>" << endl;
418 
419  get_os() << "<div class=\"firstcol\">" << endl << "<ul>" << endl;
420  Index hitcount = 0;
421  for (i = 0; i < Workspace::wsv_data.nelem(); ++i) {
422  if (!is_agenda_group_id(Workspace::wsv_data[i].Group())) hitcount++;
423  }
424 
425  Index hitcount2 = 0;
426  for (i = 0; i < Workspace::wsv_data.nelem(); ++i) {
428  get_os() << "<li>" << insert_wsv_link(Workspace::wsv_data[i].Name())
429  << "</li>" << endl;
430  hitcount2++;
431 
432  if (hitcount2 == hitcount / 2)
433  get_os() << "</ul>" << endl
434  << "</div>" << endl
435  << "<div class=\"secondcol\">" << endl
436  << "<ul>" << endl;
437  }
438  }
439 
440  get_os() << "</ul>" << endl << "</div>" << endl;
441 }
442 
444 
454 String Docserver::description_add_links(const String& desc,
455  const String& mname) {
456  string ret;
457  string link;
458  bool inside_link = false;
459  string::const_iterator it = desc.begin();
460 
462  using global_data::MdRawMap;
463 
464  while (it != desc.end()) {
465  if (!inside_link) {
466  if (*it == '*')
467  inside_link = true;
468  else
469  ret += html_escape_char(*it);
470  } else {
471  if (*it == '*') {
472  inside_link = false;
473  if (MdRawMap.find(link) != MdRawMap.end())
474  ret += insert_wsm_link(link);
475  else if (AgendaMap.find(link) != AgendaMap.end())
476  ret += insert_agenda_link(link);
477  else if (Workspace::WsvMap.find(link) != Workspace::WsvMap.end())
478  ret += insert_wsv_link(link);
479  else if (get_wsv_group_id(link) != -1)
480  ret += insert_group_link(link);
481  else if (mname != "") {
482  using global_data::MdRawMap;
483  bool found = false;
484 
485  // Find method id:
486  map<String, Index>::const_iterator mit = MdRawMap.find(mname);
487  if (mit != MdRawMap.end()) {
489  const MdRecord& mdr = md_data_raw[mit->second];
490 
491  for (ArrayOfString::const_iterator sit = mdr.GIn().begin();
492  !found && sit != mdr.GIn().end();
493  sit++) {
494  if ((*sit) == link) {
495  ret += "*" + link + "*";
496  found = true;
497  }
498  }
499 
500  for (ArrayOfString::const_iterator sit = mdr.GOut().begin();
501  !found && sit != mdr.GOut().end();
502  sit++) {
503  if ((*sit) == link) {
504  ret += "*" + link + "*";
505  found = true;
506  }
507  }
508  }
509 
510  if (!found)
511  ret += "<span class=\"brokendoclink\">*" + link + "*</span>";
512  } else
513  ret += "<span class=\"brokendoclink\">*" + link + "*</span>";
514 
515  link = "";
516  } else {
517  if (!isalnum(*it) && *it != '_') {
518  inside_link = false;
519  ret += "*" + link + *it;
520  link = "";
521  } else
522  link += html_escape_char(*it);
523  }
524  }
525 
526  it++;
527  }
528 
529  if (inside_link) ret += "*" + link;
530 
531  return ret;
532 }
533 
535 
543 void Docserver::doc_method(const string& mname) {
544  // Make global data visible:
546  using global_data::MdRawMap;
548 
549  // Let's first assume it is a method that the user wants to have
550  // described.
551 
552  // Find method id:
553  map<String, Index>::const_iterator it = MdRawMap.find(mname);
554  if (it != MdRawMap.end()) {
555  // If we are here, then the given name matches a method.
556  const MdRecord& mdr = md_data_raw[it->second];
557  String indent = "";
558 
559  get_os() << "<h3>Description</h3>" << endl;
560 
561  get_os() << "<pre>" << endl;
562  get_os() << description_add_links(mdr.Description(), mname);
563  get_os() << endl << "</pre>" << endl << endl;
564 
565  bool is_first_author = true;
566  for (Index i = 0; i < mdr.Authors().nelem(); i++) {
567  if (is_first_author) {
568  get_os() << "<p><b>Authors: </b>";
569  is_first_author = false;
570  } else
571  get_os() << ", ";
572 
573  get_os() << mdr.Authors()[i];
574  }
575  get_os() << "\n";
576 
577  // Print the method's synopsis
578  while (indent.length() < mdr.Name().length() + 2) indent += ' ';
579 
580  get_os() << "<h3>Synopsis</h3>" << endl;
581 
582  ostringstream buf;
583  ostringstream param;
584  const size_t linelen = 2048;
585 
586  buf << "<p><table><tr><td>" << mdr.Name() << "(&nbsp;</td><td>";
587  bool first = true;
588  for (Index i = 0; i < mdr.Out().nelem(); ++i) {
589  if (first)
590  first = false;
591  else
592  buf << ", ";
593  param << insert_wsv_link(Workspace::wsv_data[mdr.Out()[i]].Name());
594 
595  limit_line_length(buf, param, indent, linelen);
596  }
597 
598  for (Index i = 0; i < mdr.GOutType().nelem(); ++i) {
599  if (first)
600  first = false;
601  else
602  buf << ", ";
603 
604  if (mdr.GOut()[i].length())
605  param << mdr.GOut()[i];
606  else
607  param << "gout" << i;
608 
609  limit_line_length(buf, param, indent, linelen);
610  }
611 
612  const ArrayOfIndex& inonly = mdr.InOnly();
613  for (Index i = 0; i < inonly.nelem(); ++i) {
614  if (first)
615  first = false;
616  else
617  buf << ", ";
618 
619  param << insert_wsv_link(Workspace::wsv_data[inonly[i]].Name());
620 
621  limit_line_length(buf, param, indent, linelen);
622  }
623 
624  for (Index i = 0; i < mdr.GInType().nelem(); ++i) {
625  if (first)
626  first = false;
627  else
628  buf << ", ";
629 
630  if (mdr.GIn()[i].length())
631  param << mdr.GIn()[i];
632  else
633  param << "gin" << i;
634 
635  limit_line_length(buf, param, indent, linelen);
636  }
637  if (buf.str().length()) get_os() << buf.str();
638 
639  get_os() << " )</td></tr></table>" << endl;
640 
641  get_os() << "<h3>Variables</h3>" << endl;
642 
643  // Out:
644  indent = "";
645  String desc;
646  get_os() << "<table>" << endl;
647  for (Index i = 0; i < mdr.Out().nelem(); ++i) {
648  buf.str("");
649  buf << "<tr>";
650  if (std::find(mdr.In().begin(), mdr.In().end(), mdr.Out()[i]) ==
651  mdr.In().end())
652  buf << "<td>OUT</td>";
653  else
654  buf << "<td>OUT+IN</td>";
655 
656  {
657  const String& vname = Workspace::wsv_data[mdr.Out()[i]].Name();
658  buf << "<td class=\"right\">" << insert_wsv_link(vname) << "</td><td>(";
659  buf << insert_group_link(
660  wsv_group_names[Workspace::wsv_data[mdr.Out()[i]].Group()]);
661  buf << ")</td><td>";
662  }
663 
665  desc, Workspace::wsv_data[mdr.Out()[i]].Description());
666 
667  if (buf.str().length() + desc.length() > linelen) {
668  format_paragraph(desc, indent, linelen);
669  buf << endl << indent << description_add_links(desc);
670  } else {
671  buf << description_add_links(desc);
672  }
673 
674  get_os() << buf.str() << "</td></tr>" << endl;
675  }
676 
677  size_t lastlen;
678  bool fit;
679  for (Index i = 0; i < mdr.GOut().nelem(); ++i) {
680  buf.str("");
681  buf << "<tr>";
682  buf << "<td>GOUT</td><td class=\"right\">" << mdr.GOut()[i]
683  << "</td><td>(";
684 
685  if (mdr.GOutType()[i] == get_wsv_group_id("Any") &&
686  mdr.GOutSpecType()[i].nelem()) {
687  bool firstarg = true;
688  for (Index j = 0; j < mdr.GOutSpecType()[i].nelem(); j++) {
689  if (!firstarg)
690  buf << ", ";
691  else
692  firstarg = false;
693 
694  buf << insert_group_link(wsv_group_names[mdr.GOutSpecType()[i][j]]);
695  }
696  } else {
697  buf << insert_group_link(wsv_group_names[mdr.GOutType()[i]]);
698  }
699 
700  buf << ")</td><td>";
701  desc = buf.str();
702  lastlen = desc.length();
703  fit = format_paragraph(desc, indent, linelen);
704  buf.str("");
705  get_os() << desc;
706 
707  desc = mdr.GOutDescription()[i];
708  if (!fit) {
709  format_paragraph(desc, indent, linelen);
710  buf << endl << indent << description_add_links(desc);
711  } else {
712  if (lastlen + desc.length() > linelen) {
713  format_paragraph(desc, indent, linelen, lastlen);
714  buf << endl << description_add_links(desc);
715  } else
716  buf << description_add_links(desc);
717  }
718 
719  get_os() << buf.str() << "</td></tr>" << endl;
720  }
721 
722  for (Index i = 0; i < mdr.In().nelem(); ++i) {
723  if (std::find(mdr.Out().begin(), mdr.Out().end(), mdr.In()[i]) !=
724  mdr.Out().end())
725  continue;
726 
727  buf.str("");
728  buf << "<tr>";
729  buf << "<td>IN</td>";
730 
731  const String& vname = Workspace::wsv_data[mdr.In()[i]].Name();
732  buf << "<td class=\"right\">" << insert_wsv_link(vname);
733  buf << "</td><td>(";
734  buf << insert_group_link(
735  wsv_group_names[Workspace::wsv_data[mdr.In()[i]].Group()]);
736  buf << ")</td><td>";
737 
739  Workspace::wsv_data[mdr.In()[i]].Description());
740 
741  if (buf.str().length() + desc.length() > linelen) {
742  format_paragraph(desc, indent, linelen, indent.length());
743  buf << endl << indent << description_add_links(desc);
744  } else
745  buf << description_add_links(desc);
746 
747  get_os() << buf.str() << "</td></tr>" << endl;
748  }
749 
750  for (Index i = 0; i < mdr.GIn().nelem(); ++i) {
751  buf.str("");
752  buf << "<tr>";
753  buf << "<td>GIN</td><td class=\"right\">" << mdr.GIn()[i] << "</td><td>(";
754  if (mdr.GInType()[i] == get_wsv_group_id("Any") &&
755  mdr.GInSpecType()[i].nelem()) {
756  bool firstarg = true;
757  for (Index j = 0; j < mdr.GInSpecType()[i].nelem(); j++) {
758  if (!firstarg)
759  buf << ", ";
760  else
761  firstarg = false;
762  buf << insert_group_link(wsv_group_names[mdr.GInSpecType()[i][j]]);
763  }
764  } else {
765  buf << insert_group_link(wsv_group_names[mdr.GInType()[i]]);
766  }
767 
768  if (mdr.GInDefault()[i] != NODEF) {
769  buf << ", Default: ";
770  if (mdr.GInType()[i] == get_wsv_group_id("String")) {
771  buf << "\"" << mdr.GInDefault()[i] << "\"";
772  } else {
773  buf << mdr.GInDefault()[i];
774  }
775  }
776 
777  buf << ")</td><td>";
778  desc = buf.str();
779  lastlen = desc.length();
780  fit = format_paragraph(desc, indent, linelen);
781  buf.str("");
782  get_os() << desc;
783 
784  desc = mdr.GInDescription()[i];
785  if (!fit) {
786  format_paragraph(desc, indent, linelen);
787  buf << indent << description_add_links(desc);
788  } else if (lastlen + desc.length() > linelen) {
789  format_paragraph(desc, indent, linelen, indent.length());
790  buf << indent << description_add_links(desc);
791  } else {
792  buf << description_add_links(desc);
793  }
794 
795  get_os() << buf.str() << "</td></tr>" << endl;
796  }
797  get_os() << "</table>" << endl;
798  } else {
799  insert_error_message("There is no method by this name.");
800  }
801 }
802 
804 
812 void Docserver::doc_variable_methods(const string& vname) {
813  // Check if the user gave the name of a specific variable.
814  map<String, Index>::const_iterator mi = Workspace::WsvMap.find(vname);
816  if (mi != Workspace::WsvMap.end()) {
817  // If we are here, then the given name matches a variable.
818  Index wsv_key = mi->second;
819  Index hitcount = 0;
820 
821  // List specific methods:
822  hitcount = 0;
823  get_os() << "<h3>Specific methods that can generate " << vname << "</h3>"
824  << endl
825  << "<ul>" << endl;
826  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
827  // Get handle on method record:
828  const MdRecord& mdd = md_data_raw[i];
829 
830  // This if statement checks whether Output, the list
831  // of output variables contains the workspace
832  // variable key.
833  if (count(mdd.Out().begin(), mdd.Out().end(), wsv_key)) {
834  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << "\n";
835  ++hitcount;
836  }
837  }
838  if (0 == hitcount) get_os() << "<li>none\n";
839 
840  get_os() << endl << "</ul>" << endl;
841 
842  // List generic methods:
843  get_os() << "<h3>Generic and supergeneric methods that can generate "
844  << vname << "</h3>" << endl;
845  get_os() << "<ul>" << endl;
846  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
847  // Get handle on method record:
848  const MdRecord& mdd = md_data_raw[i];
849 
850  // This if statement checks whether GOutType, the list
851  // of output variable types contains the group of the
852  // requested variable.
853  // The else clause picks up methods with supergeneric input.
854  if (count(mdd.GOutType().begin(),
855  mdd.GOutType().end(),
856  Workspace::wsv_data[wsv_key].Group())) {
857  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
858  ++hitcount;
859  } else if (count(mdd.GOutType().begin(),
860  mdd.GOutType().end(),
861  get_wsv_group_id("Any"))) {
862  for (Index j = 0; j < mdd.GOutType().nelem(); j++) {
863  if (mdd.GOutType()[j] == get_wsv_group_id("Any")) {
864  if (mdd.GOutSpecType()[j].nelem()) {
865  if (count(mdd.GOutSpecType()[j].begin(),
866  mdd.GOutSpecType()[j].end(),
867  Workspace::wsv_data[wsv_key].Group())) {
868  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
869  ++hitcount;
870  }
871  } else {
872  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
873  ++hitcount;
874  }
875  }
876  }
877  }
878  }
879  if (0 == hitcount) get_os() << "<li>none" << endl;
880 
881  get_os() << endl << "</ul>" << endl;
882 
883  // List specific methods:
884  hitcount = 0;
885  get_os() << "<h3>Specific methods that require " << vname << "</h3>" << endl
886  << "<ul>" << endl;
887  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
888  // Get handle on method record:
889  const MdRecord& mdd = md_data_raw[i];
890 
891  // This if statement checks whether Output, the list
892  // of output variables contains the workspace
893  // variable key.
894  if (count(mdd.In().begin(), mdd.In().end(), wsv_key)) {
895  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << "\n";
896  ++hitcount;
897  }
898  }
899  if (0 == hitcount) get_os() << "<li>none\n";
900 
901  get_os() << endl << "</ul>" << endl;
902 
903  // List generic methods:
904  hitcount = 0;
905  get_os() << "<h3>Generic and supergeneric methods that can use " << vname
906  << "</h3>" << endl;
907  get_os() << "<ul>" << endl;
908  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
909  // Get handle on method record:
910  const MdRecord& mdd = md_data_raw[i];
911 
912  // This if statement checks whether GOutType, the list
913  // of output variable types contains the group of the
914  // requested variable.
915  // The else clause picks up methods with supergeneric input.
916  if (count(mdd.GInType().begin(),
917  mdd.GInType().end(),
918  Workspace::wsv_data[wsv_key].Group())) {
919  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
920  ++hitcount;
921  } else if (count(mdd.GInType().begin(),
922  mdd.GInType().end(),
923  get_wsv_group_id("Any"))) {
924  for (Index j = 0; j < mdd.GInType().nelem(); j++) {
925  if (mdd.GInType()[j] == get_wsv_group_id("Any")) {
926  if (mdd.GInSpecType()[j].nelem()) {
927  if (count(mdd.GInSpecType()[j].begin(),
928  mdd.GInSpecType()[j].end(),
929  Workspace::wsv_data[wsv_key].Group())) {
930  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
931  ++hitcount;
932  }
933  } else {
934  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
935  ++hitcount;
936  }
937  }
938  }
939  }
940  }
941  if (0 == hitcount) get_os() << "<li>none" << endl;
942 
943  get_os() << endl << "</ul>" << endl;
944 
945  // List agendas with this variable as output:
947  hitcount = 0;
948  get_os() << "<h3>Agendas that can generate " << vname << "</h3>" << endl
949  << "<ul>" << endl;
950  for (Index i = 0; i < agenda_data.nelem(); ++i) {
951  // Get handle on method record:
952  const AgRecord& ar = agenda_data[i];
953 
954  // This if statement checks whether Output, the list
955  // of output variables contains the workspace
956  // variable key.
957  if (count(ar.Out().begin(), ar.Out().end(), wsv_key)) {
958  get_os() << "<li>" << insert_agenda_link(ar.Name()) << "\n";
959  ++hitcount;
960  }
961  }
962  if (0 == hitcount) get_os() << "<li>none\n";
963 
964  get_os() << endl << "</ul>" << endl;
965 
966  // List agendas with this variable as input:
967  hitcount = 0;
968  get_os() << "<h3>Agendas that require " << vname << "</h3>" << endl
969  << "<ul>" << endl;
970  for (Index i = 0; i < agenda_data.nelem(); ++i) {
971  // Get handle on method record:
972  const AgRecord& ar = agenda_data[i];
973 
974  // This if statement checks whether Output, the list
975  // of output variables contains the workspace
976  // variable key.
977  if (count(ar.In().begin(), ar.In().end(), wsv_key)) {
978  get_os() << "<li>" << insert_agenda_link(ar.Name()) << "\n";
979  ++hitcount;
980  }
981  }
982 
983  if (0 == hitcount) get_os() << "<li>none\n";
984 
985  get_os() << endl << "</ul>" << endl;
986  }
987 }
988 
990 
998 void Docserver::doc_variable(const string& vname) {
1000 
1001  // Find wsv id:
1002  map<String, Index>::const_iterator it = Workspace::WsvMap.find(vname);
1003  if (it != Workspace::WsvMap.end()) {
1004  // If we are here, then the given name matches a workspace
1005  // variable.
1006  get_os() << "<pre>" << endl;
1007  get_os() << description_add_links(
1008  Workspace::wsv_data[it->second].Description());
1009  get_os() << endl << "</pre>" << endl << endl;
1010 
1011  get_os() << "<p><b>Group: </b>"
1012  << insert_group_link(
1013  wsv_group_names[Workspace::wsv_data[it->second].Group()])
1014  << endl;
1015 
1016  doc_variable_methods(vname);
1017  } else {
1018  insert_error_message("There is no variable by this name.");
1019  }
1020 }
1021 
1023 
1031 void Docserver::doc_agenda(const string& aname) {
1033 
1034  // Find wsv id:
1035  map<String, Index>::const_iterator it = Workspace::WsvMap.find(aname);
1037  using global_data::AgendaMap;
1038  map<String, Index>::const_iterator ait = AgendaMap.find(aname);
1039 
1040  if (it != Workspace::WsvMap.end() && ait != AgendaMap.end()) {
1041  // If we are here, then the given name matches a workspace
1042  // variable.
1043  get_os() << "<pre>" << endl;
1044  get_os() << description_add_links(agenda_data[ait->second].Description());
1045  get_os() << endl << "</pre>" << endl << endl;
1046 
1047  get_os() << "<p><b>Group: </b>"
1048  << insert_group_link(
1049  wsv_group_names[Workspace::wsv_data[it->second].Group()])
1050  << endl;
1051 
1052  get_os() << "<h3>Variables</h3>" << endl;
1053 
1054  // Out:
1055  if (ait != AgendaMap.end()) {
1056  // If we are here, then the given name matches a method.
1057  const AgRecord& agr = agenda_data[ait->second];
1058  String indent = "";
1059  String desc;
1060  ostringstream buf;
1061  size_t linelen = 80;
1062  get_os() << "<table>" << endl;
1063  for (Index i = 0; i < agr.Out().nelem(); ++i) {
1064  buf.str("");
1065  buf << "<tr>";
1066  buf << "<td>OUT</td>";
1067 
1068  {
1069  const String& vname = Workspace::wsv_data[agr.Out()[i]].Name();
1070  buf << "<td class=\"right\">" << insert_wsv_link(vname)
1071  << "</td><td>(";
1072  buf << insert_group_link(
1073  wsv_group_names[Workspace::wsv_data[agr.Out()[i]].Group()]);
1074  buf << ")</td><td>";
1075  }
1076 
1078  desc, Workspace::wsv_data[agr.Out()[i]].Description());
1079 
1080  if (buf.str().length() + desc.length() > linelen) {
1081  format_paragraph(desc, indent, linelen);
1082  buf << endl << indent << description_add_links(desc);
1083  } else {
1084  buf << description_add_links(desc);
1085  }
1086 
1087  get_os() << buf.str() << "</td></tr>" << endl;
1088  }
1089 
1090  for (Index i = 0; i < agr.In().nelem(); ++i) {
1091  buf.str("");
1092  buf << "<tr>";
1093  buf << "<td>IN</td>";
1094 
1095  const String& vname = Workspace::wsv_data[agr.In()[i]].Name();
1096  buf << "<td class=\"right\">" << insert_wsv_link(vname);
1097  buf << "</td><td>(";
1098  buf << insert_group_link(
1099  wsv_group_names[Workspace::wsv_data[agr.In()[i]].Group()]);
1100  buf << ")</td><td>";
1101 
1103  desc, Workspace::wsv_data[agr.In()[i]].Description());
1104 
1105  if (buf.str().length() + desc.length() > linelen) {
1106  format_paragraph(desc, indent, linelen, indent.length());
1107  buf << endl << indent << description_add_links(desc);
1108  } else
1109  buf << description_add_links(desc);
1110 
1111  get_os() << buf.str() << "</td></tr>" << endl;
1112  }
1113 
1114  get_os() << "</table>" << endl;
1115  }
1116 
1117  doc_variable_methods(aname);
1118  } else {
1119  insert_error_message("There is no agenda by this name.");
1120  }
1121 }
1122 
1124 
1132 void Docserver::doc_group(const string& gname) {
1133  // Check if the user gave the name of a specific variable.
1134  Index gid = get_wsv_group_id(gname);
1136  if (gid != -1) {
1137  // If we are here, then the given name matches a group.
1138  Index hitcount = 0;
1139 
1140  if (gname != "Any") {
1141  // List specific methods:
1142  hitcount = 0;
1143  get_os() << "<h3>Specific methods that can generate " << gname << "</h3>"
1144  << endl;
1145  get_os() << "<ul>" << endl;
1146  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
1147  // Get handle on method record:
1148  const MdRecord& mdd = md_data_raw[i];
1149 
1150  bool first = true;
1151  for (Index j = 0; j < mdd.Out().nelem(); j++) {
1152  // This if statement checks whether the type of this output variable
1153  // matches this group.
1154  if (Workspace::wsv_data[mdd.Out()[j]].Group() == gid) {
1155  if (first) {
1156  first = false;
1157  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << " (";
1158  } else
1159  get_os() << ", ";
1160  get_os() << insert_wsv_link(
1161  Workspace::wsv_data[mdd.Out()[j]].Name());
1162 
1163  ++hitcount;
1164  }
1165  }
1166  if (!first) get_os() << ")" << endl;
1167  }
1168  if (0 == hitcount) get_os() << "<li>none" << endl;
1169 
1170  get_os() << endl << "</ul>" << endl;
1171  }
1172 
1173  // List generic methods:
1174  get_os() << "<h3>Generic and supergeneric methods that can generate "
1175  << gname << "</h3>" << endl;
1176  get_os() << "<ul>" << endl;
1177  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
1178  // Get handle on method record:
1179  const MdRecord& mdd = md_data_raw[i];
1180 
1181  // This if statement checks whether GOutType, the list
1182  // of output variable types contains the group of the
1183  // requested variable.
1184  // The else clause picks up methods with supergeneric input.
1185  if (count(mdd.GOutType().begin(), mdd.GOutType().end(), gid)) {
1186  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
1187  ++hitcount;
1188  } else if (count(mdd.GOutType().begin(),
1189  mdd.GOutType().end(),
1190  get_wsv_group_id("Any"))) {
1191  for (Index j = 0; j < mdd.GOutType().nelem(); j++) {
1192  if (mdd.GOutType()[j] == get_wsv_group_id("Any")) {
1193  if (mdd.GOutSpecType()[j].nelem()) {
1194  if (count(mdd.GOutSpecType()[j].begin(),
1195  mdd.GOutSpecType()[j].end(),
1196  gid)) {
1197  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
1198  ++hitcount;
1199  }
1200  } else {
1201  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
1202  ++hitcount;
1203  }
1204  }
1205  }
1206  }
1207  }
1208  if (0 == hitcount) get_os() << "<li>none" << endl;
1209 
1210  get_os() << endl << "</ul>" << endl;
1211 
1212  if (gname != "Any") {
1213  hitcount = 0;
1214  get_os() << "<h3>Specific methods that require variables of group "
1215  << gname << "</h3>" << endl;
1216  get_os() << "<ul>" << endl;
1217  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
1218  // Get handle on method record:
1219  const MdRecord& mdd = md_data_raw[i];
1220 
1221  bool first = true;
1222  for (Index j = 0; j < mdd.In().nelem(); j++) {
1223  // This if statement checks whether the type of this output variable
1224  // matches this group.
1225  if (Workspace::wsv_data[mdd.In()[j]].Group() == gid) {
1226  if (first) {
1227  first = false;
1228  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << " (";
1229  } else
1230  get_os() << ", ";
1231  get_os() << insert_wsv_link(
1232  Workspace::wsv_data[mdd.In()[j]].Name());
1233 
1234  ++hitcount;
1235  }
1236  }
1237  if (!first) get_os() << ")" << endl;
1238  }
1239  if (0 == hitcount) get_os() << "<li>none" << endl;
1240 
1241  get_os() << endl << "</ul>" << endl;
1242  }
1243 
1244  hitcount = 0;
1245  get_os() << "<h3>Generic and supergeneric methods that can use " << gname
1246  << "</h3>" << endl;
1247  get_os() << "<ul>" << endl;
1248  for (Index i = 0; i < md_data_raw.nelem(); ++i) {
1249  // Get handle on method record:
1250  const MdRecord& mdd = md_data_raw[i];
1251 
1252  // This if statement checks whether GOutType, the list
1253  // of output variable types contains the group of the
1254  // requested variable.
1255  // The else clause picks up methods with supergeneric input.
1256  if (count(mdd.GInType().begin(), mdd.GInType().end(), gid)) {
1257  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
1258  ++hitcount;
1259  } else if (count(mdd.GInType().begin(),
1260  mdd.GInType().end(),
1261  get_wsv_group_id("Any"))) {
1262  for (Index j = 0; j < mdd.GInType().nelem(); j++) {
1263  if (mdd.GInType()[j] == get_wsv_group_id("Any")) {
1264  if (mdd.GInSpecType()[j].nelem()) {
1265  if (count(mdd.GInSpecType()[j].begin(),
1266  mdd.GInSpecType()[j].end(),
1267  gid)) {
1268  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
1269  ++hitcount;
1270  }
1271  } else {
1272  get_os() << "<li>" << insert_wsm_link(mdd.Name()) << endl;
1273  ++hitcount;
1274  }
1275  }
1276  }
1277  }
1278  }
1279  if (0 == hitcount) get_os() << "<li>none" << endl;
1280 
1281  get_os() << endl << "</ul>" << endl;
1282 
1283  if (gname != "Any") {
1284  Index i;
1285 
1286  get_os() << "<h3>Workspace Variables of group " << gname << "</h3>"
1287  << endl
1288  << "<ul>" << endl;
1289 
1290  hitcount = 0;
1291  for (i = 0; i < Workspace::wsv_data.nelem(); ++i) {
1292  if (Workspace::wsv_data[i].Group() == get_wsv_group_id(gname)) {
1293  get_os() << "<li>" << insert_wsv_link(Workspace::wsv_data[i].Name())
1294  << endl;
1295  hitcount++;
1296  }
1297  }
1298  if (0 == hitcount) get_os() << "<li>none" << endl;
1299 
1300  get_os() << "</ul>" << endl;
1301  }
1302  } else {
1303  insert_error_message("There is no group by this name.");
1304  }
1305 }
1306 
1308 
1315 void Docserver::find_token_type() {
1316  if (tokens.size() < 1 || tokens[0] != "all") return;
1317 
1318  // Make global data visible:
1319  using global_data::AgendaMap;
1320  using global_data::MdRawMap;
1321 
1322  // Find method id:
1323  if (MdRawMap.find(tokens[1]) != MdRawMap.end())
1324  tokens[0] = "methods";
1325  else if (AgendaMap.find(tokens[1]) != AgendaMap.end())
1326  tokens[0] = "agendas";
1327  else if (Workspace::WsvMap.find(tokens[1]) != Workspace::WsvMap.end())
1328  tokens[0] = "variables";
1329  else if (get_wsv_group_id(tokens[1]) != -1)
1330  tokens[0] = "groups";
1331 }
1332 
1334 
1342 void Docserver::insert_breadcrumb_token(size_t token_id) {
1343  if (token_id != tokens.size()) {
1344  get_os() << "<a href=\"" << mbaseurl << "/";
1345  for (size_t t = 0; t < token_id; t++) {
1346  if (t) get_os() << "/";
1347  get_os() << tokens[t];
1348  }
1349  get_os() << "\">";
1350  }
1351 
1352  if (!token_id)
1353  get_os() << "Home";
1354  else if (tokens[token_id - 1] == "methods")
1355  get_os() << "Methods";
1356  else if (tokens[token_id - 1] == "variables")
1357  get_os() << "Variables";
1358  else if (tokens[token_id - 1] == "agendas")
1359  get_os() << "Agendas";
1360  else if (tokens[token_id - 1] == "groups")
1361  get_os() << "Groups";
1362  else if (tokens[token_id - 1] == "all")
1363  get_os() << "All";
1364  else if (tokens[token_id - 1] == "doccheck")
1365  get_os() << "Doc Check";
1366  else
1367  get_os() << tokens[token_id - 1];
1368 
1369  if (token_id != tokens.size()) get_os() << "</a>";
1370 }
1371 
1373 
1379 void Docserver::insert_breadcrumbs() {
1380  get_os() << "<div id=\"navbar\"><div class=\"breadcrumbs\">";
1381  for (size_t t = 0; t <= tokens.size(); t++) {
1382  if (t) get_os() << "&nbsp;>>&nbsp;";
1383  insert_breadcrumb_token(t);
1384  }
1385  get_os() << "</div>" << endl;
1386 
1387  get_os() << "<div class=\"goto\">Go to: "
1388  << "<a href=\"" << mbaseurl << "/groups/\">Groups</a>&nbsp;-&nbsp;"
1389  << "<a href=\"" << mbaseurl
1390  << "/variables/\">Variables</a>&nbsp;-&nbsp;"
1391  << "<a href=\"" << mbaseurl << "/methods/\">Methods</a>&nbsp;-&nbsp;"
1392  << "<a href=\"" << mbaseurl << "/agendas/\">Agendas</a>"
1393  << "</div></div>" << endl;
1394 }
1395 
1397 
1404 void Docserver::insert_error(const string& error) {
1405  begin_page("");
1406  insert_breadcrumbs();
1407  begin_content();
1408  insert_error_message(error);
1409  end_content();
1410  end_page();
1411 }
1412 
1414 
1421 void Docserver::insert_error_message(const string& error) {
1422  if (error.length())
1423  get_os() << "<p class=\"error\">" << error << "</p>" << endl;
1424 }
1425 
1427 
1434 void Docserver::insert_title(const string& title) {
1435  get_os() << "<h1>" DOCSERVER_NAME;
1436  if (title.length()) get_os() << " &mdash; " << title;
1437  get_os() << "</h1>" << endl;
1438 }
1439 
1441 
1447 void Docserver::insert_index() {
1448  if (tokens.size() == 0 || tokens[0] == "all") {
1449  begin_page("");
1450  insert_breadcrumbs();
1451  begin_content();
1452  insert_title("Index");
1453 
1454  list_groups();
1455  list_variables();
1456  list_methods();
1457  list_agendas();
1458  end_content();
1459  end_page();
1460  } else {
1461  if (tokens[0] == "methods") {
1462  begin_page("Method Index");
1463  insert_breadcrumbs();
1464  begin_content();
1465  insert_title("Method Index");
1466  get_os() << "<table class=\"list\">" << endl;
1467  list_methods();
1468  get_os() << "</table>" << endl;
1469  end_content();
1470  end_page();
1471  } else if (tokens[0] == "variables") {
1472  begin_page("Variable Index");
1473  insert_breadcrumbs();
1474  begin_content();
1475  insert_title("Variable Index");
1476  get_os() << "<table class=\"list\">" << endl;
1477  list_variables();
1478  get_os() << "</table>" << endl;
1479  end_content();
1480  end_page();
1481  } else if (tokens[0] == "groups") {
1482  begin_page("Group Index");
1483  insert_breadcrumbs();
1484  begin_content();
1485  insert_title("Group Index");
1486  get_os() << "<table class=\"list\">" << endl;
1487  list_groups();
1488  get_os() << "</table>" << endl;
1489  end_content();
1490  end_page();
1491  } else if (tokens[0] == "agendas") {
1492  begin_page("Agenda Index");
1493  insert_breadcrumbs();
1494  begin_content();
1495  insert_title("Agenda Index");
1496  get_os() << "<table class=\"list\">" << endl;
1497  list_agendas();
1498  get_os() << "</table>" << endl;
1499  end_content();
1500  end_page();
1501  } else {
1502  insert_error(DS_ERROR_404);
1503  }
1504  }
1505 }
1506 
1508 
1514 void Docserver::insert_doc() {
1515  find_token_type();
1516 
1517  if (tokens[0] == "methods") {
1518  begin_page(tokens[1]);
1519  insert_breadcrumbs();
1520  begin_content();
1521  insert_title();
1522  get_os() << "<h2>"
1523  << "Workspace Method " + tokens[1] << "</h2>" << endl;
1524  doc_method(tokens[1]);
1525  end_content();
1526  end_page();
1527  } else if (tokens[0] == "variables") {
1528  begin_page(tokens[1]);
1529  insert_breadcrumbs();
1530  begin_content();
1531  insert_title();
1532  get_os() << "<h2>"
1533  << "Workspace Variable " + tokens[1] << "</h2>" << endl;
1534  doc_variable(tokens[1]);
1535  end_content();
1536  end_page();
1537  } else if (tokens[0] == "groups") {
1538  begin_page(tokens[1]);
1539  insert_breadcrumbs();
1540  begin_content();
1541  insert_title();
1542  get_os() << "<h2>"
1543  << "Workspace Group " + tokens[1] << "</h2>" << endl;
1544  doc_group(tokens[1]);
1545  end_content();
1546  end_page();
1547  } else if (tokens[0] == "agendas") {
1548  begin_page(tokens[1]);
1549  insert_breadcrumbs();
1550  begin_content();
1551  insert_title();
1552  get_os() << "<h2>"
1553  << "Agenda " + tokens[1] << "</h2>" << endl;
1554  doc_agenda(tokens[1]);
1555  end_content();
1556  end_page();
1557  } else {
1558  insert_error(DS_ERROR_404);
1559  }
1560 }
1561 
1563 
1568 void Docserver::insert_stylesheet() {
1569  get_os()
1570  << "body { font-family: 'Roboto Mono', 'Hack', 'Andale Mono', monospace; "
1571  << "font-size: 90%; }" << endl
1572  << "a:link { color: #3465a4; text-decoration: none; }" << endl
1573  << "a:visited { color: #729fcf; text-decoration: none; }" << endl
1574  << "a:active { color: #ce5c00; text-decoration: none; background-color: #eeeeec}"
1575  << endl
1576  << "a:hover { color: #f57900; text-decoration: none; }" << endl
1577 
1578  << "@media (prefers-color-scheme: dark) {" << endl
1579  << " body { background-color: #121212; color: #b0bec5; }" << endl
1580  << " a:link { color: #90caf9; }" << endl
1581  << " a:visited { color: #bbdefb; }" << endl
1582  << " a:hover { color: #ffcc80; }" << endl
1583  << " a:active { color: #ffcc80; background-color: #121212; }"
1584  << endl
1585  << "}" << endl
1586 
1587  << "pre { font-family: 'Roboto Mono', 'Hack', 'Andale Mono', monospace; }" << endl
1588 
1589  << "table.list {" << endl
1590  << "width: 90%;" << endl
1591  << "margin-left: 5%;" << endl
1592  << "margin-right: 5%;" << endl
1593  << "}" << endl
1594 
1595  << "h1 {" << endl
1596  << "font-size: 1.5em;" << endl
1597  << "}" << endl
1598 
1599  << "h2 {" << endl
1600  << "font-size: 1.25em;" << endl
1601  << "}" << endl
1602 
1603  << "h3 {" << endl
1604  << "font-size: 1em;" << endl
1605  << "}" << endl
1606 
1607  << "li {" << endl
1608  << "font-size: 1em;" << endl
1609  << "}" << endl
1610 
1611  << "#navbar {" << endl
1612  << "position: fixed;" << endl
1613  << "top: 0px;" << endl
1614  << "left: 10px;" << endl
1615  << "right: 10px;" << endl
1616  << "background-color: #fff;" << endl
1617  << "border-bottom: solid 1px #ddd;" << endl
1618  << "border-left: solid 1px #ddd;" << endl
1619  << "border-right: solid 1px #ddd;" << endl
1620  << "padding: 2px;" << endl
1621  << "}" << endl
1622 
1623  << "@media (prefers-color-scheme: dark) {" << endl
1624  << " #navbar {" << endl
1625  << " background-color: #121212;" << endl
1626  << " color: #b0bec5;" << endl
1627  << " border-bottom-color: #b0bec5;" << endl
1628  << " border-left-color: #b0bec5;" << endl
1629  << " border-right-color: #b0bec5;" << endl
1630  << " }" << endl
1631  << "}" << endl
1632 
1633  << ".firstcol {" << endl
1634  << "float: left;" << endl
1635  << "clear: left;" << endl
1636  << "width: 50%;" << endl
1637  << "white-space: nowrap;" << endl
1638  << "}" << endl
1639 
1640  << ".firstcol ul {" << endl
1641  << "float: left;" << endl
1642  << "clear: both;" << endl
1643  << "padding-top: 0;" << endl
1644  << "}" << endl
1645 
1646  << ".secondcol ul {" << endl
1647  << "float: left;" << endl
1648  << "clear: both;" << endl
1649  << "padding-top: 0;" << endl
1650  << "}" << endl
1651 
1652  << ".secondcol {" << endl
1653  << "float: left;" << endl
1654  << "clear: right;" << endl
1655  << "width: 50%;" << endl
1656  << "white-space: nowrap;" << endl
1657  << "}" << endl
1658 
1659  << ".firstcol ul li {" << endl
1660  << "margin-left: 0;" << endl
1661  << "}" << endl
1662 
1663  << ".brokendoclink {" << endl
1664  << " color: #f44336;" << endl
1665  << "}" << endl
1666 
1667  << "@media (prefers-color-scheme: dark) {" << endl
1668  << " .brokendoclink {" << endl
1669  << " color: #ef9a9a;" << endl
1670  << " }" << endl
1671  << "}" << endl
1672 
1673  << ".goto {" << endl
1674  << "float: right;" << endl
1675  << "}" << endl
1676 
1677  << ".breadcrumbs {" << endl
1678  << "float: left;" << endl
1679  << "}" << endl
1680 
1681  << "@media only screen and (max-device-width: 480px) {" << endl
1682  << "#navbar { position: static; border: none; }" << endl
1683  << ".goto { position: static; float: none; }" << endl
1684  << ".breadcrumbs { position: static; float: none; }" << endl
1685  << ".firstcol { float: left; clear: left; width: 100%; }" << endl
1686  << ".secondcol { float: left; clear: both; width: 100%; }" << endl
1687  << ".firstcol ul { margin-top: 0; margin-bottom: 0; }" << endl
1688  << ".secondcol ul { margin-top: 0; }" << endl
1689  << "ul { padding-left: 1em; }" << endl
1690  << "}" << endl
1691 
1692  << "table {" << endl
1693  << "border-width: 0px;" << endl
1694  << "}" << endl
1695 
1696  << "table td {" << endl
1697  << "vertical-align: top;" << endl
1698  << "}" << endl
1699 
1700  << "table td.right {" << endl
1701  << "text-align: right;" << endl
1702  << "}" << endl
1703 
1704  << ".content {" << endl
1705  << "padding-top: 1em;" << endl
1706  << "clear: both;" << endl
1707  << "width: 100%;" << endl
1708  << "}" << endl
1709 
1710  << ".error {" << endl
1711  << "color: #f44336;" << endl
1712  << "font-weight: bold;" << endl
1713  << "font-size: 1.2em;" << endl
1714  << "}" << endl
1715 
1716  << "@media (prefers-color-scheme: dark) {" << endl
1717  << " .error {" << endl
1718  << " color: #ef9a9a;" << endl
1719  << " }" << endl
1720  << "}" << endl
1721 
1722  << "div.footer {" << endl
1723  << "float: left;" << endl
1724  << "text-align: right;" << endl
1725  << "color: #aaaaa8;" << endl
1726  << "font-size: small;" << endl
1727  << "clear: left;" << endl
1728  << "margin-top: 2em;" << endl
1729  << "width: 100%;" << endl
1730  << "}" << endl
1731 
1732  << endl;
1733 }
1734 
1736 
1742 std::tuple<size_t, std::vector<string> >
1743 Docserver::list_broken_description_links() {
1744  size_t counter = 0;
1745  vector<string> all_broken_links;
1746 
1747  auto insert_broken = [](vector<string> brk_links) {
1748  ostringstream os;
1749  bool first_broken = true;
1750  for (auto&& broken_link : brk_links) {
1751  if (!first_broken)
1752  os << ", ";
1753  else
1754  first_broken = false;
1755  os << broken_link;
1756  }
1757  return os.str();
1758  };
1759 
1760  // Broken links in WSV descriptions
1761  bool first = true;
1762  for (Index i = 0; i < Workspace::wsv_data.nelem(); ++i) {
1763  vector<string> broken_links;
1764  broken_links =
1765  find_broken_description_links(Workspace::wsv_data[i].Description());
1766  counter += broken_links.size();
1767 
1768  if (broken_links.size()) {
1769  if (first) {
1770  first = false;
1771  all_broken_links.push_back("");
1772  all_broken_links.push_back("WSV descriptions");
1773  all_broken_links.push_back("----------------");
1774  }
1775  all_broken_links.push_back(Workspace::wsv_data[i].Name() + ": " +
1776  insert_broken(broken_links));
1777  }
1778  }
1779 
1780  // Broken links in agenda descriptions
1782  first = true;
1783  for (auto&& ait : agenda_data) {
1784  vector<string> broken_links;
1785  broken_links = find_broken_description_links(ait.Description());
1786  counter += broken_links.size();
1787 
1788  if (broken_links.size()) {
1789  if (first) {
1790  first = false;
1791  all_broken_links.push_back("");
1792  all_broken_links.push_back("Agenda descriptions");
1793  all_broken_links.push_back("-------------------");
1794  }
1795  all_broken_links.push_back(ait.Name() + ": " +
1796  insert_broken(broken_links));
1797  }
1798  }
1799 
1800  // Broken links in method descriptions
1802 
1803  first = true;
1804  for (auto&& mit : md_data_raw) {
1805  vector<string> broken_links;
1806  broken_links = find_broken_description_links(mit.Description(), mit.Name());
1807  counter += broken_links.size();
1808 
1809  if (broken_links.size()) {
1810  if (first) {
1811  first = false;
1812  all_broken_links.push_back("");
1813  all_broken_links.push_back("WSM descriptions");
1814  all_broken_links.push_back("----------------");
1815  }
1816  all_broken_links.push_back(mit.Name() + ": " +
1817  insert_broken(broken_links));
1818  }
1819  }
1820 
1821  return std::make_tuple(counter, all_broken_links);
1822 }
1823 
1825 
1835 vector<string> Docserver::find_broken_description_links(const String& desc,
1836  const String& mname) {
1837  vector<string> broken_links;
1838  string ret;
1839  string link;
1840  bool inside_link = false;
1841  string::const_iterator it = desc.begin();
1842 
1843  using global_data::AgendaMap;
1844  using global_data::MdRawMap;
1845 
1846  while (it != desc.end()) {
1847  if (!inside_link) {
1848  if (*it == '*')
1849  inside_link = true;
1850  else
1851  ret += *it;
1852  } else {
1853  if (*it == '*') {
1854  inside_link = false;
1855  bool found = false;
1856  if (MdRawMap.find(link) != MdRawMap.end() ||
1857  AgendaMap.find(link) != AgendaMap.end() ||
1858  Workspace::WsvMap.find(link) != Workspace::WsvMap.end() ||
1859  get_wsv_group_id(link) != -1)
1860  found = true;
1861  else if (mname != "") {
1862  // Find method id:
1863  map<String, Index>::const_iterator mit = MdRawMap.find(mname);
1864  if (mit != MdRawMap.end()) {
1866  const MdRecord& mdr = md_data_raw[mit->second];
1867 
1868  for (ArrayOfString::const_iterator sit = mdr.GIn().begin();
1869  !found && sit != mdr.GIn().end();
1870  sit++) {
1871  if ((*sit) == link) {
1872  ret += "*" + link + "*";
1873  found = true;
1874  }
1875  }
1876 
1877  for (ArrayOfString::const_iterator sit = mdr.GOut().begin();
1878  !found && sit != mdr.GOut().end();
1879  sit++) {
1880  if ((*sit) == link) {
1881  ret += "*" + link + "*";
1882  found = true;
1883  }
1884  }
1885  }
1886  }
1887 
1888  if (!found) {
1889  broken_links.push_back("*" + link + "*");
1890  }
1891 
1892  link = "";
1893  } else {
1894  if (!isalnum(*it) && *it != '_') {
1895  inside_link = false;
1896  ret += "*" + link + *it;
1897  link = "";
1898  } else
1899  link += html_escape_char(*it);
1900  }
1901  }
1902 
1903  it++;
1904  }
1905 
1906  // if (inside_link) ret += "*" + link;
1907 
1908  return broken_links;
1909 }
1910 
1912 
1921 int Docserver::launch(bool daemon) {
1922  struct MHD_Daemon* d;
1923 
1924  d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
1925  (uint16_t)mport,
1926  NULL,
1927  NULL,
1928  &ahc_echo,
1929  (void*)this,
1930  MHD_OPTION_END);
1931 
1932  if (d == NULL) {
1933  cerr << "Error: Cannot start server. Maybe port " << mport
1934  << " is already in use?\n";
1935  return 1;
1936  } else {
1937  if (daemon)
1938  cerr << "ARTS docserver listening at http://localhost:" << mport << "\n";
1939  else
1940  cerr << "\n"
1941  << "===========================================================\n"
1942  << "Now point your web browser to http://localhost:" << mport << "\n"
1943  << "===========================================================\n\n"
1944  << "Press enter to exit.\n";
1945  }
1946 
1947  if (daemon)
1948  pause();
1949  else {
1950  (void)getc(stdin);
1951  cout << "Stopping docserver.\n";
1952  MHD_stop_daemon(d);
1953  cout << "Goodbye.\n";
1954  }
1955  return 0;
1956 }
1957 
1959 
1967 Docserver::Docserver(const Index port, const string& baseurl) : mos(NULL) {
1968  mbaseurl = baseurl;
1969  if (port == -1)
1970  mport = 9000;
1971  else
1972  mport = port;
1973 }
1974 
1976 
1993 static int ahc_echo(void* cls,
1994  struct MHD_Connection* connection,
1995  const char* url,
1996  const char* method,
1997  const char* version _U_,
1998  const char* upload_data _U_,
1999  size_t* upload_data_size _U_,
2000  void** ptr) {
2001  static int aptr;
2002  string surl(url);
2003  struct MHD_Response* response;
2004  int ret;
2005 
2006  if (!cls) {
2007  cerr << "Docserver error: Docserver object reference is NULL.\n";
2008  return MHD_NO; /* unexpected method */
2009  }
2010 
2011  // Make a local copy of the docserver object for thread-safety
2012  Docserver docserver = *((Docserver*)cls);
2013 
2014  if (0 != strcmp(method, "GET")) {
2015  cerr << "Docserver error: Unexpected method " << method << ".\n";
2016  return MHD_NO; /* unexpected method */
2017  }
2018  if (&aptr != *ptr) {
2019  /* do never respond on first call */
2020  *ptr = &aptr;
2021  return MHD_YES;
2022  }
2023  *ptr = NULL; /* reset when done */
2024  MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "q");
2025 
2026  string content_type;
2027  ostringstream hout;
2028  docserver.set_ostream(hout);
2029  content_type = docserver.new_page(surl);
2030  docserver.clear_ostream();
2031 
2032  response = MHD_create_response_from_data(
2033  hout.str().length(), (void*)hout.str().c_str(), MHD_NO, MHD_YES);
2034 
2035  if (response == NULL) {
2036  cerr << "Docserver error: response = 0\n";
2037  return MHD_NO;
2038  }
2039 
2040  MHD_add_response_header(response, "Content-type", content_type.c_str());
2041  ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
2042  MHD_destroy_response(response);
2043 
2044  return ret;
2045 }
2046 
2047 #endif /* ENABLE_DOCSERVER */
Index get_wsv_group_id(const String &name)
Returns the id of the given group.
Definition: groups.cc:223
INDEX Index
The type to use for all integer numbers and indices.
Definition: matpack.h:39
static Array< WsvRecord > wsv_data
Global WSV data.
Definition: workspace_ng.h:58
Lookup information for one agenda.
Definition: agenda_record.h:43
Index nelem() const
Number of elements.
Definition: array.h:195
#define ARTS_FULL_VERSION
Definition: auto_version.h:1
Declarations having to do with the four output streams.
map< String, Index > AgendaMap
The map associated with agenda_data.
Declarations for the arts documentation server.
const ArrayOfIndex & Out() const
Definition: methods.h:91
const Array< String > & GInDescription() const
Definition: methods.h:101
const ArrayOfIndex & Out() const
Definition: agenda_record.h:60
const map< String, Index > MdRawMap
The map associated with md_data_raw.
Definition: methods_aux.cc:41
All information for one workspace method.
Definition: methods.h:41
const String & Name() const
Definition: methods.h:88
const ArrayOfString & GOut() const
Definition: methods.h:92
This file contains the Workspace class.
const Array< String > & GInDefault() const
Definition: methods.h:100
#define NODEF
Definition: methods.h:35
_CS_string_type str() const
Definition: sstream.h:491
const ArrayOfIndex & In() const
Definition: methods.h:96
const ArrayOfArrayOfIndex & GInSpecType() const
Definition: methods.h:99
const ArrayOfString & Authors() const
Definition: methods.h:90
bool format_paragraph(String &s, const String &indent, const size_t linelen, const size_t offset=0)
Definition: methods_aux.cc:518
const Array< AgRecord > agenda_data
The lookup information for the agendas.
Definition: agendas.cc:41
const ArrayOfIndex & In() const
Definition: agenda_record.h:61
bool is_agenda_group_id(const Index group)
Check if group is an agenda group.
Definition: groups.cc:218
This can be used to make arrays out of anything.
Definition: array.h:40
const ArrayOfIndex & InOnly() const
Definition: methods.h:102
const ArrayOfIndex & GOutType() const
Definition: methods.h:93
const ArrayOfString wsv_group_names
The names associated with Wsv groups as Strings.
Definition: global_data.h:93
void get_short_wsv_description(String &s, const String &desc)
Definition: methods_aux.cc:552
static map< String, Index > WsvMap
Global map associated with wsv_data.
Definition: workspace_ng.h:61
const Array< String > & GOutDescription() const
Definition: methods.h:95
#define _U_
Definition: config.h:183
const ArrayOfArrayOfIndex & GOutSpecType() const
Definition: methods.h:94
const String & Description() const
Definition: methods.h:89
const ArrayOfIndex & GInType() const
Definition: methods.h:98
const String & Name() const
Definition: agenda_record.h:58
void limit_line_length(ostream &os, ostringstream &curline, ostringstream &token, const String &indent, size_t linelen)
Limit length of output.
Definition: methods_aux.cc:633
constexpr Rational end(Rational Ju, Rational Jl, Polarization type) noexcept
Gives the largest M for a polarization type of this transition.
Definition: zeemandata.h:108
Declaration of the class MdRecord.
const ArrayOfString & GIn() const
Definition: methods.h:97
Declarations for AgRecord, storing lookup information for one agenda.
const Array< MdRecord > md_data_raw
Lookup information for workspace methods.
Definition: methods.cc:39