33 #define h2x_assert(bed) arb_assert(bed)
36 #define WARN_FORMATTING_PROBLEMS
37 #define WARN_MISSING_HELP
43 #if defined(WARN_FORMATTING_PROBLEMS)
45 #define WARN_FIXED_LAYOUT_LIST_ELEMENTS
46 #define WARN_LONESOME_ENUM_ELEMENTS
56 #define MAX_LINE_LENGTH 200 // maximum length of lines in input stream
93 throw string(
"out of memory");
96 length = vsnprintf(buffer, buf_size, format, argPtr);
97 if (length < buf_size)
break;
100 buf_size += buf_size/2;
105 return string(buffer, length);
141 warnings.push_back(laMsg);
150 virtual string location_description()
const = 0;
154 string where = location_description();
176 string location_description() const
OVERRIDE {
return ""; }
181 if (in.eof()) eof =
true;
188 if (in.eof()) eof =
true;
189 else if (in.fail())
throw "line too long";
191 if (strchr(lineBuffer,
'\t')) {
194 for (
int o = 0; lineBuffer[o]; ++o) {
195 if (lineBuffer[o] ==
'\t') {
197 while (spaces--) lineBuffer2[o2++] =
' ';
200 lineBuffer2[o2++] = lineBuffer[o];
204 strcpy(lineBuffer, lineBuffer2);
207 char *
eol = strchr(lineBuffer, 0)-1;
208 while (eol >= lineBuffer && isspace(eol[0])) {
212 if (eol > lineBuffer) {
214 if (eol[0] ==
'-' && isalnum(eol[-1])) {
215 attach_warning(
"manual hyphenation detected");
227 if (readAgain) readAgain =
false;
229 return eof ?
NULp : lineBuffer;
284 operator const string&()
const {
return content; }
285 operator string&() {
return content; }
303 const char *
c_str()
const {
return content.c_str(); }
308 #if defined(WARN_MISSING_HELP)
310 if (strstr(line,
"@@@") || strstr(line,
"TODO")) {
316 #endif // WARN_MISSING_HELP
327 string location_description() const
OVERRIDE {
return string(
"in SECTION '")+name+
"'"; }
337 const Ostrings&
Content()
const {
return content; }
341 const string&
getName()
const {
return name; }
342 void setName(
const string& name_) { name = name_; }
352 size_t source_lineno;
355 Link(
const string& target_,
size_t source_lineno_) :
357 source_lineno(source_lineno_)
360 const string&
Target()
const {
return target; }
373 Links auto_references;
375 SectionList sections;
378 void check_self_ref(
const string&
link) {
379 size_t slash = inputfile.find(
'/');
380 if (slash != string::npos) {
381 if (inputfile.substr(slash+1) ==
link) {
382 throw string(
"Invalid link to self");
391 void readHelp(istream& in,
const string& filename);
392 void writeXML(FILE *out,
const string& page_name);
393 void extractInternalLinks();
398 inline bool isWhite(
char c) {
return c ==
' '; }
401 if (s[0] ==
'#')
return true;
402 for (
int off = 0; ; ++off) {
403 if (s[off] == 0)
return true;
414 const char *
space = strchr(line,
' ');
415 if (space && space>line) {
416 keyword =
string(line, 0, space-line);
422 return strchr(line, 0);
435 if (paragraph.length()) {
437 sec.Content().push_back(
Ostring(paragraph, lineNo, type, etype, num));
440 sec.Content().push_back(
Ostring(paragraph, lineNo, type));
456 (contentStart[0] ==
'-' ||
457 contentStart[0] ==
'*')
459 isspace(contentStart[1])
461 !(isspace(contentStart[2]) ||
462 contentStart[2] ==
'-');
465 #define MAX_ALLOWED_ENUM 99 // otherwise it starts interpreting years as enums
472 size_t off = s.find_first_not_of(
" \n");
473 if (off == string::npos)
return NONE;
474 if (!isalpha(s[off]))
return NONE;
479 number = s[off]-(etype ==
ALPHA_UPPER ?
'A' :
'a')+1;
484 if (s[off] !=
'.' && s[off] !=
')')
return NONE;
485 if (s[off+1] !=
' ')
return NONE;
489 while (s[off+1] ==
' ') ++off;
490 s.erase(astart, off-astart+1);
499 size_t off = s.find_first_not_of(
" \n");
500 if (off == string::npos)
return false;
501 if (!isdigit(s[off]))
return false;
503 size_t num_start = off;
506 for (; isdigit(s[off]); ++off) {
507 number = number*10 + (s[off]-
'0');
511 if (s[off] !=
'.' && s[off] !=
')')
return false;
512 if (s[off+1] !=
' ')
return false;
516 while (s[off+1] ==
' ') ++off;
517 s.erase(num_start, off-num_start+1);
528 string paragraph =
line;
529 size_t para_start_lineno = reader.
getLineNo();
535 unsigned last_alpha_num = -1;
544 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
561 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
564 const char *firstNonWhite =
firstChar(line);
568 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
570 Line[firstNonWhite-
line] =
' ';
578 if (foundNum == (last_alpha_num+1) || foundNum == 1) {
579 last_alpha_num = foundNum;
582 #if defined(WARN_IGNORED_ALPHA_ENUMS)
593 if (foundEtype !=
NONE) {
594 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
602 throw "Enumerations starting with zero are not supported";
608 if (paragraph.length()) {
609 paragraph = paragraph+
"\n"+Line;
612 paragraph =
string(
"\n")+Line;
618 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
620 if (sec.Content().size()>0 && indentation>0) {
622 spaces.reserve(indentation);
623 spaces.append(indentation,
' ');
625 string& ostr = sec.Content().front();
626 ostr =
string(
"\n") + spaces + ostr;
631 for (Links::const_iterator ex = existing.begin(); ex != existing.end(); ++ex) {
632 if (ex->Target() ==
link) {
633 if (add_warnings)
add_warning(
strf(
"First Link to '%s' was found here.", ex->Target().c_str()), ex->SourceLineno());
634 throw strf(
"Link to '%s' duplicated here.", link.c_str());
645 SectionList::iterator end = sections.end();
646 for (SectionList::iterator
s = sections.begin();
s != end; ++
s) {
647 const string& sname =
s->getName();
648 if (sname ==
"NOTES")
continue;
650 SectionList::iterator o =
s; ++o;
651 for (; o != end; ++o) {
652 if (sname == o->getName()) {
653 o->attach_warning(
"duplicated SECTION name");
654 if (seen.find(sname) == seen.end()) {
655 s->attach_warning(
"name was first used");
671 const char *
name_only = strrchr(filename.c_str(),
'/');
692 if (keyword ==
"UP") {
696 if (strcmp(name_only, rest) == 0)
throw "UP link to self";
701 else if (keyword ==
"SUB") {
705 if (strcmp(name_only, rest) == 0)
throw "SUB link to self";
710 else if (keyword ==
"TITLE") {
714 if (
title.Content().empty())
throw "empty TITLE not allowed";
716 const char *t =
title.Content().front().c_str();
718 if (strstr(t,
"Standard help file form")) {
719 throw strf(
"Illegal title for help file: '%s'", t);
723 if (keyword ==
"NOTE") keyword =
"NOTES";
724 if (keyword ==
"EXAMPLE") keyword =
"EXAMPLES";
725 if (keyword ==
"WARNING") keyword =
"WARNINGS";
730 if (knownSections[idx] == keyword) {
738 if (idx >= KNOWN_SECTION_TYPES)
throw strf(
"unknown keyword '%s'", keyword.c_str());
741 string section_name =
eatWhite(rest);
742 Section sec(section_name, stype, lineno);
744 sections.push_back(sec);
747 Section sec(keyword, stype, lineno);
750 sections.push_back(sec);
755 throw strf(
"Unhandled line");
767 enum { START,
CHAR, SPACE, MULTIPLE, DOT, DOTSPACE } state = START;
768 bool equal_indent =
true;
772 for (string::const_iterator c = s.begin(); c != s.end(); ++c, ++thisIndent) {
778 if (state == DOT || state == DOTSPACE) state = DOTSPACE;
779 else if (state == SPACE) state = MULTIPLE;
780 else if (state ==
CHAR) state = SPACE;
783 if (state == MULTIPLE)
return false;
784 if (state == START) {
785 if (lastIndent == -1) lastIndent = thisIndent;
786 else if (lastIndent != thisIndent) equal_indent =
false;
788 state = (*c ==
'.' || *c ==
',') ? DOT :
CHAR;
793 equal_indent =
false;
797 foundIndentation = lastIndent-1;
806 if (!change)
return text;
808 size_t first = text.find_first_not_of(
' ');
809 if (first == string::npos)
return "";
812 int remove = -change;
814 return text.substr(
remove);
818 return string(change,
' ')+text;
824 size_t this_lineend = text.find(
'\n');
827 if (this_lineend == string::npos) {
831 result =
correctSpaces(text.substr(0, this_lineend), change);
833 while (this_lineend != string::npos) {
834 size_t next_lineend = text.find(
'\n', this_lineend+1);
835 if (next_lineend == string::npos) {
836 result = result+
"\n"+
correctSpaces(text.substr(this_lineend+1), change);
839 result = result+
"\n"+
correctSpaces(text.substr(this_lineend+1, next_lineend-this_lineend-1), change);
841 this_lineend = next_lineend;
848 size_t first = text.find_first_not_of(
' ');
849 if (first == string::npos)
return INT_MAX;
854 size_t this_lineend = text.find(
'\n');
855 size_t min_indent = INT_MAX;
857 if (this_lineend == string::npos) {
861 while (this_lineend != string::npos) {
862 size_t next_lineend = text.find(
'\n', this_lineend+1);
863 if (next_lineend == string::npos) {
864 min_indent =
min(min_indent,
countSpaces(text.substr(this_lineend+1)));
867 min_indent =
min(min_indent,
countSpaces(text.substr(this_lineend+1, next_lineend-this_lineend-1)));
869 this_lineend = next_lineend;
873 if (min_indent == INT_MAX) min_indent = 0;
881 ParagraphTree *brother;
890 string location_description() const
OVERRIDE {
return "in paragraph starting here"; }
893 ParagraphTree(Ostrings::const_iterator begin,
const Ostrings::const_iterator end)
900 string& text = otext;
904 size_t reststart = text.find(
'\n', 1);
906 if (reststart == 0) {
907 attach_warning(
"[internal] Paragraph starts with LF -> reflow calculation will probably fail");
910 if (reststart != string::npos) {
911 int rest_indent = -1;
912 string rest = text.substr(reststart);
918 size_t last = text.find_last_not_of(
' ', reststart-1);
919 bool is_header = last != string::npos && text[last] ==
':';
921 if (!is_header && rest_indent == (first_indent+8)) {
923 size_t textstart = text.find_first_not_of(
" \n");
932 int diff = rest_indent-first_indent;
939 attach_warning(
strf(
"[internal] unhandled: more indentation on the 1st line (diff=%i)", diff));
955 brother = buildParagraphTree(++begin, end);
958 void brothers_to_sons(ParagraphTree *new_brother);
973 const char *res =
NULp;
976 case ITEM: res =
"ITEM";
break;
984 if (son) nodes += son->countTextNodes();
985 if (brother) nodes += brother->countTextNodes();
989 #if defined(DUMP_PARAGRAPHS)
991 char *masknl(
const char *text) {
993 for (
int i = 0; result[i]; ++i) {
994 if (result[i] ==
'\n') result[i] =
'|';
998 void dump(ostream& out,
int indent = 0) {
1001 char *mtext = masknl(otext.
as_string().c_str());
1002 out <<
"text='" << mtext <<
"'\n";
1007 out <<
"type='" << readable_type() <<
"' ";
1009 out <<
"enumeration='" << otext.
get_number() <<
"' ";
1011 out <<
"reflow='" << reflow <<
"' ";
1012 out <<
"indentation='" << indentation <<
"'\n";
1016 son->dump(out, indent+2);
1021 brother->dump(out, indent);
1024 #endif // DUMP_PARAGRAPHS
1027 static ParagraphTree* buildParagraphTree(Ostrings::const_iterator begin,
const Ostrings::const_iterator end) {
1028 if (begin == end)
return NULp;
1029 return new ParagraphTree(begin, end);
1033 const Ostrings& txt = sec.Content();
1034 if (txt.empty())
throw "attempt to build an empty ParagraphTree";
1035 return buildParagraphTree(txt.begin(), txt.end());
1041 (son && son->contains(that)) ||
1042 (brother && brother->contains(that));
1046 if (brother == before_this)
return this;
1047 if (!brother)
return NULp;
1048 return brother->predecessor(before_this);
1052 if (!brother) brother = new_brother;
1053 else brother->append(new_brother);
1057 return (other == brother) || (brother && brother->is_some_brother(other));
1061 ParagraphTree *removed =
this;
1062 ParagraphTree *after_pred =
this;
1070 if (after_pred->brother == after) {
1071 after_pred->brother =
NULp;
1074 after_pred = after_pred->brother;
1083 case ITEM:
return this;
1085 if (get_enumeration() == 1)
return this;
1089 if (brother)
return brother->firstListMember();
1094 if (indentation<previous.indentation)
return NULp;
1095 if (indentation == previous.indentation &&
get_type() == previous.get_type()) {
1097 if (get_enumeration() > previous.get_enumeration())
return this;
1100 if (!brother)
return NULp;
1101 return brother->nextListMemberAfter(previous);
1104 return brother ? brother->nextListMemberAfter(*
this) :
NULp;
1108 if (indentation < wanted_indentation)
return this;
1109 if (!brother)
return NULp;
1110 return brother->firstWithLessIndentThan(wanted_indentation);
1113 void format_indentations();
1114 void format_lists();
1117 static ParagraphTree* buildNewParagraph(
const string& Text,
size_t beginLineNo,
ParagraphType type) {
1119 S.push_back(
Ostring(Text, beginLineNo, type));
1120 return new ParagraphTree(S.begin(), S.end());
1122 ParagraphTree *xml_write_list_contents();
1123 ParagraphTree *xml_write_enum_contents();
1124 void xml_write_textblock();
1130 #if defined(DUMP_PARAGRAPHS)
1131 static void dump_paragraph(ParagraphTree *para) {
1133 para->dump(cout, 0);
1137 void ParagraphTree::brothers_to_sons(ParagraphTree *new_brother) {
1146 if (brother != new_brother) {
1149 son->attach_warning(
"Found unexpected son (in brothers_to_sons)");
1150 brother->attach_warning(
"while trying to transform paragraphs from here ..");
1151 new_brother->attach_warning(
".. to here ..");
1152 attach_warning(
".. into sons of this paragraph.");
1165 son = brother->takeAllInFrontOf(new_brother);
1166 brother = new_brother;
1176 void ParagraphTree::format_lists() {
1178 ParagraphTree *member = firstListMember();
1180 for (ParagraphTree *curr =
this; curr != member; curr = curr->brother) {
1182 if (curr->son) curr->son->format_lists();
1185 for (ParagraphTree *next = member->nextListMember();
1187 member = next, next = member->nextListMember())
1189 member->brothers_to_sons(next);
1192 if (member->son) member->son->format_lists();
1197 if (member->brother) {
1198 ParagraphTree *non_member = member->brother->firstWithLessIndentThan(member->indentation+1);
1199 member->brothers_to_sons(non_member);
1202 if (member->son) member->son->format_lists();
1203 if (member->brother) member->brother->format_lists();
1206 for (ParagraphTree *curr =
this; curr; curr = curr->brother) {
1208 if (curr->son) curr->son->format_lists();
1213 void ParagraphTree::format_indentations() {
1215 ParagraphTree *same_indent = brother->firstWithLessIndentThan(indentation+1);
1216 #if defined(WARN_POSSIBLY_WRONG_INDENTATION_CORRECTION)
1217 if (same_indent && indentation != same_indent->indentation) {
1218 same_indent->attach_warning(
"indentation is assumed to be same as ..");
1219 attach_warning(
".. here");
1222 brothers_to_sons(same_indent);
1223 if (brother) brother->format_indentations();
1226 if (son) son->format_indentations();
1262 return link_id[idx];
1266 size_t last_dot = name.find_last_of(
'.');
1267 if (last_dot == string::npos) {
1270 return name.c_str()+last_dot+1;
1277 if (ext && strcasecmp(ext,
"hlp") == 0) type =
LT_HLP;
1278 else if (link_target.find(
"http://") == 0) type =
LT_HTTP;
1279 else if (link_target.find(
"https://") == 0) type =
LT_HTTPS;
1280 else if (link_target.find(
"ftp://") == 0) type =
LT_FTP;
1281 else if (link_target.find(
"file://") == 0) type =
LT_FILE;
1282 else if (link_target.find(
'@') != string::npos) type =
LT_EMAIL;
1283 else if (ext && strcasecmp(ext,
"ps") == 0) type =
LT_PS;
1284 else if (ext && strcasecmp(ext,
"pdf") == 0) type =
LT_PDF;
1297 static string path[
PATHS] = {
"source/",
"genhelp/" };
1300 for (
size_t p = 0; p<
PATHS; p++) {
1301 string fullname = path[p]+helpname;
1302 if (stat(fullname.c_str(), &st) == 0) {
1314 if (located.empty()) {
1322 string msg =
string(
"Invalid link (dest='")+dest+
"')";
1326 link.add_attribute(
"dest", dest);
1328 link.add_attribute(
"source_line", source_line);
1332 if (fullhelp.empty()) {
1333 link.add_attribute(
"missing",
"1");
1334 string deadlink =
strf(
"Dead link to '%s'", dest.c_str());
1335 #if defined(DEVEL_RELEASE)
1337 #else // !defined(DEVEL_RELEASE)
1345 size_t found = text.find(
"LINK{", 0);
1346 if (found != string::npos) {
1347 size_t inside_link = found+5;
1348 size_t close = text.find(
'}', inside_link);
1350 if (close == string::npos)
throw "unclosed 'LINK{}'";
1352 string link_target = text.substr(inside_link, close-inside_link);
1354 string dest = link_target;
1359 XML_Tag
link(
"LINK");
1360 link.set_on_extra_line(
false);
1371 void ParagraphTree::xml_write_textblock() {
1372 XML_Tag textblock(
"T");
1373 textblock.add_attribute(
"reflow", reflow ?
"1" :
"0");
1377 const string& text = otext;
1388 ParagraphTree *ParagraphTree::xml_write_list_contents() {
1390 #if defined(WARN_FIXED_LAYOUT_LIST_ELEMENTS)
1391 if (!reflow) attach_warning(
"ITEM not reflown (check output)");
1394 XML_Tag entry(
"ENTRY");
1395 entry.add_attribute(
"item",
"1");
1396 xml_write_textblock();
1397 if (son) son->xml_write();
1399 if (brother && brother->is_itemlist_member()) {
1400 return brother->xml_write_list_contents();
1404 ParagraphTree *ParagraphTree::xml_write_enum_contents() {
1406 #if defined(WARN_FIXED_LAYOUT_LIST_ELEMENTS)
1407 if (!reflow) attach_warning(
"ENUMERATED not reflown (check output)");
1410 XML_Tag entry(
"ENTRY");
1411 switch (get_enum_type()) {
1413 entry.add_attribute(
"enumerated",
strf(
"%i", get_enumeration()));
1416 entry.add_attribute(
"enumerated",
strf(
"%c",
'A'-1+get_enumeration()));
1419 entry.add_attribute(
"enumerated",
strf(
"%c",
'a'-1+get_enumeration()));
1425 xml_write_textblock();
1426 if (son) son->xml_write();
1428 if (brother && brother->get_enumeration()) {
1429 int diff = brother->get_enumeration()-get_enumeration();
1431 attach_warning(
"Non-consecutive enumeration detected between here..");
1432 brother->attach_warning(
".. and here");
1434 return brother->xml_write_enum_contents();
1439 void ParagraphTree::xml_write() {
1441 ParagraphTree *next =
NULp;
1442 if (get_enumeration()) {
1443 XML_Tag enu(
"ENUM");
1444 if (get_enumeration() != 1) {
1445 attach_warning(
strf(
"First enum starts with '%u.' (maybe previous enum was not detected)", get_enumeration()));
1447 next = xml_write_enum_contents();
1448 #if defined(WARN_LONESOME_ENUM_ELEMENTS)
1449 if (next == brother) attach_warning(
"Suspicious single-element-ENUM");
1452 else if (is_itemlist_member()) {
1453 XML_Tag list(
"LIST");
1454 next = xml_write_list_contents();
1455 #if defined(WARN_LONESOME_LIST_ELEMENTS)
1456 if (next == brother) attach_warning(
"Suspicious single-element-LIST");
1462 xml_write_textblock();
1463 if (son) son->xml_write();
1467 if (next) next->xml_write();
1469 catch (
string& err) {
throw attached_message(err); }
1470 catch (
const char *err) {
throw attached_message(err); }
1474 for (Links::const_iterator
s = links.begin();
s != links.end(); ++
s) {
1481 XML_Document xml(
"PAGE",
"arb_help.dtd", out);
1483 xml.skip_empty_tags =
true;
1484 xml.indentation_per_level = 2;
1486 xml.getRoot().add_attribute(
"name", page_name);
1488 xml.getRoot().add_attribute(
"edit_warning",
"devel");
1490 xml.getRoot().add_attribute(
"edit_warning",
"release");
1493 xml.getRoot().add_attribute(
"source",
inputfile.c_str());
1504 XML_Tag title_tag(
"TITLE");
1505 const Ostrings&
T =
title.Content();
1506 for (Ostrings::const_iterator
s = T.begin();
s != T.end(); ++
s) {
1507 if (
s != T.begin()) {
XML_Text text(
"\n"); }
1512 for (SectionList::const_iterator sec = sections.begin(); sec != sections.end(); ++sec) {
1514 XML_Tag section_tag(
"SECTION");
1515 section_tag.add_attribute(
"name", sec->getName());
1517 ParagraphTree *ptree = ParagraphTree::buildParagraphTree(*sec);
1520 size_t textnodes = ptree->countTextNodes();
1522 #if defined(DUMP_PARAGRAPHS)
1523 cout <<
"Dump of section '" << sec->getName() <<
"' (before format_lists):\n";
1525 cout <<
"----------------------------------------\n";
1528 ptree->format_lists();
1530 #if defined(DUMP_PARAGRAPHS)
1531 cout <<
"Dump of section '" << sec->getName() <<
"' (after format_lists):\n";
1533 cout <<
"----------------------------------------\n";
1536 size_t textnodes2 = ptree->countTextNodes();
1540 ptree->format_indentations();
1542 #if defined(DUMP_PARAGRAPHS)
1543 cout <<
"Dump of section '" << sec->getName() <<
"' (after format_indentations):\n";
1545 cout <<
"----------------------------------------\n";
1548 size_t textnodes3 = ptree->countTextNodes();
1556 catch (
string& err) {
throw sec->attached_message(err); }
1557 catch (
const char *err) {
throw sec->attached_message(err); }
1562 for (SectionList::const_iterator sec = sections.begin(); sec != sections.end(); ++sec) {
1564 const Ostrings&
s = sec->Content();
1566 for (Ostrings::const_iterator
li = s.begin();
li != s.end(); ++
li) {
1567 const string&
line = *
li;
1571 size_t found = line.find(
"LINK{", start);
1572 if (found == string::npos)
break;
1574 size_t close = line.find(
'}', found);
1575 if (close == string::npos)
break;
1577 string link_target = line.substr(found, close-found);
1579 if (link_target.find(
"http://") == string::npos &&
1580 link_target.find(
"https://")== string::npos &&
1581 link_target.find(
"ftp://") == string::npos &&
1582 link_target.find(
"file://") == string::npos &&
1583 link_target.find(
'@') == string::npos)
1585 check_self_ref(link_target);
1593 auto_references.push_back(
Link(link_target, sec->line_number()));
1595 catch (
string& err) {
1603 catch (
string& err) {
1604 throw sec->attached_message(
"'"+err+
"' while scanning LINK{}");
1609 static void show_err(
const string& err,
size_t lineno,
const string& helpfile) {
1610 if (err.find(helpfile+
':') != string::npos) {
1613 else if (lineno == NO_LINENUMBER_INFO) {
1614 cerr << helpfile <<
":1: [in unknown line] " << err;
1617 cerr << helpfile <<
":" << lineno <<
": " << err;
1628 for (list<LineAttachedMessage>::const_iterator wi = warnings.begin(); wi != warnings.end(); ++wi) {
1639 cerr <<
"Usage: arb_help2xml <ARB helpfile> <XML output>\n";
1649 string xml_output = argv[2];
1652 ifstream in(arb_help.c_str());
1659 FILE *out = std::fopen(xml_output.c_str(),
"wt");
1660 if (!out)
throw string(
"Can't open '")+xml_output+
'\'';
1664 size_t slash = arb_help.find(
'/');
1665 size_t dot = arb_help.find_last_of(
'.');
1667 if (slash == string::npos || dot == string::npos) {
1668 throw string(
"parameter <ARB helpfile> has to be in format 'source/name.hlp' (not '"+arb_help+
"')");
1671 string page_name(arb_help, slash+1, dot-slash-1);
1677 remove(xml_output.c_str());
1704 static arb_test::match_expectation help_file_compiles(
const char *helpfile,
const char *expected_title,
const char *expected_error_part) {
1708 ifstream in(helpfile);
1717 FILE *devnul = fopen(
"/dev/null",
"wt");
1725 if (expected_error_part) {
1733 const Ostrings& title_strings = title.Content();
1745 return all().ofgroup(expected);
1748 #define HELP_FILE_COMPILES(name,expTitle) TEST_EXPECTATION(help_file_compiles(name,expTitle,NULp))
1749 #define HELP_FILE_COMPILE_ERROR(name,expError) TEST_EXPECTATION(help_file_compiles(name,NULp,expError))
1751 void TEST_hlp2xml_conversion() {
1754 HELP_FILE_COMPILES(
"genhelp/agde_treepuzzle.hlp",
"treepuzzle");
1756 HELP_FILE_COMPILES(
"source/markbyref.hlp",
"Mark by reference");
1757 HELP_FILE_COMPILES(
"source/ad_align.hlp",
"Alignment Administration");
1758 HELP_FILE_COMPILES(
"genhelp/copyright.hlp",
"Copyrights and licenses");
1760 HELP_FILE_COMPILE_ERROR(
"akjsdlkad.hlp",
"Can't read from");
1767 void TEST_hlp2xml_output() {
1768 string tested_helpfile[] = {
1772 string HELP_SOURCE =
"../../HELP_SOURCE/";
1773 string LIB =
"../../lib/";
1774 string EXPECTED =
"help/";
1776 for (
size_t i = 0; i<
ARRAY_ELEMS(tested_helpfile); ++i) {
1777 string xml = HELP_SOURCE +
"Xml/" + tested_helpfile[i] +
".xml";
1778 string html = LIB +
"help_html/" + tested_helpfile[i] +
".html";
1779 string hlp = LIB +
"help/" + tested_helpfile[i] +
".hlp";
1781 string xml_expected = EXPECTED + tested_helpfile[i] +
".xml";
1782 string html_expected = EXPECTED + tested_helpfile[i] +
".html";
1783 string hlp_expected = EXPECTED + tested_helpfile[i] +
".hlp";
1786 #if defined(TEST_AUTO_UPDATE)
1787 # if defined(NDEBUG)
1788 # error please use auto-update only in DEBUG mode
1790 TEST_COPY_FILE(xml.c_str(), xml_expected.c_str());
1791 TEST_COPY_FILE(html.c_str(), html_expected.c_str());
1792 TEST_COPY_FILE(hlp.c_str(), hlp_expected.c_str());
1794 #else // !defined(TEST_AUTO_UPDATE)
1797 int expected_xml_difflines = 0;
1798 int expected_hlp_difflines = 0;
1799 # else // !defined(DEBUG)
1800 int expected_xml_difflines = 1;
1801 int expected_hlp_difflines = 2;
1811 #if defined(PROTECT_HELP_VS_CHANGES)
1812 void TEST_protect_help_vs_changes() {
1821 bool do_help =
true;
1822 bool do_html =
true;
1824 const char *ref_WC =
"ARB.help.ref";
1828 string this_base =
"../..";
1829 string ref_base = this_base+
"/../"+ref_WC;
1830 string to_help =
"/lib/help";
1831 string to_html =
"/lib/help_html";
1832 string diff_help =
"diff -u "+ref_base+to_help+
" "+this_base+to_help;
1833 string diff_html =
"diff -u "+ref_base+to_html+
" "+this_base+to_html;
1838 if (do_html) update_cmd =
string(
"(")+diff_help+
";"+diff_html+
")";
1839 else update_cmd = diff_help;
1841 else if (do_html) update_cmd = diff_html;
1843 string patch =
"help_changes.patch";
1844 update_cmd +=
" >"+patch+
" ||true";
1846 string fail_on_change_cmd =
"test \"`cat "+patch+
" | grep -v '^Common subdirectories' | wc -l`\" = \"0\" || ( echo \"Error: Help changed\"; false)";
1853 #endif // UNIT_TESTS
static LinkType detectLinkType(const string &link_target)
GB_ERROR GBK_system(const char *system_command)
const char * eatWhite(const char *line)
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 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)
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
#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[]
void setName(const string &name_)
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)
static void show_error_and_warnings(const LineAttachedMessage &error, const string &helpfile)
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)
static void warnAboutDuplicate(SectionList §ions)
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)
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