33 #define h2x_assert(bed) arb_assert(bed)
38 #define MAX_TITLE_CHARS 42
39 #define MAX_SUBTITLE_CHARS 75
42 #define WARN_FORMATTING_PROBLEMS
43 #define WARN_MISSING_HELP
49 #if defined(WARN_FORMATTING_PROBLEMS)
51 #define WARN_FIXED_LAYOUT_LIST_ELEMENTS
52 #define WARN_LONESOME_ENUM_ELEMENTS
62 #define MAX_LINE_LENGTH 200 // maximum length of lines in input stream
99 throw string(
"out of memory");
102 length = vsnprintf(buffer, buf_size, format, argPtr);
103 if (length < buf_size)
break;
106 buf_size += buf_size/2;
111 return string(buffer, length);
147 warnings.push_back(laMsg);
156 virtual string location_description()
const = 0;
160 string where = location_description();
182 string location_description() const
OVERRIDE {
return ""; }
187 if (in.eof()) eof =
true;
194 if (in.eof()) eof =
true;
195 else if (in.fail())
throw "line too long";
197 if (strchr(lineBuffer,
'\t')) {
200 for (
int o = 0; lineBuffer[o]; ++o) {
201 if (lineBuffer[o] ==
'\t') {
203 while (spaces--) lineBuffer2[o2++] =
' ';
206 lineBuffer2[o2++] = lineBuffer[o];
210 strcpy(lineBuffer, lineBuffer2);
213 char *
eol = strchr(lineBuffer, 0)-1;
214 while (eol >= lineBuffer && isspace(eol[0])) {
218 if (eol > lineBuffer) {
220 if (eol[0] ==
'-' && isalnum(eol[-1])) {
221 attach_warning(
"manual hyphenation detected");
233 if (readAgain) readAgain =
false;
235 return eof ?
NULp : lineBuffer;
293 operator const string&()
const {
return content; }
294 operator string&() {
return content; }
312 const char *
c_str()
const {
return content.c_str(); }
317 #if defined(WARN_MISSING_HELP)
319 if (strstr(line,
"@@@") || strstr(line,
"TODO")) {
325 #endif // WARN_MISSING_HELP
336 string location_description() const
OVERRIDE {
return string(
"in SECTION '")+name+
"'"; }
346 const Ostrings&
Content()
const {
return content; }
350 const string&
getName()
const {
return name; }
351 void setName(
const string& name_) { name = name_; }
363 size_t source_lineno;
366 Link(
const string& target_,
size_t source_lineno_) :
368 source_lineno(source_lineno_)
371 const string&
Target()
const {
return target; }
383 Links auto_references;
385 SectionList sections;
388 void check_self_ref(
const string&
link) {
389 size_t slash = inputfile.find(
'/');
390 if (slash != string::npos) {
391 if (inputfile.substr(slash+1) ==
link) {
392 throw string(
"Invalid link to self");
401 void readHelp(istream& in,
const string& filename);
402 void writeXML(FILE *out,
const string& page_name);
403 void extractInternalLinks();
408 inline bool isSpace(
char c) {
return c ==
' '; }
412 if (s[0] ==
'#')
return true;
413 for (
int off = 0; ; ++off) {
414 if (s[off] == 0)
return true;
425 const char *
space = strchr(line,
' ');
426 if (space && space>line) {
427 keyword =
string(line, 0, space-line);
433 return strchr(line, 0);
451 if (paragraph.length()) {
453 sec.Content().push_back(
Ostring(paragraph, lineNo, type, etype, num));
456 sec.Content().push_back(
Ostring(paragraph, lineNo, type));
472 (contentStart[0] ==
'-' ||
473 contentStart[0] ==
'*')
475 isspace(contentStart[1])
477 !(isspace(contentStart[2]) ||
478 contentStart[2] ==
'-');
481 #define MAX_ALLOWED_ENUM 99 // otherwise it starts interpreting years as enums
488 size_t off = s.find_first_not_of(
" \n");
489 if (off == string::npos)
return NONE;
490 if (!isalpha(s[off]))
return NONE;
495 number = s[off]-(etype ==
ALPHA_UPPER ?
'A' :
'a')+1;
500 if (s[off] !=
'.' && s[off] !=
')')
return NONE;
501 if (s[off+1] !=
' ')
return NONE;
505 while (s[off+1] ==
' ') ++off;
506 s.erase(astart, off-astart+1);
515 size_t off = s.find_first_not_of(
" \n");
516 if (off == string::npos)
return false;
517 if (!isdigit(s[off]))
return false;
519 size_t num_start = off;
522 for (; isdigit(s[off]); ++off) {
523 number = number*10 + (s[off]-
'0');
527 if (s[off] !=
'.' && s[off] !=
')')
return false;
528 if (s[off+1] !=
' ')
return false;
532 while (s[off+1] ==
' ') ++off;
533 s.erase(num_start, off-num_start+1);
544 string paragraph =
line;
545 size_t para_start_lineno = reader.
getLineNo();
549 sec.set_line_number(para_start_lineno);
556 unsigned last_alpha_num = -1;
565 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
582 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
585 const char *firstNonWhite =
firstChar(line);
589 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
591 Line[firstNonWhite-
line] =
' ';
599 if (foundNum == (last_alpha_num+1) || foundNum == 1) {
600 last_alpha_num = foundNum;
603 #if defined(WARN_IGNORED_ALPHA_ENUMS)
614 if (foundEtype !=
NONE) {
615 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
623 throw "Enumerations starting with zero are not supported";
629 if (paragraph.length()) {
630 paragraph = paragraph+
"\n"+Line;
633 paragraph =
string(
"\n")+Line;
639 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
641 if (sec.Content().size()>0 && indentation>0) {
643 spaces.reserve(indentation);
644 spaces.append(indentation,
' ');
646 string& ostr = sec.Content().front();
647 ostr =
string(
"\n") + spaces + ostr;
652 for (Links::const_iterator ex = existing.begin(); ex != existing.end(); ++ex) {
653 if (ex->Target() ==
link) {
654 if (add_warnings)
add_warning(
strf(
"First Link to '%s' was found here.", ex->Target().c_str()), ex->SourceLineno());
655 throw strf(
"Link to '%s' duplicated here.", link.c_str());
666 SectionList::iterator end = sections.end();
667 for (SectionList::iterator
s = sections.begin();
s != end; ++
s) {
668 const string& sname =
s->getName();
669 if (sname ==
"NOTES")
continue;
671 SectionList::iterator o =
s; ++o;
672 for (; o != end; ++o) {
673 if (sname == o->getName()) {
674 o->attach_warning(
"duplicated SECTION name");
675 if (seen.find(sname) == seen.end()) {
676 s->attach_warning(
"name was first used");
692 const char *
name_only = strrchr(filename.c_str(),
'/');
713 if (keyword ==
"UP") {
717 if (strcmp(name_only, rest) == 0)
throw "UP link to self";
722 else if (keyword ==
"SUB") {
726 if (strcmp(name_only, rest) == 0)
throw "SUB link to self";
731 else if (keyword ==
"TITLE") {
735 if (
title.Content().empty())
throw "empty TITLE not allowed";
737 const string& t =
title.Content().front();
738 if (t.find(
"Standard help file form") != string::npos) {
739 throw strf(
"Illegal title for help file: '%s'", t.c_str());
742 const size_t len = t.length();
745 size_t last_alnum_pos = len-1;
746 while (!isalnum(t[last_alnum_pos])) {
752 const size_t ignored = len-last_alnum_pos;
754 title.attach_warning(
strf(
"TITLE too verbose (max. %i chars allowed; found %zu%s)",
757 ignored ?
strf(
"; acceptable trailing chars: %zu", ignored).c_str() :
""
763 if (keyword ==
"NOTE") keyword =
"NOTES";
764 if (keyword ==
"EXAMPLE") keyword =
"EXAMPLES";
765 if (keyword ==
"WARNING") keyword =
"WARNINGS";
770 if (knownSections[idx] == keyword) {
778 if (idx >= KNOWN_SECTION_TYPES)
throw strf(
"unknown keyword '%s'", keyword.c_str());
781 string section_name =
eatSpace(rest);
782 Section sec(section_name, stype, lineno);
784 sections.push_back(sec);
787 Section sec(keyword, stype, lineno);
790 sections.push_back(sec);
795 throw strf(
"Unhandled line");
807 enum { START,
CHAR, SPACE, MULTIPLE, DOT, DOTSPACE } state = START;
808 bool equal_indent =
true;
812 for (string::const_iterator c = s.begin(); c != s.end(); ++c, ++thisIndent) {
818 if (state == DOT || state == DOTSPACE) state = DOTSPACE;
819 else if (state == SPACE) state = MULTIPLE;
820 else if (state ==
CHAR) state = SPACE;
823 if (state == MULTIPLE)
return false;
824 if (state == START) {
825 if (lastIndent == -1) lastIndent = thisIndent;
826 else if (lastIndent != thisIndent) equal_indent =
false;
828 state = (*c ==
'.' || *c ==
',') ? DOT :
CHAR;
833 equal_indent =
false;
837 foundIndentation = lastIndent-1;
846 if (!change)
return text;
848 size_t first = text.find_first_not_of(
' ');
849 if (first == string::npos)
return "";
852 int remove = -change;
854 return text.substr(
remove);
858 return string(change,
' ')+text;
864 size_t this_lineend = text.find(
'\n');
867 if (this_lineend == string::npos) {
871 result =
correctSpaces(text.substr(0, this_lineend), change);
873 while (this_lineend != string::npos) {
874 size_t next_lineend = text.find(
'\n', this_lineend+1);
875 if (next_lineend == string::npos) {
876 result = result+
"\n"+
correctSpaces(text.substr(this_lineend+1), change);
879 result = result+
"\n"+
correctSpaces(text.substr(this_lineend+1, next_lineend-this_lineend-1), change);
881 this_lineend = next_lineend;
888 size_t first = text.find_first_not_of(
' ');
889 if (first == string::npos)
return INT_MAX;
894 size_t this_lineend = text.find(
'\n');
895 size_t min_indent = INT_MAX;
897 if (this_lineend == string::npos) {
901 while (this_lineend != string::npos) {
902 size_t next_lineend = text.find(
'\n', this_lineend+1);
903 if (next_lineend == string::npos) {
904 min_indent =
min(min_indent,
countSpaces(text.substr(this_lineend+1)));
907 min_indent =
min(min_indent,
countSpaces(text.substr(this_lineend+1, next_lineend-this_lineend-1)));
909 this_lineend = next_lineend;
913 if (min_indent == INT_MAX) min_indent = 0;
921 ParagraphTree *brother;
930 string location_description() const
OVERRIDE {
return "in paragraph starting here"; }
933 ParagraphTree(Ostrings::const_iterator begin,
const Ostrings::const_iterator end)
940 string& text = otext;
944 size_t reststart = text.find(
'\n', 1);
946 if (reststart == 0) {
947 attach_warning(
"[internal] Paragraph starts with LF -> reflow calculation will probably fail");
950 if (reststart != string::npos) {
951 int rest_indent = -1;
952 string rest = text.substr(reststart);
958 size_t last = text.find_last_not_of(
' ', reststart-1);
959 bool is_header = last != string::npos && text[last] ==
':';
961 if (!is_header && rest_indent == (first_indent+8)) {
963 size_t textstart = text.find_first_not_of(
" \n");
972 int diff = rest_indent-first_indent;
979 attach_warning(
strf(
"[internal] unhandled: more indentation on the 1st line (diff=%i)", diff));
995 brother = buildParagraphTree(++begin, end);
998 void brothers_to_sons(ParagraphTree *new_brother);
1013 const char *res =
NULp;
1016 case ITEM: res =
"ITEM";
break;
1024 if (son) nodes += son->countTextNodes();
1025 if (brother) nodes += brother->countTextNodes();
1029 #if defined(DUMP_PARAGRAPHS)
1031 char *masknl(
const char *text) {
1033 for (
int i = 0; result[i]; ++i) {
1034 if (result[i] ==
'\n') result[i] =
'|';
1038 void dump(ostream& out,
int indent = 0) {
1041 char *mtext = masknl(otext.
as_string().c_str());
1042 out <<
"text='" << mtext <<
"'\n";
1047 out <<
"type='" << readable_type() <<
"' ";
1049 out <<
"enumeration='" << otext.
get_number() <<
"' ";
1051 out <<
"reflow='" << reflow <<
"' ";
1052 out <<
"indentation='" << indentation <<
"'\n";
1056 son->dump(out, indent+2);
1061 brother->dump(out, indent);
1064 #endif // DUMP_PARAGRAPHS
1067 static ParagraphTree* buildParagraphTree(Ostrings::const_iterator begin,
const Ostrings::const_iterator end) {
1068 if (begin == end)
return NULp;
1069 return new ParagraphTree(begin, end);
1073 const Ostrings& txt = sec.Content();
1074 if (txt.empty())
throw "attempt to build an empty ParagraphTree";
1075 return buildParagraphTree(txt.begin(), txt.end());
1081 (son && son->contains(that)) ||
1082 (brother && brother->contains(that));
1086 if (brother == before_this)
return this;
1087 if (!brother)
return NULp;
1088 return brother->predecessor(before_this);
1092 if (!brother) brother = new_brother;
1093 else brother->append(new_brother);
1097 return (other == brother) || (brother && brother->is_some_brother(other));
1101 ParagraphTree *removed =
this;
1102 ParagraphTree *after_pred =
this;
1110 if (after_pred->brother == after) {
1111 after_pred->brother =
NULp;
1114 after_pred = after_pred->brother;
1123 case ITEM:
return this;
1125 if (get_enumeration() == 1)
return this;
1129 if (brother)
return brother->firstListMember();
1134 if (indentation<previous.indentation)
return NULp;
1135 if (indentation == previous.indentation &&
get_type() == previous.get_type()) {
1137 if (get_enumeration() > previous.get_enumeration())
return this;
1140 if (!brother)
return NULp;
1141 return brother->nextListMemberAfter(previous);
1144 return brother ? brother->nextListMemberAfter(*
this) :
NULp;
1148 if (indentation < wanted_indentation)
return this;
1149 if (!brother)
return NULp;
1150 return brother->firstWithLessIndentThan(wanted_indentation);
1153 void format_indentations();
1154 void format_lists();
1157 static ParagraphTree* buildNewParagraph(
const string& Text,
size_t beginLineNo,
ParagraphType type) {
1159 S.push_back(
Ostring(Text, beginLineNo, type));
1160 return new ParagraphTree(S.begin(), S.end());
1162 ParagraphTree *xml_write_list_contents();
1163 ParagraphTree *xml_write_enum_contents();
1164 void xml_write_textblock();
1170 #if defined(DUMP_PARAGRAPHS)
1171 static void dump_paragraph(ParagraphTree *para) {
1173 para->dump(cout, 0);
1177 void ParagraphTree::brothers_to_sons(ParagraphTree *new_brother) {
1186 if (brother != new_brother) {
1189 son->attach_warning(
"Found unexpected son (in brothers_to_sons)");
1190 brother->attach_warning(
"while trying to transform paragraphs from here ..");
1191 new_brother->attach_warning(
".. to here ..");
1192 attach_warning(
".. into sons of this paragraph.");
1205 son = brother->takeAllInFrontOf(new_brother);
1206 brother = new_brother;
1216 void ParagraphTree::format_lists() {
1218 ParagraphTree *member = firstListMember();
1220 for (ParagraphTree *curr =
this; curr != member; curr = curr->brother) {
1222 if (curr->son) curr->son->format_lists();
1225 for (ParagraphTree *next = member->nextListMember();
1227 member = next, next = member->nextListMember())
1229 member->brothers_to_sons(next);
1232 if (member->son) member->son->format_lists();
1237 if (member->brother) {
1238 ParagraphTree *non_member = member->brother->firstWithLessIndentThan(member->indentation+1);
1239 member->brothers_to_sons(non_member);
1242 if (member->son) member->son->format_lists();
1243 if (member->brother) member->brother->format_lists();
1246 for (ParagraphTree *curr =
this; curr; curr = curr->brother) {
1248 if (curr->son) curr->son->format_lists();
1253 void ParagraphTree::format_indentations() {
1255 ParagraphTree *same_indent = brother->firstWithLessIndentThan(indentation+1);
1256 #if defined(WARN_POSSIBLY_WRONG_INDENTATION_CORRECTION)
1257 if (same_indent && indentation != same_indent->indentation) {
1258 same_indent->attach_warning(
"indentation is assumed to be same as ..");
1259 attach_warning(
".. here");
1262 brothers_to_sons(same_indent);
1263 if (brother) brother->format_indentations();
1266 if (son) son->format_indentations();
1305 return link_id[idx];
1309 size_t last_dot = name.find_last_of(
'.');
1310 if (last_dot == string::npos) {
1313 return name.c_str()+last_dot+1;
1320 if (ext && strcasecmp(ext,
"hlp") == 0) type =
LT_HLP;
1321 else if (link_target.find(
"http://") == 0) type =
LT_HTTP;
1322 else if (link_target.find(
"https://") == 0) type =
LT_HTTPS;
1323 else if (link_target.find(
"ftp://") == 0) type =
LT_FTP;
1324 else if (link_target.find(
"file://") == 0) type =
LT_FILE;
1325 else if (link_target.find(
'@') != string::npos) type =
LT_EMAIL;
1326 else if (ext && strcasecmp(ext,
"ps") == 0) type =
LT_PS;
1327 else if (ext && strcasecmp(ext,
"pdf") == 0) type =
LT_PDF;
1328 else if (link_target[0] ==
'#') type =
LT_TICKET;
1341 static string path[
PATHS] = {
"source/",
"genhelp/" };
1344 for (
size_t p = 0; p<
PATHS; p++) {
1345 string fullname = path[p]+helpname;
1346 if (stat(fullname.c_str(), &st) == 0) {
1358 if (located.empty()) {
1366 string msg =
string(
"Unknown link type (dest='")+dest+
"')";
1370 link.add_attribute(
"dest", dest);
1372 link.add_attribute(
"source_line", source_line);
1376 if (fullhelp.empty()) {
1377 link.add_attribute(
"missing",
"1");
1378 string deadlink =
strf(
"Dead link to '%s'", dest.c_str());
1389 size_t found = text.find(
"LINK{", 0);
1390 if (found != string::npos) {
1391 size_t inside_link = found+5;
1392 size_t close = text.find(
'}', inside_link);
1394 if (close == string::npos)
throw "unclosed 'LINK{}'";
1396 string link_target = text.substr(inside_link, close-inside_link);
1398 string dest = link_target;
1403 XML_Tag
link(
"LINK");
1404 link.set_on_extra_line(
false);
1416 size_t hashpos = text.find(
'#');
1417 if (hashpos == string::npos) {
1421 if (!isdigit(text[hashpos+1])) {
1422 size_t afterhash = hashpos+1;
1424 text.substr(0, afterhash) +
1428 size_t hashlength = 2;
1429 while (isdigit(text[hashpos+hashlength])) ++hashlength;
1432 text.substr(0, hashpos) +
1434 text.substr(hashpos, hashlength) +
1444 void ParagraphTree::xml_write_textblock() {
1445 XML_Tag textblock(
"T");
1446 textblock.add_attribute(
"reflow", reflow ?
"1" :
"0");
1450 const string& text = otext;
1461 ParagraphTree *ParagraphTree::xml_write_list_contents() {
1463 #if defined(WARN_FIXED_LAYOUT_LIST_ELEMENTS)
1464 if (!reflow) attach_warning(
"ITEM not reflown (check output)");
1467 XML_Tag entry(
"ENTRY");
1468 entry.add_attribute(
"item",
"1");
1469 xml_write_textblock();
1470 if (son) son->xml_write();
1472 if (brother && brother->is_itemlist_member()) {
1473 return brother->xml_write_list_contents();
1477 ParagraphTree *ParagraphTree::xml_write_enum_contents() {
1479 #if defined(WARN_FIXED_LAYOUT_LIST_ELEMENTS)
1480 if (!reflow) attach_warning(
"ENUMERATED not reflown (check output)");
1483 XML_Tag entry(
"ENTRY");
1484 switch (get_enum_type()) {
1486 entry.add_attribute(
"enumerated",
strf(
"%i", get_enumeration()));
1489 entry.add_attribute(
"enumerated",
strf(
"%c",
'A'-1+get_enumeration()));
1492 entry.add_attribute(
"enumerated",
strf(
"%c",
'a'-1+get_enumeration()));
1498 xml_write_textblock();
1499 if (son) son->xml_write();
1501 if (brother && brother->get_enumeration()) {
1502 int diff = brother->get_enumeration()-get_enumeration();
1504 attach_warning(
"Non-consecutive enumeration detected between here..");
1505 brother->attach_warning(
".. and here");
1507 return brother->xml_write_enum_contents();
1512 void ParagraphTree::xml_write() {
1514 ParagraphTree *next =
NULp;
1515 if (get_enumeration()) {
1516 XML_Tag enu(
"ENUM");
1517 if (get_enumeration() != 1) {
1518 attach_warning(
strf(
"First enum starts with '%u.' (maybe previous enum was not detected)", get_enumeration()));
1520 next = xml_write_enum_contents();
1521 #if defined(WARN_LONESOME_ENUM_ELEMENTS)
1522 if (next == brother) attach_warning(
"Suspicious single-element-ENUM");
1525 else if (is_itemlist_member()) {
1526 XML_Tag list(
"LIST");
1527 next = xml_write_list_contents();
1528 #if defined(WARN_LONESOME_LIST_ELEMENTS)
1529 if (next == brother) attach_warning(
"Suspicious single-element-LIST");
1535 xml_write_textblock();
1536 if (son) son->xml_write();
1540 if (next) next->xml_write();
1542 catch (
string& err) {
throw attached_message(err); }
1543 catch (
const char *err) {
throw attached_message(err); }
1547 for (Links::const_iterator
s = links.begin();
s != links.end(); ++
s) {
1557 size_t lf = paragraph.find(
'\n', pos);
1558 if (lf == string::npos)
break;
1561 if (lf>0 && paragraph[lf-1] ==
' ') {
1563 while (sp>=1 && paragraph[sp-1] ==
' ') --sp;
1565 paragraph.erase(sp, lf-sp);
1570 size_t ns = paragraph.find(
' ', lf);
1572 paragraph[lf] =
' ';
1576 size_t as = paragraph.find_first_not_of(
' ', ns);
1577 size_t ls = as == string::npos ? ns : as-1;
1578 paragraph.erase(lf, ls-lf);
1582 size_t ls = paragraph.find_last_not_of(
' ');
1583 if (ls == string::npos) {
1588 paragraph.erase(ls, paragraph.length()-ls);
1594 XML_Document xml(
"PAGE",
"arb_help.dtd", out);
1596 xml.skip_empty_tags =
true;
1597 xml.indentation_per_level = 2;
1599 xml.getRoot().add_attribute(
"name", page_name);
1601 xml.getRoot().add_attribute(
"edit_warning",
"devel");
1603 xml.getRoot().add_attribute(
"edit_warning",
"release");
1606 xml.getRoot().add_attribute(
"source",
inputfile.c_str());
1617 string titleText, subtitleText;
1619 const Ostrings&
T =
title.Content();
1620 Ostrings::const_iterator
s = T.begin();
1622 if (s != T.end()) titleText = *s++;
1624 bool subtitleAdded =
false;
1625 for (; s != T.end(); ++
s) {
1627 throw s->attached_message(
"wrong paragraph type (plain text expected)");
1629 string text = s->as_string();
1630 if (!text.empty()) {
1632 if (!text.empty()) {
1633 if (subtitleAdded)
throw s->attached_message(
"only one subtitle accepted");
1638 s->attach_warning(
strf(
"subtitle too verbose (max. %i chars allowed; found %zu)",
MAX_SUBTITLE_CHARS, text.length()));
1640 subtitleText = text;
1641 subtitleAdded =
true;
1647 XML_Tag title_tag(
"TITLE"); {
XML_Text text(titleText); }
1649 if (!subtitleText.empty()) {
1650 XML_Tag title_tag(
"SUBTITLE"); {
XML_Text text(subtitleText); }
1654 catch (
string& err) {
throw title.attached_message(err); }
1655 catch (
const char *err) {
throw title.attached_message(err); }
1657 for (SectionList::const_iterator sec = sections.begin(); sec != sections.end(); ++sec) {
1659 XML_Tag section_tag(
"SECTION");
1660 section_tag.add_attribute(
"name", sec->getName());
1662 ParagraphTree *ptree = ParagraphTree::buildParagraphTree(*sec);
1665 size_t textnodes = ptree->countTextNodes();
1667 #if defined(DUMP_PARAGRAPHS)
1668 cout <<
"Dump of section '" << sec->getName() <<
"' (before format_lists):\n";
1670 cout <<
"----------------------------------------\n";
1673 ptree->format_lists();
1675 #if defined(DUMP_PARAGRAPHS)
1676 cout <<
"Dump of section '" << sec->getName() <<
"' (after format_lists):\n";
1678 cout <<
"----------------------------------------\n";
1681 size_t textnodes2 = ptree->countTextNodes();
1685 ptree->format_indentations();
1687 #if defined(DUMP_PARAGRAPHS)
1688 cout <<
"Dump of section '" << sec->getName() <<
"' (after format_indentations):\n";
1690 cout <<
"----------------------------------------\n";
1693 size_t textnodes3 = ptree->countTextNodes();
1701 catch (
string& err) {
throw sec->attached_message(err); }
1702 catch (
const char *err) {
throw sec->attached_message(err); }
1707 for (SectionList::const_iterator sec = sections.begin(); sec != sections.end(); ++sec) {
1709 const Ostrings&
s = sec->Content();
1711 for (Ostrings::const_iterator
li = s.begin();
li != s.end(); ++
li) {
1712 const string&
line = *
li;
1716 size_t found = line.find(
"LINK{", start);
1717 if (found == string::npos)
break;
1719 size_t close = line.find(
'}', found);
1720 if (close == string::npos)
break;
1722 string link_target = line.substr(found, close-found);
1724 if (link_target.find(
"http://") == string::npos &&
1725 link_target.find(
"https://")== string::npos &&
1726 link_target.find(
"ftp://") == string::npos &&
1727 link_target.find(
"file://") == string::npos &&
1728 link_target.find(
'@') == string::npos)
1730 check_self_ref(link_target);
1738 auto_references.push_back(
Link(link_target,
li->line_number()));
1740 catch (
string& err) {
1748 catch (
string& err) {
1749 throw sec->attached_message(
"'"+err+
"' while scanning LINK{}");
1754 static void show_err(
const string& err,
size_t lineno,
const string& helpfile) {
1755 if (err.find(helpfile+
':') != string::npos) {
1758 else if (lineno == NO_LINENUMBER_INFO) {
1759 cerr << helpfile <<
":1: [in unknown line] " << err;
1762 cerr << helpfile <<
":" << lineno <<
": " << err;
1773 for (list<LineAttachedMessage>::const_iterator wi = warnings.begin(); wi != warnings.end(); ++wi) {
1784 cerr <<
"Usage: arb_help2xml <ARB helpfile> <XML output>\n";
1794 string xml_output = argv[2];
1797 ifstream in(arb_help.c_str());
1804 FILE *out = std::fopen(xml_output.c_str(),
"wt");
1805 if (!out)
throw string(
"Can't open '")+xml_output+
'\'';
1809 size_t slash = arb_help.find(
'/');
1810 size_t dot = arb_help.find_last_of(
'.');
1812 if (slash == string::npos || dot == string::npos) {
1813 throw string(
"parameter <ARB helpfile> has to be in format 'source/name.hlp' (not '"+arb_help+
"')");
1816 string page_name(arb_help, slash+1, dot-slash-1);
1822 remove(xml_output.c_str());
1852 #define TEST_REMOVE_LF_AND_INDENTATION(i,want) TEST_EXPECT_EQUAL(remove_LF_and_indentation(i).c_str(), want)
1853 #define TEST_REMOVE_LF_AND_INDENTATION__BROKEN(i,want,got) TEST_EXPECT_EQUAL__BROKEN(remove_LF_and_indentation(i).c_str(), want, got)
1855 void TEST_remove_LF_and_indentation() {
1856 TEST_REMOVE_LF_AND_INDENTATION(
"",
1859 TEST_REMOVE_LF_AND_INDENTATION(
" \n \n \n ",
1861 TEST_REMOVE_LF_AND_INDENTATION(
"hello\nNewline",
1863 TEST_REMOVE_LF_AND_INDENTATION(
"hello\nNewline\n 1\n2 \n 3 \n4\n5\n 6 \n 7 \n 8\n",
1864 "hello Newline 1 2 3 4 5 6 7 8");
1866 TEST_REMOVE_LF_AND_INDENTATION(
"Visualization of Three-dimensional\n structure of small subunit (16S) rRNA",
1867 "Visualization of Three-dimensional structure of small subunit (16S) rRNA");
1870 static arb_test::match_expectation help_file_compiles(
const char *helpfile,
const char *expected_title,
const char *expected_error_part) {
1874 ifstream in(helpfile);
1883 FILE *devnul = fopen(
"/dev/null",
"wt");
1891 if (expected_error_part) {
1899 const Ostrings& title_strings = title.Content();
1911 return all().ofgroup(expected);
1914 #define HELP_FILE_COMPILES(name,expTitle) TEST_EXPECTATION(help_file_compiles(name,expTitle,NULp))
1915 #define HELP_FILE_COMPILE_ERROR(name,expError) TEST_EXPECTATION(help_file_compiles(name,NULp,expError))
1917 void TEST_hlp2xml_conversion() {
1920 HELP_FILE_COMPILES(
"genhelp/agde_treepuzzle.hlp",
"treepuzzle");
1922 HELP_FILE_COMPILES(
"source/markbyref.hlp",
"Mark by reference");
1923 HELP_FILE_COMPILES(
"source/ad_align.hlp",
"Alignment Administration");
1924 HELP_FILE_COMPILES(
"genhelp/copyright.hlp",
"Copyrights and licenses");
1928 HELP_FILE_COMPILE_ERROR(
"akjsdlkad.hlp",
"Can't read from");
1935 void TEST_hlp2xml_output() {
1936 string tested_helpfile[] = {
1940 string HELP_SOURCE =
"../../HELP_SOURCE/";
1941 string LIB =
"../../lib/";
1942 string EXPECTED =
"help/";
1944 for (
size_t i = 0; i<
ARRAY_ELEMS(tested_helpfile); ++i) {
1945 string xml = HELP_SOURCE +
"Xml/" + tested_helpfile[i] +
".xml";
1946 string html = LIB +
"help_html/" + tested_helpfile[i] +
".html";
1947 string hlp = LIB +
"help/" + tested_helpfile[i] +
".hlp";
1949 string xml_expected = EXPECTED + tested_helpfile[i] +
".xml";
1950 string html_expected = EXPECTED + tested_helpfile[i] +
".html";
1951 string hlp_expected = EXPECTED + tested_helpfile[i] +
".hlp";
1954 #if defined(TEST_AUTO_UPDATE)
1955 # if defined(NDEBUG)
1956 # error please use auto-update only in DEBUG mode
1958 TEST_COPY_FILE(xml.c_str(), xml_expected.c_str());
1959 TEST_COPY_FILE(html.c_str(), html_expected.c_str());
1960 TEST_COPY_FILE(hlp.c_str(), hlp_expected.c_str());
1962 #else // !defined(TEST_AUTO_UPDATE)
1965 int expected_xml_difflines = 0;
1966 int expected_hlp_difflines = 0;
1967 # else // !defined(DEBUG)
1968 int expected_xml_difflines = 1;
1969 int expected_hlp_difflines = 2;
1979 #if defined(PROTECT_HELP_VS_CHANGES)
1980 void TEST_protect_help_vs_changes() {
1989 bool do_help =
true;
1990 bool do_html =
true;
1992 const char *ref_WC =
"ARB.help.ref";
1996 string this_base =
"../..";
1997 string ref_base = this_base+
"/../"+ref_WC;
1998 string to_help =
"/lib/help";
1999 string to_html =
"/lib/help_html";
2000 string diff_help =
"diff -u "+ref_base+to_help+
" "+this_base+to_help;
2001 string diff_html =
"diff -u "+ref_base+to_html+
" "+this_base+to_html;
2006 if (do_html) update_cmd =
string(
"(")+diff_help+
";"+diff_html+
")";
2007 else update_cmd = diff_help;
2009 else if (do_html) update_cmd = diff_html;
2011 string patch =
"help_changes.patch";
2012 update_cmd +=
" >"+patch+
" ||true";
2014 string fail_on_change_cmd =
"test \"`cat "+patch+
" | grep -v '^Common subdirectories' | wc -l`\" = \"0\" || ( echo \"Error: Help changed\"; false)";
2021 #endif // UNIT_TESTS
static LinkType detectLinkType(const string &link_target)
GB_ERROR GBK_system(const char *system_command)
static void show_err(const string &err, size_t lineno, const string &helpfile)
const char * c_str() const
AliDataPtr format(AliDataPtr data, const size_t wanted_len, GB_ERROR &error)
int ARB_main(int argc, char *argv[])
return string(buffer, length)
static void dot(double **i, double **j, double **k)
void show_warnings(const string &helpfile)
EnumerationType get_enum_type() const
bool is_some_brother(const ParagraphTree *other) const
static bool shouldReflow(const string &s, int &foundIndentation)
static string correctIndentation(const string &text, int change)
bool is_itemlist_member() const
void writeXML(FILE *out, const string &page_name)
void attach_warning(const string &message) const
ParagraphTree * predecessor(ParagraphTree *before_this)
LineAttachedMessage attached_message(const string &message) const
const string & Target() const
char * ARB_strdup(const char *str)
void readHelp(istream &in, const string &filename)
static size_t scanMinIndentation(const string &text)
const char * extractKeyword(const char *line, string &keyword)
size_t line_number() const OVERRIDE
void warning(int warning_num, const char *warning_message)
const char * firstChar(const char *s)
static string locate_document(const string &docname)
char * strf(const char *format,...) __ATTR__FORMAT(1)
Ostring(const string &s, size_t line_no, ParagraphType type_, EnumerationType etype_, unsigned num)
void add_warning(const LineAttachedMessage &laMsg)
list< Section > SectionList
void print_XML_Text(const string &text, size_t lineNo)
void show_warning(const LineAttachedMessage &line_err, const string &helpfile)
static EnumerationType detectLineEnumType(string &line, unsigned &number)
const string & getName() const
unsigned get_enumeration() const
#define ARRAY_ELEMS(array)
virtual ~MessageAttachable()
static void print_XML_Text_expanding_links(const string &text, size_t lineNo)
string remove_LF_and_indentation(string paragraph)
void check_duplicates(const string &link, const Links &uplinks, const Links &references, bool add_warnings)
static HelixNrInfo * start
void check_specific_duplicates(const string &link, const Links &existing, bool add_warnings)
#define TEST_PUBLISH(testfunction)
LineAttachedMessage unattached_message(const string &message)
LineAttachedMessage(const string &message_, size_t lineno_)
AliDataPtr after(AliDataPtr data, size_t pos)
static int diff(int v1, int v2, int v3, int v4, int st, int en)
const Ostrings & Content() const
const char * eatWhitespace(const char *paragraph)
#define is_equal_to_NULL()
static EnumerationType startsWithLetter(string &s, unsigned &number)
void extractInternalLinks()
__ATTR__VFORMAT(1) static string vstrf(const char *format
static const char * link_id[]
const char * eatSpace(const char *line)
void setName(const string &name_)
string location_description() const OVERRIDE
va_list static argPtr size_t buf_size
XML_Document * the_XML_Document
void message(char *errortext)
static void error(const char *msg)
ParagraphTree * nextListMemberAfter(const ParagraphTree &previous)
static const char * knownSections[]
expectation_group & add(const expectation &e)
#define MAX_SUBTITLE_CHARS
static void show_error_and_warnings(const LineAttachedMessage &error, const string &helpfile)
static string autolink_ticket_references(const string &text)
const Section & get_title() const
static void add_link_attributes(XML_Tag &link, LinkType type, const string &dest, size_t source_line)
ParagraphType get_type() const
#define TEST_EXPECT_TEXTFILE_DIFFLINES_IGNORE_DATES(fgot, fwant, diff)
#define does_differ_from_NULL()
ParagraphTree * nextListMember() const
AW_selection_list * links
STATIC_ASSERT(ARRAY_ELEMS(knownSections)==KNOWN_SECTION_TYPES)
bool is_startof_itemlist_element(const char *contentStart)
#define TEST_EXPECT_ZERO(cond)
bool isWhitespace(char c)
static void warnAboutDuplicate(SectionList §ions)
void set_line_number(size_t lineNumber)
const string & Message() const
Link(const string &target_, size_t source_lineno_)
#define does_contain(val)
const string & as_string() const
ParagraphTree * takeAllInFrontOf(ParagraphTree *after)
Ostring(const string &s, size_t line_no, ParagraphType type_)
static ParagraphTree * buildParagraphTree(const Section &sec)
size_t get_lineno() const
static list< LineAttachedMessage > warnings
const size_t NO_LINENUMBER_INFO
static bool startsWithNumber(string &s, unsigned &number)
ParagraphTree * firstWithLessIndentThan(int wanted_indentation)
bool contains(ParagraphTree *that)
static string LinkType2id(LinkType type)
const char * name_only(const char *fullpath)
static string correctSpaces(const string &text, int change)
const EnumerationType & get_enum_type() const
__ATTR__FORMAT(1) static string strf(const char *format
Section(string name_, SectionType type_, size_t lineno_)
unsigned get_number() const
#define TEST_EXPECT_NO_ERROR(call)
bool isEmptyOrComment(const char *s)
const char * getExtension(const string &name)
void append(ParagraphTree *new_brother)
static void parseSection(Section &sec, const char *line, int indentation, Reader &reader)
const char * readable_type() const
void check_TODO(const char *, const Reader &)
#define TEST_EXPECT_TEXTFILE_DIFFLINES(fgot, fwant, diff)
void print_indent(int indent)
size_t SourceLineno() const
ParagraphTree * firstListMember()
const ParagraphType & get_type() const
static void create_top_links(const Links &links, const char *tag)
static string locate_helpfile(const string &helpname)
size_t countSpaces(const string &text)
size_t line_number() const OVERRIDE
AW_selection_list * uplinks
void pushParagraph(Section &sec, string ¶graph, size_t lineNo, ParagraphType &type, EnumerationType &etype, unsigned num)
SectionType get_type() const
GB_write_int const char s