#include <stdio.h>
/*
hl2chl - Header Level to Chapter Level conversion
Example Markdown headers (other text omitted):
# Using a mouse
## Basic mouse actions
### Left button
### Right button
#### Context menus
## Selecting
### Click and drag
### Double click
Corresponding TOC (but see below!):
1. Using a mouse
1.1. Basic mouse actions
1.1.1 Left button
1.1.2 Right button
1.1.2.1 Context menus
1.2. Selecting
1.2.1 Click and drag
1.2.2 Double click
Corresponding TOC considering first header being Document Header (not part of ToC):
<h1>Using a mouse</h1>
1. Basic mouse actions
1.1. Left button
1.2. Right button
1.2.1. Context menus
2. Selecting
2.1. Click and drag
2.2. Double click
Terminology
-----------
The numbers in a TOC, separated by '.', is called a 'label' in DocBook terms.
Let's be a bit more specific and call it 'chapter label'.
The 1st number in a 'chapter label' is called 'chapter number'.
The 2nd number in a 'chapter label' is called 'section number'.
The 3rd to the 6th number in a 'chapter label' is called 'subsection number'.
Each number in a 'chapter label', independently of its position, is called a 'chapter level'.
The 'header level' (1-6) is the number of hashes '#' in a Markdown header (or its equivalent HTML Header Level).
Let's define the steps for creating the Chapter Labels:
# Using a mouse first header found (render as Title only) => 'chapter_label' = '', chapter Level = 0
## Basic mouse actions second header found (always render this Header as the first Chapter in ToC) => 'chapter_label' = '1.', chapter Level = 1
### Left button 'header_level' = 3 > previous 'header_level' (2) , previous 'chapter_label' is '1.' => 'chapter_label' = '1.1.', chapter Level = 2
### Right button 'header_level' = 3 = previous 'header_level' (3) , previous 'chapter_label' is '1.1.' => 'chapter_label' = '1.2.', chapter Level = 2
#### Context menus 'header_level' = 4 > previous 'header_level' (3) , previous 'chapter_label' is '1.2.' => 'chapter_label' = '1.2.1.', chapter Level = 3
## Selecting 'header_level' = 2 < previous 'header_level' (4) , previous 'chapter_label' is '1.2.1.' => 'chapter_label' = '2.', chapter Level = 1
### Click and drag 'header_level' = 3 > previous 'header_level' (2) , previous 'chapter_label' is '2.' => 'chapter_label' = '2.1.', chapter Level = 2
### Double click 'header_level' = 3 = previous 'header_level' (3) , previous 'chapter_label' is '2.1.' => 'chapter_label' = '2.2.', chapter Level = 2
The same steps described as rules:
1. Start with: chapter_level = 0, chapter_label = ''
2. If current header is first header in document, render it as a Title (not done here), do not include in ToC.
3. If current header is second header in document, render it as the first entry in ToC: chapter_label = '1.'
Save header_level (normally '2', but not necessarrily, see 'Erroneous example 2').
set chapter_level = 1
set prev_chapter_level = 0
4. If chapter_level is set (> 0) and this header_level is the same as the previous header_level,
increment the last 'chapter_label_number' in the 'chapter_label'.
That is, '1.' becomes '2', '1.1.1' becomes '1.1.2', '2.3.4' becomes '2.3.5', and so on.
Set chapter_level to the same as previous chapter_level:
set chapter_level = prev_chapter_level
5. If current header_level is greater than the previous header_level,
this header is considered a Section/Subsection of the previous header,
so append a '.1' to the end of the 'chapter_label'.
Increment chapter_level by 1 (even if the header_level has increased more than 1, as in 'Erroneous example 4')
Increase by 1 makes Chapter Labels for subsections "smooth": Subsection of '1.' always becomes '1.1', never '1.1.1.1'.
Max Header Level is 6, and Max Chapter Level is 5 (any greater number is considered an error).
if (chapter < 5) {chapter_level++}
6. If current header_level is less than the previous header_level, this header is considered a new Chapter/Section/Subsection.
If current header_level is 1 (see header # Selecting in *Erroneous example 1*)
it will be rendered as a <h1> title, but we will include it anyway as a new Chapter in ToC,
as it would be a <h2> title, with chapter_level=1.
Then get the difference between current chapter/section and the previous one.
Use chapter_levels for this, not header_level.
The difference indicates how many 'chapter label numbers' to drop from the end of the 'chapter label', and increment the 'chapter label numbers'
which now has become the last one.
That is, as in the example above:
'1.1.2.1 Context menus' has chapter_level=3.
'1.2. Selecting' has chapter_level=1.
The difference is 3-1=**2**, so drop the last 2 'chapter label numbers': '1.1.2.1.' becomes '1.1.' (dropping '2.1.').
Finally, increment the new last 'chapter label number' '1.1++.' to get the final '1.2.'.
While do "smooth increase" do avoid weird subsection numbering, the same is not true when decreasing *Chapter Level*.
chapter_level = (header_level > 1 ) ? header_level - 1 : 1
With these rules defined, all the erroneous examples above should render to the same ToC.
*/
#define TOC_CHAPTER_LABEL_MAX_SIZE 206
#define MAX_CHAPTER_LABEL_NUMBERS 5
char *md_headers[] = {
"# Using a mouse",
"## 1. Basic mouse actions",
"### 1.1. Left button",
"### 1.2. Right button",
"#### 1.2.1. Context menus",
"## 2. Selecting",
"### 2.1 Click and drag",
"### 2.2 Double click",
"NOT A HEADER No Click",
"# 3 Double click",
"#### 3.1 Double click",
"###### 3.1.1 Double click",
"####### NOT A HEADER Double click",
"######## NOT A HEADER Double click",
"##### 3.2. Double click",
"#### 4. Double click",
"### 5. Double click",
"## 6. Double click",
"## 7. Double click",
"### 7.1. Double click",
"#### 7.1.1. Double click",
"##### 7.1.1.1. Double click",
"###### 7.1.1.1.1. Double click",
NULL};
/* Returns 1-6 if text starts with 1-6 '#', return 0 if no or more than 6 '#' */
int hl_get(char *text)
{
int i = 0;
char *p = text;
while (p && p[i++] == '#' && i <= 7);
return (i > 7 || i == 0) ? 0 : i-1;
}
/* Returns 0-5, the number of assigned Chapter Label Numbers in the array. */
int clnums_get(int chapter_label_numbers[])
{
int i = 0;
while ((i < MAX_CHAPTER_LABEL_NUMBERS) && chapter_label_numbers[i])
{
i++;
}
return i;
}
/* Print array of Chapter Label Numbers. */
void clnums_print(int chapter_label_numbers[])
{
printf("[%d].[%d].[%d].[%d].[%d]\n",
chapter_label_numbers[0],
chapter_label_numbers[1],
chapter_label_numbers[2],
chapter_label_numbers[3],
chapter_label_numbers[4]);
}
/* Increment the last assigned number (i.e. > 0) by one */
int clnums_inc_last(int chapter_label_numbers[])
{
int i = clnums_get(chapter_label_numbers);
int rc = 1;
/* If array is not yet initialized, all elements in the array are 0, so set the first element explicitly to 1 */
if (i == 0)
{
chapter_label_numbers[0] = 1;
}
else if (i < MAX_CHAPTER_LABEL_NUMBERS)
{
chapter_label_numbers[i-1]++;
rc = chapter_label_numbers[i-1];
}
return rc;
}
/* Append a new Chapter Label Number number Assigned */
int clnums_append_one(int chapter_label_numbers[])
{
int i = clnums_get(chapter_label_numbers);
/* Only append if current level is less than MAX_CHAPTER_LABEL_NUMBERS */
if (i < MAX_CHAPTER_LABEL_NUMBERS - 1)
{
chapter_label_numbers[i] = 1;
return 1;
}
else
{
return 0;
}
}
int clnums_drop(int chapter_label_numbers[], int levels_to_drop)
{
int i = clnums_get(chapter_label_numbers);
do
{
chapter_label_numbers[i--] = 0;
} while ((levels_to_drop--) && i);
return 0;
}
int
main(int argc, char *argv[])
{
char **p = md_headers;
int header_idx = 0;
int header_level = 0;
int prev_header_level = 0;
/* 1. Start with: chapter_level = 0, chapter_label = '' */
char chapter_label[TOC_CHAPTER_LABEL_MAX_SIZE] = {0};
int chapter_level = 0;
int prev_chapter_level = 0;
int chapter_label_numbers[MAX_CHAPTER_LABEL_NUMBERS] = {0};
(void)argc; (void)argv;
(void)chapter_label;
while (*p)
{
header_idx++;
if (header_idx == 1)
{
/* 2. If current header is first header in document, render it as a Title (not done here), do not include in ToC. */
chapter_level = 0;
prev_chapter_level = 0;
printf("DOCUMENT TITLE: %s\n", *p);
}
else if (header_idx == 2)
{
/* 3. If current header is second header in document, render it as the first entry in ToC: chapter_label = '1.' */
/* Save parsed header_level (normally '2', but not necessarrily, see 'Erroneous example 2'). */
/* set chapter_level = 1 */
clnums_inc_last(chapter_label_numbers);
/* printf("FIRST TOC ENTRY: PRINT: "); clnums_print(chapter_label_numbers); */
prev_header_level = header_level = hl_get(*p);
chapter_level = 1;
prev_chapter_level = 0;
/* printf("FIRST TOC ENTRY: HL=%d: %s %s\n", header_level, chapter_label, *p); */
}
else
{
/* 4. If chapter_level is set (> 0) and this header_level is the same as the previous header_level, */
/* increment the last 'chapter_label_number' in the 'chapter_label'. */
/* That is, '1.' becomes '2', '1.1.1' becomes '1.1.2', '2.3.4' becomes '2.3.5', and so on. */
/* Set chapter_level to the same as previous chapter_level: */
/* set chapter_level = prev_chapter_level */
header_level = hl_get(*p);
/* If header_level == 0 => Skip, this is not a header */
if (header_level)
{
if (header_level == prev_header_level)
{
clnums_inc_last(chapter_label_numbers);
chapter_level = prev_chapter_level;
/* printf("TOC == PREV: PRINT: "); clnums_print(chapter_label_numbers); */
}
/* 5. If current header_level is greater than the previous header_level, */
/* this header is considered a Section/Subsection of the previous header, */
/* so append a '.1' to the end of the 'chapter_label'. */
/* Increment chapter_level by 1 (even if the header_level has increased more than 1, as in 'Erroneous example 4') */
/* Increase by 1 makes Chapter Labels for subsections "smooth": Subsection of '1.' always becomes '1.1', never '1.1.1.1'. */
/* set chapter_level++ */
else if (header_level > prev_header_level)
{
if (chapter_level < MAX_CHAPTER_LABEL_NUMBERS)
{
clnums_append_one(chapter_label_numbers);
chapter_level = prev_chapter_level + 1;
}
else
{
/* Should never come here, but if we do, continue as this would be the same level as previous header */
printf("TOC > PREV: SHOULD NEVER COME HERE!\n");
clnums_inc_last(chapter_label_numbers);
chapter_level = prev_chapter_level;
}
/* printf("TOC > PREV: PRINT: "); clnums_print(chapter_label_numbers); */
}
/* 6. If current header_level is less than the previous header_level, this header is considered a new Chapter/Section/Subsection. */
/* If current header_level is 1 (see header # Selecting in *Erroneous example 1*) */
/* it will be rendered as a <h1> title, but we will include it anyway as a new Chapter in ToC, */
/* as it would be a <h2> title, with chapter_level=1. */
/* Then get the difference between current chapter/section and the previous one. */
/* Use chapter_levels for this, not header_level. */
/* The difference indicates how many 'chapter label numbers' to drop from the end of the 'chapter label', and increment the 'chapter label numbers' */
/* which now has become the last one. */
/* That is, as in the example above: */
/* '1.1.2.1 Context menus' has chapter_level=3. */
/* '1.2. Selecting' has chapter_level=1. */
/* The difference is 3-1=2, so drop the last 2 'chapter label numbers': '1.1.2.1.' becomes '1.1.' (dropping '2.1.'). */
/* Finally, increment the new last 'chapter label number' '1.1++.' to get the final '1.2.'. */
/* While do "smooth increase" do avoid weird subsection numbering, the same is not true when decreasing Chapter Level. */
/* chapter_level = (header_level > 1 ) ? header_level - 1 : 1 */
else /* if (header_level < prev_header_level) */
{
int levels_to_drop = 0;
chapter_level = (header_level > 1 ) ? header_level - 1 : 1;
levels_to_drop = prev_chapter_level - chapter_level;
clnums_drop(chapter_label_numbers, levels_to_drop);
clnums_inc_last(chapter_label_numbers);
/* printf("TOC < PREV: PRINT: "); clnums_print(chapter_label_numbers); */
}
/* printf("TOC ENTRY: HL=%d: %s %s\n", header_level, chapter_label, *p); */
prev_header_level = header_level;
printf("TOC ENTRY: HL=%d: %s \t", header_level, *p);
clnums_print(chapter_label_numbers);
}
}
p++;
}
return 0;
}