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;
263 unsigned preformatted_width;
269 static unsigned current_preformatted_width;
270 static unsigned current_preformatted_blocks;
272 void check_auto_unpreformat() {
273 if (current_preformatted_blocks && !--current_preformatted_blocks) {
274 current_preformatted_width = 0;
284 preformatted_width(current_preformatted_width),
288 check_auto_unpreformat();
294 preformatted_width(current_preformatted_width),
301 check_auto_unpreformat();
305 current_preformatted_width = allowed;
309 current_preformatted_blocks = preformatted_blocks;
316 operator const string&()
const {
return content; }
317 operator string&() {
return content; }
335 return preformatted_width;
339 const char *
c_str()
const {
return content.c_str(); }
341 unsigned Ostring::current_preformatted_width = 0;
342 unsigned Ostring::current_preformatted_blocks = 0;
346 #if defined(WARN_MISSING_HELP)
348 if (strstr(line,
"@@@") || strstr(line,
"TODO")) {
354 #endif // WARN_MISSING_HELP
365 string location_description() const
OVERRIDE {
return string(
"in SECTION '")+name+
"'"; }
375 const Ostrings&
Content()
const {
return content; }
379 const string&
getName()
const {
return name; }
380 void setName(
const string& name_) { name = name_; }
392 size_t source_lineno;
395 Link(
const string& target_,
size_t source_lineno_) :
397 source_lineno(source_lineno_)
400 const string&
Target()
const {
return target; }
412 Links auto_references;
414 SectionList sections;
417 void check_self_ref(
const string&
link) {
418 size_t slash = inputfile.find(
'/');
419 if (slash != string::npos) {
420 if (inputfile.substr(slash+1) ==
link) {
421 throw string(
"Invalid link to self");
430 void readHelp(istream& in,
const string& filename);
431 void writeXML(FILE *out,
const string& page_name);
432 void extractInternalLinks();
437 inline bool isSpace(
char c) {
return c ==
' '; }
445 for (
int off = 0; ; ++off) {
446 if (s[off] == 0)
return true;
454 const int DEFAULT_WIDTH = 91;
459 const char *KEYWORD =
"PREFORMATTED ";
460 const char *found = strstr(line, KEYWORD);
462 const char *rest = found + strlen(KEYWORD);
463 if (strcmp(rest,
"RESET") == 0) {
467 else if (strncmp(rest,
"WIDTH ",
WLEN) == 0) {
468 const char *rest2 = rest +
WLEN;
469 int width = atoi(rest2);
471 if (strncmp(rest2,
"DEFAULT", 7) == 0) {
472 width = DEFAULT_WIDTH;
475 throw strf(
"invalid width %i in control comment '%s'", width, line);
481 else if (strcmp(rest,
"1") == 0) {
486 throw strf(
"invalid control comment '%s' (while parsing at '%s')", line, rest);
496 const char *
space = strchr(line,
' ');
497 if (space && space>line) {
498 keyword =
string(line, 0, space-line);
504 return strchr(line, 0);
522 if (paragraph.length()) {
524 sec.Content().push_back(
Ostring(paragraph, lineNo, type, etype, num));
527 sec.Content().push_back(
Ostring(paragraph, lineNo, type));
543 (contentStart[0] ==
'-' ||
544 contentStart[0] ==
'*')
546 isspace(contentStart[1])
548 !(isspace(contentStart[2]) ||
549 contentStart[2] ==
'-');
552 #define MAX_ALLOWED_ENUM 99 // otherwise it starts interpreting years as enums
559 size_t off = s.find_first_not_of(
" \n");
560 if (off == string::npos)
return NONE;
561 if (!isalpha(s[off]))
return NONE;
566 number = s[off]-(etype ==
ALPHA_UPPER ?
'A' :
'a')+1;
571 if (s[off] !=
'.' && s[off] !=
')')
return NONE;
572 if (s[off+1] !=
' ')
return NONE;
576 while (s[off+1] ==
' ') ++off;
577 s.erase(astart, off-astart+1);
586 size_t off = s.find_first_not_of(
" \n");
587 if (off == string::npos)
return false;
588 if (!isdigit(s[off]))
return false;
590 size_t num_start = off;
593 for (; isdigit(s[off]); ++off) {
594 number = number*10 + (s[off]-
'0');
598 if (s[off] !=
'.' && s[off] !=
')')
return false;
599 if (s[off+1] !=
' ')
return false;
603 while (s[off+1] ==
' ') ++off;
604 s.erase(num_start, off-num_start+1);
615 string paragraph =
line;
616 size_t para_start_lineno = reader.
getLineNo();
620 sec.set_line_number(para_start_lineno);
627 unsigned last_alpha_num = -1;
636 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
654 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
657 const char *firstNonWhite =
firstChar(line);
661 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
663 Line[firstNonWhite-
line] =
' ';
671 if (foundNum == (last_alpha_num+1) || foundNum == 1) {
672 last_alpha_num = foundNum;
675 #if defined(WARN_IGNORED_ALPHA_ENUMS)
686 if (foundEtype !=
NONE) {
687 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
695 throw "Enumerations starting with zero are not supported";
701 if (paragraph.length()) {
702 paragraph = paragraph+
"\n"+Line;
705 paragraph =
string(
"\n")+Line;
711 pushParagraph(sec, paragraph, para_start_lineno, type, etype, num);
713 if (sec.Content().size()>0 && indentation>0) {
715 spaces.reserve(indentation);
716 spaces.append(indentation,
' ');
718 string& ostr = sec.Content().front();
719 ostr =
string(
"\n") + spaces + ostr;
724 for (Links::const_iterator ex = existing.begin(); ex != existing.end(); ++ex) {
725 if (ex->Target() ==
link) {
726 if (add_warnings)
add_warning(
strf(
"First Link to '%s' was found here.", ex->Target().c_str()), ex->SourceLineno());
727 throw strf(
"Link to '%s' duplicated here.", link.c_str());
738 SectionList::iterator end = sections.end();
739 for (SectionList::iterator
s = sections.begin();
s != end; ++
s) {
740 const string& sname =
s->getName();
741 if (sname ==
"NOTES")
continue;
743 SectionList::iterator o =
s; ++o;
744 for (; o != end; ++o) {
745 if (sname == o->getName()) {
746 o->attach_warning(
"duplicated SECTION name");
747 if (seen.find(sname) == seen.end()) {
748 s->attach_warning(
"name was first used");
764 const char *
name_only = strrchr(filename.c_str(),
'/');
786 if (keyword ==
"UP") {
790 if (strcmp(name_only, rest) == 0)
throw "UP link to self";
795 else if (keyword ==
"SUB") {
799 if (strcmp(name_only, rest) == 0)
throw "SUB link to self";
804 else if (keyword ==
"TITLE") {
808 if (
title.Content().empty())
throw "empty TITLE not allowed";
810 const string& t =
title.Content().front();
811 if (t.find(
"Standard help file form") != string::npos) {
812 throw strf(
"Illegal title for help file: '%s'", t.c_str());
815 const size_t len = t.length();
818 size_t last_alnum_pos = len-1;
819 while (!isalnum(t[last_alnum_pos])) {
825 const size_t ignored = len-last_alnum_pos;
827 title.attach_warning(
strf(
"TITLE too verbose (max. %i chars allowed; found %zu%s)",
830 ignored ?
strf(
"; acceptable trailing chars: %zu", ignored).c_str() :
""
836 if (keyword ==
"NOTE") keyword =
"NOTES";
837 if (keyword ==
"EXAMPLE") keyword =
"EXAMPLES";
838 if (keyword ==
"WARNING") keyword =
"WARNINGS";
843 if (knownSections[idx] == keyword) {
851 if (idx >= KNOWN_SECTION_TYPES)
throw strf(
"unknown keyword '%s'", keyword.c_str());
854 string section_name =
eatSpace(rest);
855 Section sec(section_name, stype, lineno);
857 sections.push_back(sec);
860 Section sec(keyword, stype, lineno);
863 sections.push_back(sec);
868 throw strf(
"Unhandled line");
880 enum { START,
CHAR, SPACE, MULTIPLE, DOT, DOTSPACE } state = START;
881 bool equal_indent =
true;
885 for (string::const_iterator c = s.begin(); c != s.end(); ++c, ++thisIndent) {
891 if (state == DOT || state == DOTSPACE) state = DOTSPACE;
892 else if (state == SPACE) state = MULTIPLE;
893 else if (state ==
CHAR) state = SPACE;
896 if (state == MULTIPLE)
return false;
897 if (state == START) {
898 if (lastIndent == -1) lastIndent = thisIndent;
899 else if (lastIndent != thisIndent) equal_indent =
false;
901 state = (*c ==
'.' || *c ==
',') ? DOT :
CHAR;
906 equal_indent =
false;
910 foundIndentation = lastIndent-1;
919 if (!change)
return text;
921 size_t first = text.find_first_not_of(
' ');
922 if (first == string::npos)
return "";
925 int remove = -change;
927 return text.substr(
remove);
931 return string(change,
' ')+text;
937 size_t this_lineend = text.find(
'\n');
940 if (this_lineend == string::npos) {
944 result =
correctSpaces(text.substr(0, this_lineend), change);
946 while (this_lineend != string::npos) {
947 size_t next_lineend = text.find(
'\n', this_lineend+1);
948 if (next_lineend == string::npos) {
949 result = result+
"\n"+
correctSpaces(text.substr(this_lineend+1), change);
952 result = result+
"\n"+
correctSpaces(text.substr(this_lineend+1, next_lineend-this_lineend-1), change);
954 this_lineend = next_lineend;
961 size_t first = text.find_first_not_of(
' ');
962 if (first == string::npos)
return INT_MAX;
967 size_t this_lineend = text.find(
'\n');
968 size_t min_indent = INT_MAX;
970 if (this_lineend == string::npos) {
974 while (this_lineend != string::npos) {
975 size_t next_lineend = text.find(
'\n', this_lineend+1);
976 if (next_lineend == string::npos) {
977 min_indent =
min(min_indent,
countSpaces(text.substr(this_lineend+1)));
980 min_indent =
min(min_indent,
countSpaces(text.substr(this_lineend+1, next_lineend-this_lineend-1)));
982 this_lineend = next_lineend;
986 if (min_indent == INT_MAX) min_indent = 0;
994 ParagraphTree *brother;
1002 string location_description() const
OVERRIDE {
return "in paragraph starting here"; }
1005 ParagraphTree(Ostrings::const_iterator begin,
const Ostrings::const_iterator end)
1012 string& text = otext;
1015 size_t reststart = text.find(
'\n', 1);
1017 if (reststart == 0) {
1018 attach_warning(
"[internal] Paragraph starts with LF -> reflow calculation will probably fail");
1021 if (reststart != string::npos) {
1022 int rest_indent = -1;
1023 string rest = text.substr(reststart);
1029 size_t last = text.find_last_not_of(
' ', reststart-1);
1030 bool is_header = last != string::npos && text[last] ==
':';
1032 if (!is_header && rest_indent == (first_indent+8)) {
1034 size_t textstart = text.find_first_not_of(
" \n");
1043 int diff = rest_indent-first_indent;
1050 attach_warning(
strf(
"[internal] unhandled: more indentation on the 1st line (diff=%i)", diff));
1066 brother = buildParagraphTree(++begin, end);
1069 void brothers_to_sons(ParagraphTree *new_brother);
1071 unsigned get_preformatted_width()
const {
1088 const char *res =
NULp;
1091 case ITEM: res =
"ITEM";
break;
1099 if (son) nodes += son->countTextNodes();
1100 if (brother) nodes += brother->countTextNodes();
1104 #if defined(DUMP_PARAGRAPHS)
1106 char *masknl(
const char *text) {
1108 for (
int i = 0; result[i]; ++i) {
1109 if (result[i] ==
'\n') result[i] =
'|';
1113 void dump(ostream& out,
int indent = 0) {
1116 char *mtext = masknl(otext.
as_string().c_str());
1117 out <<
"text='" << mtext <<
"'\n";
1122 out <<
"type='" << readable_type() <<
"' ";
1124 out <<
"enumeration='" << otext.
get_number() <<
"' ";
1126 out <<
"reflow='" << reflow <<
"' ";
1127 out <<
"indentation='" << indentation <<
"'\n";
1131 son->dump(out, indent+2);
1136 brother->dump(out, indent);
1139 #endif // DUMP_PARAGRAPHS
1142 static ParagraphTree* buildParagraphTree(Ostrings::const_iterator begin,
const Ostrings::const_iterator end) {
1143 if (begin == end)
return NULp;
1144 return new ParagraphTree(begin, end);
1148 const Ostrings& txt = sec.Content();
1149 if (txt.empty())
throw "attempt to build an empty ParagraphTree";
1150 return buildParagraphTree(txt.begin(), txt.end());
1156 (son && son->contains(that)) ||
1157 (brother && brother->contains(that));
1161 if (brother == before_this)
return this;
1162 if (!brother)
return NULp;
1163 return brother->predecessor(before_this);
1167 if (!brother) brother = new_brother;
1168 else brother->append(new_brother);
1172 return (other == brother) || (brother && brother->is_some_brother(other));
1176 ParagraphTree *removed =
this;
1177 ParagraphTree *after_pred =
this;
1185 if (after_pred->brother == after) {
1186 after_pred->brother =
NULp;
1189 after_pred = after_pred->brother;
1198 case ITEM:
return this;
1200 if (get_enumeration() == 1)
return this;
1204 if (brother)
return brother->firstListMember();
1209 if (indentation<previous.indentation)
return NULp;
1210 if (indentation == previous.indentation &&
get_type() == previous.get_type()) {
1212 if (get_enumeration() > previous.get_enumeration())
return this;
1215 if (!brother)
return NULp;
1216 return brother->nextListMemberAfter(previous);
1219 return brother ? brother->nextListMemberAfter(*
this) :
NULp;
1223 if (indentation < wanted_indentation)
return this;
1224 if (!brother)
return NULp;
1225 return brother->firstWithLessIndentThan(wanted_indentation);
1228 void format_indentations();
1229 void format_lists();
1232 static ParagraphTree* buildNewParagraph(
const string& Text,
size_t beginLineNo,
ParagraphType type) {
1234 S.push_back(
Ostring(Text, beginLineNo, type));
1235 return new ParagraphTree(S.begin(), S.end());
1237 ParagraphTree *xml_write_list_contents();
1238 ParagraphTree *xml_write_enum_contents();
1239 void xml_write_textblock();
1245 #if defined(DUMP_PARAGRAPHS)
1246 static void dump_paragraph(ParagraphTree *para) {
1248 para->dump(cout, 0);
1252 void ParagraphTree::brothers_to_sons(ParagraphTree *new_brother) {
1261 if (brother != new_brother) {
1264 son->attach_warning(
"Found unexpected son (in brothers_to_sons)");
1265 brother->attach_warning(
"while trying to transform paragraphs from here ..");
1266 new_brother->attach_warning(
".. to here ..");
1267 attach_warning(
".. into sons of this paragraph.");
1280 son = brother->takeAllInFrontOf(new_brother);
1281 brother = new_brother;
1291 void ParagraphTree::format_lists() {
1293 ParagraphTree *member = firstListMember();
1295 for (ParagraphTree *curr =
this; curr != member; curr = curr->brother) {
1297 if (curr->son) curr->son->format_lists();
1300 for (ParagraphTree *next = member->nextListMember();
1302 member = next, next = member->nextListMember())
1304 member->brothers_to_sons(next);
1307 if (member->son) member->son->format_lists();
1312 if (member->brother) {
1313 ParagraphTree *non_member = member->brother->firstWithLessIndentThan(member->indentation+1);
1314 member->brothers_to_sons(non_member);
1317 if (member->son) member->son->format_lists();
1318 if (member->brother) member->brother->format_lists();
1321 for (ParagraphTree *curr =
this; curr; curr = curr->brother) {
1323 if (curr->son) curr->son->format_lists();
1328 void ParagraphTree::format_indentations() {
1330 ParagraphTree *same_indent = brother->firstWithLessIndentThan(indentation+1);
1331 #if defined(WARN_POSSIBLY_WRONG_INDENTATION_CORRECTION)
1332 if (same_indent && indentation != same_indent->indentation) {
1333 same_indent->attach_warning(
"indentation is assumed to be same as ..");
1334 attach_warning(
".. here");
1337 brothers_to_sons(same_indent);
1338 if (brother) brother->format_indentations();
1341 if (son) son->format_indentations();
1380 return link_id[idx];
1384 size_t last_dot = name.find_last_of(
'.');
1385 if (last_dot == string::npos) {
1388 return name.c_str()+last_dot+1;
1395 if (ext && strcasecmp(ext,
"hlp") == 0) type =
LT_HLP;
1396 else if (link_target.find(
"http://") == 0) type =
LT_HTTP;
1397 else if (link_target.find(
"https://") == 0) type =
LT_HTTPS;
1398 else if (link_target.find(
"ftp://") == 0) type =
LT_FTP;
1399 else if (link_target.find(
"file://") == 0) type =
LT_FILE;
1400 else if (link_target.find(
'@') != string::npos) type =
LT_EMAIL;
1401 else if (ext && strcasecmp(ext,
"ps") == 0) type =
LT_PS;
1402 else if (ext && strcasecmp(ext,
"pdf") == 0) type =
LT_PDF;
1403 else if (link_target[0] ==
'#') type =
LT_TICKET;
1416 static string path[
PATHS] = {
"source/",
"genhelp/" };
1419 for (
size_t p = 0; p<
PATHS; p++) {
1420 string fullname = path[p]+helpname;
1421 if (stat(fullname.c_str(), &st) == 0) {
1433 if (located.empty()) {
1441 string msg =
string(
"Unknown link type (dest='")+dest+
"')";
1445 link.add_attribute(
"dest", dest);
1447 link.add_attribute(
"source_line", source_line);
1451 if (fullhelp.empty()) {
1452 link.add_attribute(
"missing",
"1");
1453 string deadlink =
strf(
"Dead link to '%s'", dest.c_str());
1464 size_t found = text.find(
"LINK{", 0);
1465 if (found != string::npos) {
1466 size_t inside_link = found+5;
1467 size_t close = text.find(
'}', inside_link);
1469 if (close == string::npos)
throw "unclosed 'LINK{}'";
1471 string link_target = text.substr(inside_link, close-inside_link);
1473 string dest = link_target;
1478 XML_Tag
link(
"LINK");
1479 link.set_on_extra_line(
false);
1491 size_t hashpos = text.find(
'#');
1492 if (hashpos == string::npos) {
1496 if (!isdigit(text[hashpos+1])) {
1497 size_t afterhash = hashpos+1;
1499 text.substr(0, afterhash) +
1503 size_t hashlength = 2;
1504 while (isdigit(text[hashpos+hashlength])) ++hashlength;
1507 text.substr(0, hashpos) +
1509 text.substr(hashpos, hashlength) +
1519 void ParagraphTree::xml_write_textblock() {
1520 XML_Tag textblock(
"T");
1521 textblock.add_attribute(
"reflow", reflow ?
"1" :
"0");
1523 unsigned width = get_preformatted_width();
1525 textblock.add_attribute(
"width",
strf(
"%i", width));
1531 const string& text = otext;
1542 ParagraphTree *ParagraphTree::xml_write_list_contents() {
1544 #if defined(WARN_FIXED_LAYOUT_LIST_ELEMENTS)
1545 if (!reflow) attach_warning(
"ITEM not reflown (check output)");
1548 XML_Tag entry(
"ENTRY");
1549 entry.add_attribute(
"item",
"1");
1550 xml_write_textblock();
1551 if (son) son->xml_write();
1553 if (brother && brother->is_itemlist_member()) {
1554 return brother->xml_write_list_contents();
1558 ParagraphTree *ParagraphTree::xml_write_enum_contents() {
1560 #if defined(WARN_FIXED_LAYOUT_LIST_ELEMENTS)
1561 if (!reflow) attach_warning(
"ENUMERATED not reflown (check output)");
1564 XML_Tag entry(
"ENTRY");
1565 switch (get_enum_type()) {
1567 entry.add_attribute(
"enumerated",
strf(
"%i", get_enumeration()));
1570 entry.add_attribute(
"enumerated",
strf(
"%c",
'A'-1+get_enumeration()));
1573 entry.add_attribute(
"enumerated",
strf(
"%c",
'a'-1+get_enumeration()));
1579 xml_write_textblock();
1580 if (son) son->xml_write();
1582 if (brother && brother->get_enumeration()) {
1583 int diff = brother->get_enumeration()-get_enumeration();
1585 attach_warning(
"Non-consecutive enumeration detected between here..");
1586 brother->attach_warning(
".. and here");
1588 return brother->xml_write_enum_contents();
1593 void ParagraphTree::xml_write() {
1595 ParagraphTree *next =
NULp;
1596 if (get_enumeration()) {
1597 XML_Tag enu(
"ENUM");
1598 if (get_enumeration() != 1) {
1599 attach_warning(
strf(
"First enum starts with '%u.' (maybe previous enum was not detected)", get_enumeration()));
1601 next = xml_write_enum_contents();
1602 #if defined(WARN_LONESOME_ENUM_ELEMENTS)
1603 if (next == brother) attach_warning(
"Suspicious single-element-ENUM");
1606 else if (is_itemlist_member()) {
1607 XML_Tag list(
"LIST");
1608 next = xml_write_list_contents();
1609 #if defined(WARN_LONESOME_LIST_ELEMENTS)
1610 if (next == brother) attach_warning(
"Suspicious single-element-LIST");
1616 xml_write_textblock();
1617 if (son) son->xml_write();
1621 if (next) next->xml_write();
1623 catch (
string& err) {
throw attached_message(err); }
1624 catch (
const char *err) {
throw attached_message(err); }
1628 for (Links::const_iterator
s = links.begin();
s != links.end(); ++
s) {
1638 size_t lf = paragraph.find(
'\n', pos);
1639 if (lf == string::npos)
break;
1642 if (lf>0 && paragraph[lf-1] ==
' ') {
1644 while (sp>=1 && paragraph[sp-1] ==
' ') --sp;
1646 paragraph.erase(sp, lf-sp);
1651 size_t ns = paragraph.find(
' ', lf);
1653 paragraph[lf] =
' ';
1657 size_t as = paragraph.find_first_not_of(
' ', ns);
1658 size_t ls = as == string::npos ? ns : as-1;
1659 paragraph.erase(lf, ls-lf);
1663 size_t ls = paragraph.find_last_not_of(
' ');
1664 if (ls == string::npos) {
1669 paragraph.erase(ls, paragraph.length()-ls);
1675 XML_Document xml(
"PAGE",
"arb_help.dtd", out);
1677 xml.skip_empty_tags =
true;
1678 xml.indentation_per_level = 2;
1680 xml.getRoot().add_attribute(
"name", page_name);
1682 xml.getRoot().add_attribute(
"edit_warning",
"devel");
1684 xml.getRoot().add_attribute(
"edit_warning",
"release");
1687 xml.getRoot().add_attribute(
"source",
inputfile.c_str());
1698 string titleText, subtitleText;
1700 const Ostrings&
T =
title.Content();
1701 Ostrings::const_iterator
s = T.begin();
1703 if (s != T.end()) titleText = *s++;
1705 bool subtitleAdded =
false;
1706 for (; s != T.end(); ++
s) {
1708 throw s->attached_message(
"wrong paragraph type (plain text expected)");
1710 string text = s->as_string();
1711 if (!text.empty()) {
1713 if (!text.empty()) {
1714 if (subtitleAdded)
throw s->attached_message(
"only one subtitle accepted");
1719 s->attach_warning(
strf(
"subtitle too verbose (max. %i chars allowed; found %zu)",
MAX_SUBTITLE_CHARS, text.length()));
1721 subtitleText = text;
1722 subtitleAdded =
true;
1728 XML_Tag title_tag(
"TITLE"); {
XML_Text text(titleText); }
1730 if (!subtitleText.empty()) {
1731 XML_Tag title_tag(
"SUBTITLE"); {
XML_Text text(subtitleText); }
1735 catch (
string& err) {
throw title.attached_message(err); }
1736 catch (
const char *err) {
throw title.attached_message(err); }
1738 for (SectionList::const_iterator sec = sections.begin(); sec != sections.end(); ++sec) {
1740 XML_Tag section_tag(
"SECTION");
1741 section_tag.add_attribute(
"name", sec->getName());
1743 ParagraphTree *ptree = ParagraphTree::buildParagraphTree(*sec);
1746 size_t textnodes = ptree->countTextNodes();
1748 #if defined(DUMP_PARAGRAPHS)
1749 cout <<
"Dump of section '" << sec->getName() <<
"' (before format_lists):\n";
1751 cout <<
"----------------------------------------\n";
1754 ptree->format_lists();
1756 #if defined(DUMP_PARAGRAPHS)
1757 cout <<
"Dump of section '" << sec->getName() <<
"' (after format_lists):\n";
1759 cout <<
"----------------------------------------\n";
1762 size_t textnodes2 = ptree->countTextNodes();
1766 ptree->format_indentations();
1768 #if defined(DUMP_PARAGRAPHS)
1769 cout <<
"Dump of section '" << sec->getName() <<
"' (after format_indentations):\n";
1771 cout <<
"----------------------------------------\n";
1774 size_t textnodes3 = ptree->countTextNodes();
1782 catch (
string& err) {
throw sec->attached_message(err); }
1783 catch (
const char *err) {
throw sec->attached_message(err); }
1788 for (SectionList::const_iterator sec = sections.begin(); sec != sections.end(); ++sec) {
1790 const Ostrings&
s = sec->Content();
1792 for (Ostrings::const_iterator
li = s.begin();
li != s.end(); ++
li) {
1793 const string&
line = *
li;
1797 size_t found = line.find(
"LINK{", start);
1798 if (found == string::npos)
break;
1800 size_t close = line.find(
'}', found);
1801 if (close == string::npos)
break;
1803 string link_target = line.substr(found, close-found);
1805 if (link_target.find(
"http://") == string::npos &&
1806 link_target.find(
"https://")== string::npos &&
1807 link_target.find(
"ftp://") == string::npos &&
1808 link_target.find(
"file://") == string::npos &&
1809 link_target.find(
'@') == string::npos)
1811 check_self_ref(link_target);
1819 auto_references.push_back(
Link(link_target,
li->line_number()));
1821 catch (
string& err) {
1829 catch (
string& err) {
1830 throw sec->attached_message(
"'"+err+
"' while scanning LINK{}");
1835 static void show_err(
const string& err,
size_t lineno,
const string& helpfile) {
1836 if (err.find(helpfile+
':') != string::npos) {
1839 else if (lineno == NO_LINENUMBER_INFO) {
1840 cerr << helpfile <<
":1: [in unknown line] " << err;
1843 cerr << helpfile <<
":" << lineno <<
": " << err;
1854 for (list<LineAttachedMessage>::const_iterator wi = warnings.begin(); wi != warnings.end(); ++wi) {
1865 cerr <<
"Usage: arb_help2xml <ARB helpfile> <XML output>\n";
1875 string xml_output = argv[2];
1878 ifstream in(arb_help.c_str());
1885 FILE *out = std::fopen(xml_output.c_str(),
"wt");
1886 if (!out)
throw string(
"Can't open '")+xml_output+
'\'';
1890 size_t slash = arb_help.find(
'/');
1891 size_t dot = arb_help.find_last_of(
'.');
1893 if (slash == string::npos || dot == string::npos) {
1894 throw string(
"parameter <ARB helpfile> has to be in format 'source/name.hlp' (not '"+arb_help+
"')");
1897 string page_name(arb_help, slash+1, dot-slash-1);
1903 remove(xml_output.c_str());
1933 #define TEST_REMOVE_LF_AND_INDENTATION(i,want) TEST_EXPECT_EQUAL(remove_LF_and_indentation(i).c_str(), want)
1934 #define TEST_REMOVE_LF_AND_INDENTATION__BROKEN(i,want,got) TEST_EXPECT_EQUAL__BROKEN(remove_LF_and_indentation(i).c_str(), want, got)
1936 void TEST_remove_LF_and_indentation() {
1937 TEST_REMOVE_LF_AND_INDENTATION(
"",
1940 TEST_REMOVE_LF_AND_INDENTATION(
" \n \n \n ",
1942 TEST_REMOVE_LF_AND_INDENTATION(
"hello\nNewline",
1944 TEST_REMOVE_LF_AND_INDENTATION(
"hello\nNewline\n 1\n2 \n 3 \n4\n5\n 6 \n 7 \n 8\n",
1945 "hello Newline 1 2 3 4 5 6 7 8");
1947 TEST_REMOVE_LF_AND_INDENTATION(
"Visualization of Three-dimensional\n structure of small subunit (16S) rRNA",
1948 "Visualization of Three-dimensional structure of small subunit (16S) rRNA");
1951 static arb_test::match_expectation help_file_compiles(
const char *helpfile,
const char *expected_title,
const char *expected_error_part) {
1955 ifstream in(helpfile);
1964 FILE *devnul = fopen(
"/dev/null",
"wt");
1972 if (expected_error_part) {
1980 const Ostrings& title_strings = title.Content();
1992 return all().ofgroup(expected);
1995 #define HELP_FILE_COMPILES(name,expTitle) TEST_EXPECTATION(help_file_compiles(name,expTitle,NULp))
1996 #define HELP_FILE_COMPILE_ERROR(name,expError) TEST_EXPECTATION(help_file_compiles(name,NULp,expError))
1998 void TEST_hlp2xml_conversion() {
2001 HELP_FILE_COMPILES(
"genhelp/agde_treepuzzle.hlp",
"treepuzzle");
2003 HELP_FILE_COMPILES(
"source/markbyref.hlp",
"Mark by reference");
2004 HELP_FILE_COMPILES(
"source/ad_align.hlp",
"Alignment Administration");
2005 HELP_FILE_COMPILES(
"genhelp/copyright.hlp",
"Copyrights and licenses");
2009 HELP_FILE_COMPILE_ERROR(
"akjsdlkad.hlp",
"Can't read from");
2016 void TEST_hlp2xml_output() {
2017 string tested_helpfile[] = {
2021 string HELP_SOURCE =
"../../HELP_SOURCE/";
2022 string LIB =
"../../lib/";
2023 string EXPECTED =
"help/";
2025 for (
size_t i = 0; i<
ARRAY_ELEMS(tested_helpfile); ++i) {
2026 string xml = HELP_SOURCE +
"Xml/" + tested_helpfile[i] +
".xml";
2027 string html = LIB +
"help_html/" + tested_helpfile[i] +
".html";
2028 string hlp = LIB +
"help/" + tested_helpfile[i] +
".hlp";
2030 string xml_expected = EXPECTED + tested_helpfile[i] +
".xml";
2031 string html_expected = EXPECTED + tested_helpfile[i] +
".html";
2032 string hlp_expected = EXPECTED + tested_helpfile[i] +
".hlp";
2035 #if defined(TEST_AUTO_UPDATE)
2036 # if defined(NDEBUG)
2037 # error please use auto-update only in DEBUG mode
2039 TEST_COPY_FILE(xml.c_str(), xml_expected.c_str());
2040 TEST_COPY_FILE(html.c_str(), html_expected.c_str());
2041 TEST_COPY_FILE(hlp.c_str(), hlp_expected.c_str());
2043 #else // !defined(TEST_AUTO_UPDATE)
2046 int expected_xml_difflines = 0;
2047 int expected_hlp_difflines = 0;
2048 # else // !defined(DEBUG)
2049 int expected_xml_difflines = 1;
2050 int expected_hlp_difflines = 2;
2060 #if defined(PROTECT_HELP_VS_CHANGES)
2061 void TEST_protect_help_vs_changes() {
2070 bool do_help =
true;
2071 bool do_html =
true;
2073 const char *ref_WC =
"ARB.help.ref";
2077 string this_base =
"../..";
2078 string ref_base = this_base+
"/../"+ref_WC;
2079 string to_help =
"/lib/help";
2080 string to_html =
"/lib/help_html";
2081 string diff_help =
"diff -u "+ref_base+to_help+
" "+this_base+to_help;
2082 string diff_html =
"diff -u "+ref_base+to_html+
" "+this_base+to_html;
2087 if (do_html) update_cmd =
string(
"(")+diff_help+
";"+diff_html+
")";
2088 else update_cmd = diff_help;
2090 else if (do_html) update_cmd = diff_html;
2092 string patch =
"help_changes.patch";
2093 update_cmd +=
" >"+patch+
" ||true";
2095 string fail_on_change_cmd =
"test \"`cat "+patch+
" | grep -v '^Common subdirectories' | wc -l`\" = \"0\" || ( echo \"Error: Help changed\"; false)";
2102 #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)
static void checkControlComment(const char *line)
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)
static void set_preformatted_blocks_wanted(unsigned preformatted_blocks)
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)
static void set_current_preformatted_width(unsigned allowed)
bool isComment(const char *s)
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)
unsigned get_preformatted_width() const
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