Annotation-System/hmml_to_html/hmml_to_html.c

1084 lines
35 KiB
C

#if 0
ctime -begin ${0%.*}.ctm
gcc -g -Wall -fsanitize=address -std=c99 $0 -o ${0%.*} hmml.a
ctime -end ${0%.*}.ctm
exit
#endif
typedef unsigned int bool;
#define TRUE 1
#define FALSE 0
#include <stdarg.h> // NOTE(matt): varargs
#include <stdio.h> // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror
#include <stdlib.h> // NOTE(matt): calloc, malloc, free
#include "hmmlib.h"
typedef struct
{
char *Location;
char *Ptr;
int Size;
} buffer;
typedef struct
{
char Timecode[8];
int Identifier;
} identifier;
typedef struct
{
char RefTitle[620];
char ID[512];
char URL[512];
char Source[256];
identifier Identifier[12];
int IdentifierCount;
} ref_info;
typedef struct
{
char Date[32];
char Text[512];
} quote_info;
#define ArrayCount(A) sizeof(A)/sizeof(*(A))
void
ClaimBuffer(char *MemoryArena, int *ClaimedMemory, buffer *Buffer, int Size)
{
Buffer->Location = MemoryArena + *ClaimedMemory;
Buffer->Size = Size;
*ClaimedMemory += Buffer->Size;
Buffer->Ptr = Buffer->Location;
}
int
TimecodeToSeconds(char *Timecode)
{
int HMS[3] = { 0, 0, 0 }; // 0 == Seconds; 1 == Minutes; 2 == Hours
int Colons = 0;
while(*Timecode)
{
//if((*Timecode < '0' || *Timecode > '9') && *Timecode != ':') { return FALSE; }
if(*Timecode == ':')
{
++Colons;
//if(Colons > 2) { return FALSE; }
for(int i = 0; i < Colons; ++i)
{
HMS[Colons - i] = HMS[Colons - (i + 1)];
}
HMS[0] = 0;
}
else
{
HMS[0] = HMS[0] * 10 + *Timecode - '0';
}
++Timecode;
}
//if(HMS[0] > 59 || HMS[1] > 59 || Timecode[-1] == ':') { return FALSE; }
return HMS[2] * 60 * 60 + HMS[1] * 60 + HMS[0];
}
void
CopyBuffer(buffer *Dest, buffer *Src)
{
Src->Ptr = Src->Location;
while(*Src->Ptr)
{
*Dest->Ptr++ = *Src->Ptr++;
}
}
__attribute__ ((format (printf, 2, 3)))
void
CopyString(char Dest[], char *Format, ...)
{
va_list Args;
va_start(Args, Format);
vsprintf(Dest, Format, Args);
va_end(Args);
}
__attribute__ ((format (printf, 2, 3)))
void
CopyStringToBuffer(buffer *Dest, char *Format, ...)
{
va_list Args;
va_start(Args, Format);
int Length = vsprintf(Dest->Ptr, Format, Args);
va_end(Args);
Dest->Ptr += Length;
}
int
StringsDiffer(char *A, char *B)
{
while(*A && *B && *A == *B)
{
++A, ++B;
}
return *A - *B;
}
bool
StringsDifferL(char *A, char *B, int LengthofA)
{
int i = 0;
while(i < LengthofA && A[i] && A[i] == B[i])
{
++i;
}
if(!A[i] && LengthofA == i)
{
return FALSE;
}
else
{
return TRUE;
}
}
typedef struct
{
unsigned int Hue:16;
unsigned int Saturation:8;
unsigned int Lightness:8;
} hsl_colour;
hsl_colour
CharToColour(char Char)
{
hsl_colour Colour;
if(Char >= 'a' && Char <= 'z')
{
Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360);
Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74);
}
else if(Char >= 'A' && Char <= 'Z')
{
Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360);
Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74);
}
else if(Char >= '0' && Char <= '9')
{
Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360);
Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74);
}
else
{
Colour.Hue = 180;
Colour.Saturation = 50;
}
return Colour;
}
char *
StringToColourHash(buffer *Buffer, char *String)
{
hsl_colour Colour = {0, 0, 26};
int i;
for(i = 0; String[i]; ++i)
{
Colour.Hue += CharToColour(String[i]).Hue;
Colour.Saturation += CharToColour(String[i]).Saturation;
}
Colour.Hue = Colour.Hue % 360;
Colour.Saturation = Colour.Saturation % 26 + 74;
Buffer->Ptr = Buffer->Location;
CopyStringToBuffer(Buffer, "hsl(%d, %d%%, %d%%)", Colour.Hue, Colour.Saturation, Colour.Lightness);
return(Buffer->Location);
}
int
StringLength(char *String)
{
int i = 0;
while(String[i])
{
++i;
}
return i;
}
char *
SanitisePunctuation(char *String)
{
char *Ptr = String;
while(*Ptr)
{
if(*Ptr == ' ')
{
*Ptr = '_';
}
if((*Ptr < '0' || *Ptr > '9') &&
(*Ptr < 'a' || *Ptr > 'z') &&
(*Ptr < 'A' || *Ptr > 'Z'))
{
*Ptr = '-';
}
++Ptr;
}
return String;
}
char *CategoryMedium[] =
{
"blackboard",
"owl",
"rant",
"research",
"run",
};
int
BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMML_Reference Ref, HMML_Annotation Anno)
{
if(Ref.url && Ref.title && Ref.author && Ref.publisher && Ref.isbn)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.isbn);
CopyString(ReferencesArray[UniqueRefs].Source, "%s (%s)", Ref.author, Ref.publisher);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.page && Ref.url && Ref.title)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].Source, Ref.title);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.page);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.site && Ref.page && Ref.url)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].Source, Ref.site);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.page);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.site && Ref.url && Ref.title)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].Source, Ref.site);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.title && Ref.author && Ref.isbn)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.isbn);
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
CopyString(ReferencesArray[UniqueRefs].URL, "http://www.isbnsearch.org/isbn/%s", Ref.isbn);
}
else if(Ref.url && Ref.article && Ref.author)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.article);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.url && Ref.title && Ref.author)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.url && Ref.title)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else if(Ref.site && Ref.url)
{
CopyString(ReferencesArray[UniqueRefs].ID, Ref.url);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.site);
CopyString(ReferencesArray[UniqueRefs].URL, Ref.url);
}
else
{
return 1;
}
CopyString(ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Timecode, Anno.time);
ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Identifier = RefIdentifier;
return 0;
}
void
BuildCategories(buffer *AnnotationClass, buffer *Category, int *MarkerIndex, bool *HasCategory, char *Marker)
{
for(int i = 0; i < ArrayCount(CategoryMedium); ++i)
{
if(!StringsDiffer(CategoryMedium[i], Marker))
{
CopyStringToBuffer(AnnotationClass, " %s", SanitisePunctuation(Marker));
++*MarkerIndex;
return;
}
}
if(*HasCategory == FALSE)
{
CopyStringToBuffer(Category, "<span class=\"categories\">");
*HasCategory = TRUE;
}
CopyStringToBuffer(Category, "<div class=\"category %s\"></div>",
SanitisePunctuation(Marker));
CopyStringToBuffer(AnnotationClass, " cat_%s",
SanitisePunctuation(Marker));
++*MarkerIndex;
return;
}
#define QUOTE_DIR "/home/matt/git/GitHub/insofaras/25fc16d58a297a486334"
int
StringToInt(char *String)
{
int Result = 0;
while(*String)
{
Result = Result * 10 + (*String - '0');
++String;
}
return Result;
}
int
BuildQuote(quote_info *Info, char *Speaker, int ID)
{
char Path[255] = {0};
sprintf(Path, "%s/#%s", QUOTE_DIR, Speaker);
FILE *File;
if(!(File = fopen(Path, "r")))
{
perror("hmml_to_html");
return 1;
}
fseek(File, 0, SEEK_END);
int Length = ftell(File);
fseek(File, 0, SEEK_SET);
char *Buffer;
if(!(Buffer = malloc(Length)))
{
perror("hmml_to_html");
}
fread(Buffer, Length, 1, File);
fclose(File);
char *InPtr = Buffer;
while(InPtr - Buffer < Length)
{
char InID[4] = {0};
char *OutPtr = InID;
while(*InPtr != ',')
{
*OutPtr++ = *InPtr++;
}
*OutPtr = '\0';
if(StringToInt(InID) == ID)
{
InPtr += 2;
OutPtr = Info->Date;
while(*InPtr != '"')
{
*OutPtr++ = *InPtr++;
}
*OutPtr = '\0';
InPtr += 3;
OutPtr = Info->Text;
while(*InPtr != '\n')
{
if(*InPtr == '\\')
{
++InPtr;
}
*OutPtr++ = *InPtr++;
}
*--OutPtr = '\0';
free(Buffer);
return 0;
}
else
{
while(*InPtr != '\n')
{
++InPtr;
}
++InPtr;
}
}
free(Buffer);
return 1;
}
void
GenerateTopicColours(buffer *Colour, char *Topic)
{
for(int i = 0; i < ArrayCount(CategoryMedium); ++i)
{
if(!StringsDiffer(Topic, CategoryMedium[i]))
{
return;
}
}
FILE *TopicsFile;
char *TopicsBuffer;
if((TopicsFile = fopen("topics.css", "a+")))
{
fseek(TopicsFile, 0, SEEK_END);
int TopicsLength = ftell(TopicsFile);
fseek(TopicsFile, 0, SEEK_SET);
if(!(TopicsBuffer = malloc(TopicsLength)))
{
perror("hmml_to_html");
return;
}
fread(TopicsBuffer, TopicsLength, 1, TopicsFile);
char *TopicsPtr = TopicsBuffer;
while(TopicsPtr - TopicsBuffer < TopicsLength)
{
TopicsPtr += 39;
if(!StringsDifferL(SanitisePunctuation(Topic), TopicsPtr, StringLength(Topic)))
{
free(TopicsBuffer);
fclose(TopicsFile);
return;
}
while(TopicsPtr - TopicsBuffer < TopicsLength && *TopicsPtr != '\n')
{
++TopicsPtr;
}
++TopicsPtr;
}
fprintf(TopicsFile, ".marker .content .categories .category.%s { border-color: %s; background: %s; }\n",
SanitisePunctuation(Topic), StringToColourHash(Colour, Topic), StringToColourHash(Colour, Topic));
fclose(TopicsFile);
free(TopicsBuffer);
}
else
{
perror("hmml_to_html");
return;
}
}
int
main(int ArgC, char **Args)
{
if(ArgC < 2)
{
fprintf(stderr, "Usage: %s filename(s)\n", Args[0]);
return 1;
}
// NOTE(matt): Init MemoryArena
char *MemoryArena;
int ArenaSize = 1024 * 1024;
if(!(MemoryArena = calloc(ArenaSize, 1)))
{
perror(Args[0]);
return 1;
}
int ClaimedMemory = 0;
// NOTE(matt): Setup buffers and ptrs
char *InPtr;
buffer Title;
buffer QuoteMenu;
buffer ReferenceMenu;
buffer Player;
buffer Annotation;
buffer AnnotationHeader;
buffer AnnotationClass;
buffer AnnotationData;
buffer Text;
buffer Category;
buffer Colour;
buffer Master;
for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex)
{
// TODO(matt): Maybe look into this further. It works, but there may be
// bugs lurking
ClaimedMemory = 0;
FILE *InFile;
if(!(InFile = fopen(Args[FileIndex], "r")))
{
perror(Args[0]);
free(MemoryArena);
return 1;
}
HMML_Output HMML = hmml_parse_file(InFile);
fclose(InFile);
if(HMML.well_formed)
{
ClaimBuffer(MemoryArena, &ClaimedMemory, &Title, 1024 * 16);
ref_info ReferencesArray[200] = { 0 };
ClaimBuffer(MemoryArena, &ClaimedMemory, &Player, 1024 * 256);
bool HasQuoteMenu = FALSE;
bool HasReferenceMenu = FALSE;
int QuoteIdentifier = 0x3b1;
int RefIdentifier = 1;
int UniqueRefs = 0;
CopyStringToBuffer(&Title,
" <div class=\"title %s\">\n"
" <span class=\"episode_name\">%s</span>\n", HMML.metadata.project, HMML.metadata.title);
CopyStringToBuffer(&Player,
" <div class=\"player_container\">\n"
" <div class=\"video_container\" data-videoId=\"%s\"></div>\n"
" <div class=\"markers_container %s\">\n", HMML.metadata.id, HMML.metadata.project);
for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex)
{
HMML_Annotation *Anno = HMML.annotations + AnnotationIndex;
bool HasCategory = FALSE;
bool HasQuote = FALSE;
bool HasReference = FALSE;
quote_info QuoteInfo = { 0 };
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationHeader, 512);
ClaimBuffer(MemoryArena, &ClaimedMemory, &Category, 256);
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationClass, 256);
ClaimBuffer(MemoryArena, &ClaimedMemory, &Text, 1024 * 4);
ClaimBuffer(MemoryArena, &ClaimedMemory, &Colour, 32);
CopyStringToBuffer(&AnnotationHeader,
" <div data-timestamp=\"%d\"",
TimecodeToSeconds(Anno->time));
CopyStringToBuffer(&AnnotationClass,
" class=\"marker");
if(Anno->author)
{
CopyStringToBuffer(&AnnotationClass, " authored");
CopyStringToBuffer(&Text,
"<span class=\"author\" style=\"color: %s;\">%s</span> ",
StringToColourHash(&Colour, Anno->author),
Anno->author);
}
InPtr = Anno->text;
int MarkerIndex = 0, RefIndex = 0;
while(*InPtr || RefIndex < Anno->reference_count)
{
if(MarkerIndex < Anno->marker_count &&
InPtr - Anno->text == Anno->markers[MarkerIndex].offset)
{
char *Readable = Anno->markers[MarkerIndex].parameter
? Anno->markers[MarkerIndex].parameter
: Anno->markers[MarkerIndex].marker;
if(Anno->markers[MarkerIndex].type == HMML_MEMBER)
{
CopyStringToBuffer(&Text,
// TODO(matt): Hoverbox
"<a href=\"https://handmade.network/m/%s\" target=\"blank\" style=\"color: %s; text-decoration: none\">%.*s</a>",
Anno->markers[MarkerIndex].marker,
StringToColourHash(&Colour, Anno->markers[MarkerIndex].marker),
StringLength(Readable), InPtr);
InPtr += StringLength(Readable);
++MarkerIndex;
}
else if(Anno->markers[MarkerIndex].type == HMML_PROJECT)
{
CopyStringToBuffer(&Text,
// TODO(matt): Hoverbox
"<a href=\"https://%s.handmade.network/\" target=\"blank\" style=\"color: %s; text-decoration: none\">%s</a>",
Anno->markers[MarkerIndex].marker,
StringToColourHash(&Colour, Anno->markers[MarkerIndex].marker),
Readable);
InPtr += StringLength(Readable);
++MarkerIndex;
}
else if(Anno->markers[MarkerIndex].type == HMML_CATEGORY)
{
GenerateTopicColours(&Colour, Anno->markers[MarkerIndex].marker);
BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, Anno->markers[MarkerIndex].marker);
}
}
if(RefIndex < Anno->reference_count &&
InPtr - Anno->text == Anno->references[RefIndex].offset)
{
HMML_Reference *CurrentRef = Anno->references + RefIndex;
if(!HasReferenceMenu)
{
ClaimBuffer(MemoryArena, &ClaimedMemory, &ReferenceMenu, 1024 * 16);
CopyStringToBuffer(&ReferenceMenu,
" <div class=\"refs_container\">\n"
" <span>References &#9660;</span>\n"
" <div class=\"mouse_catcher\"></div>\n"
" <div class=\"refs\">\n");
if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1)
{
fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n", Args[FileIndex], Anno->line);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
++ReferencesArray[RefIdentifier - 1].IdentifierCount;
++UniqueRefs;
HasReferenceMenu = TRUE;
}
else
{
for(int i = 0; i < UniqueRefs; ++i)
{
if(CurrentRef->isbn)
{
if(!StringsDiffer(CurrentRef->isbn, ReferencesArray[i].ID))
{
CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, Anno->time);
ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier;
++ReferencesArray[i].IdentifierCount;
goto AppendedIdentifier;
}
}
else if(CurrentRef->url)
{
if(!StringsDiffer(CurrentRef->url, ReferencesArray[i].ID))
{
CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, Anno->time);
ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier;
++ReferencesArray[i].IdentifierCount;
goto AppendedIdentifier;
}
}
else
{
fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Args[FileIndex], Anno->line);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
}
if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1)
{
fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n", Args[FileIndex], Anno->line);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
++ReferencesArray[UniqueRefs].IdentifierCount;
++UniqueRefs;
}
AppendedIdentifier:
if(!HasReference)
{
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationData, 128);
if(CurrentRef->isbn)
{
CopyStringToBuffer(&AnnotationData, " data-ref=\"%s", CurrentRef->isbn);
}
else if(CurrentRef->url)
{
CopyStringToBuffer(&AnnotationData, " data-ref=\"%s", CurrentRef->url);
}
else
{
fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Args[FileIndex], Anno->line);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
HasReference = TRUE;
}
else
{
if(CurrentRef->isbn)
{
CopyStringToBuffer(&AnnotationData, ",%s", CurrentRef->isbn);
}
else if(CurrentRef->url)
{
CopyStringToBuffer(&AnnotationData, ",%s", CurrentRef->url);
}
else
{
fprintf(stderr, "%s:%d: Reference must have an ISBN or URL", Args[FileIndex], Anno->line);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
}
CopyStringToBuffer(&Text, "<sup>%d</sup>", RefIdentifier);
++RefIndex;
++RefIdentifier;
}
if(*InPtr)
{
*Text.Ptr++ = *InPtr++;
}
}
if(Anno->is_quote)
{
if(!HasQuoteMenu)
{
ClaimBuffer(MemoryArena, &ClaimedMemory, &QuoteMenu, 1024 * 16);
CopyStringToBuffer(&QuoteMenu,
" <div class=\"refs_container\">\n"
" <span>Quotes &#9660;</span>\n"
" <div class=\"mouse_catcher\"></div>\n"
" <div class=\"refs\">\n");
HasQuoteMenu = TRUE;
}
if(!HasReference)
{
ClaimBuffer(MemoryArena, &ClaimedMemory, &AnnotationData, 128);
CopyStringToBuffer(&AnnotationData, " data-ref=\"&#%d;", QuoteIdentifier);
}
else
{
CopyStringToBuffer(&AnnotationData, ",&#%d;", QuoteIdentifier);
}
HasQuote = TRUE;
if(BuildQuote(&QuoteInfo, HMML.metadata.twitch ? HMML.metadata.twitch : HMML.metadata.member, Anno->quote.id) == 1)
{
fprintf(stderr, "%s:%d: Quote #%s %d not found! Consider pulling the latest quotes\n",
Args[FileIndex],
Anno->line,
HMML.metadata.twitch ? HMML.metadata.twitch : HMML.metadata.member,
Anno->quote.id);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
CopyStringToBuffer(&QuoteMenu,
" <span data-id=\"&#%d;\" class=\"ref\">\n"
" <span class=\"ref_content\">\n"
" <div class=\"source\">Quote %d</div>\n"
" <div class=\"ref_title\">%s</div>\n"
" <div class=\"quote_byline\">&mdash;%s, %s</div>\n"
" </span>\n"
" <div class=\"ref_indices\">\n"
" <span data-timestamp=\"%d\" class=\"timecode\"><span class=\"ref_index\">[&#%d;]</span><span class=\"time\">%s</span></span>\n"
" </div>\n"
" </span>\n",
QuoteIdentifier,
Anno->quote.id,
QuoteInfo.Text,
HMML.metadata.twitch ? HMML.metadata.twitch : HMML.metadata.member,
QuoteInfo.Date,
TimecodeToSeconds(Anno->time),
QuoteIdentifier,
Anno->time);
if(!Anno->text[0])
{
CopyStringToBuffer(&Text, "&#8220;%s&#8221;", QuoteInfo.Text);
}
CopyStringToBuffer(&Text, "<sup>&#%d;</sup>", QuoteIdentifier);
++QuoteIdentifier;
}
while(MarkerIndex < Anno->marker_count)
{
GenerateTopicColours(&Colour, Anno->markers[MarkerIndex].marker);
ClaimedMemory -= Colour.Size;
BuildCategories(&AnnotationClass, &Category, &MarkerIndex, &HasCategory, Anno->markers[MarkerIndex].marker);
}
CopyStringToBuffer(&AnnotationClass, "\"");
CopyBuffer(&AnnotationHeader, &AnnotationClass);
ClaimedMemory -= AnnotationClass.Size;
if(HasQuote || HasReference)
{
CopyStringToBuffer(&AnnotationData, "\"");
CopyBuffer(&AnnotationHeader, &AnnotationData);
ClaimedMemory -= AnnotationData.Size;
}
CopyStringToBuffer(&AnnotationHeader, ">\n");
ClaimBuffer(MemoryArena, &ClaimedMemory, &Annotation, 1024 * 4);
CopyBuffer(&Annotation, &AnnotationHeader);
ClaimedMemory -= AnnotationHeader.Size;
CopyStringToBuffer(&Annotation,
" <div class=\"content\"><span class=\"timecode\">%s</span>",
Anno->time);
if(HasCategory)
{
CopyStringToBuffer(&Category, "</span>");
CopyBuffer(&Text, &Category);
ClaimedMemory -= Category.Size;
}
*Text.Ptr = '\0';
CopyBuffer(&Annotation, &Text);
CopyStringToBuffer(&Annotation, "</div>\n"
" <div class=\"progress faded\">\n"
" <div class=\"content\"><span class=\"timecode\">%s</span>",
Anno->time);
CopyBuffer(&Annotation, &Text);
CopyStringToBuffer(&Annotation, "</div>\n"
" </div>\n"
" <div class=\"progress main\">\n"
" <div class=\"content\"><span class=\"timecode\">%s</span>",
Anno->time);
CopyBuffer(&Annotation, &Text);
ClaimedMemory -= Text.Size;
CopyStringToBuffer(&Annotation, "</div>\n"
" </div>\n"
" </div>\n");
CopyBuffer(&Player, &Annotation);
ClaimedMemory -= Annotation.Size;
}
if(HasQuoteMenu)
{
CopyStringToBuffer(&QuoteMenu,
" </div>\n"
" </div>\n");
CopyBuffer(&Title, &QuoteMenu);
ClaimedMemory -= QuoteMenu.Size;
}
if(HasReferenceMenu)
{
for(int i = 0; i < UniqueRefs; ++i)
{
CopyStringToBuffer(&ReferenceMenu,
" <a data-id=\"%s\" href=\"%s\" target=\"_blank\" class=\"ref\">\n"
" <span class=\"ref_content\">\n",
ReferencesArray[i].ID,
ReferencesArray[i].URL);
if(*ReferencesArray[i].Source)
{
CopyStringToBuffer(&ReferenceMenu,
" <div class=\"source\">%s</div>\n"
" <div class=\"ref_title\">%s</div>\n",
ReferencesArray[i].Source,
ReferencesArray[i].RefTitle);
}
else
{
CopyStringToBuffer(&ReferenceMenu,
" <div class=\"ref_title\">%s</div>\n",
ReferencesArray[i].RefTitle);
}
CopyStringToBuffer(&ReferenceMenu,
" </span>\n");
for(int j = 0; j < ReferencesArray[i].IdentifierCount;)
{
CopyStringToBuffer(&ReferenceMenu,
" <div class=\"ref_indices\">\n ");
for(int k = 0; k < 3 && j < ReferencesArray[i].IdentifierCount; ++k, ++j)
{
CopyStringToBuffer(&ReferenceMenu,
"<span data-timestamp=\"%d\" class=\"timecode\"><span class=\"ref_index\">[%d]</span><span class=\"time\">%s</span></span>",
TimecodeToSeconds(ReferencesArray[i].Identifier[j].Timecode),
ReferencesArray[i].Identifier[j].Identifier,
ReferencesArray[i].Identifier[j].Timecode);
}
CopyStringToBuffer(&ReferenceMenu, "\n"
" </div>\n");
}
CopyStringToBuffer(&ReferenceMenu,
" </a>\n");
}
CopyStringToBuffer(&ReferenceMenu,
" </div>\n"
" </div>\n");
CopyBuffer(&Title, &ReferenceMenu);
ClaimedMemory -= ReferenceMenu.Size;
}
CopyStringToBuffer(&Title,
" <span class=\"annotator_container\">Annotator: <span class=\"annotator\">%s</span></span>\n"
" </div>\n",
HMML.metadata.annotator);
CopyStringToBuffer(&Player,
" </div>\n"
" </div>\n");
//NOTE(matt): Collate the buffers!
ClaimBuffer(MemoryArena, &ClaimedMemory, &Master, 1024 * 512);
CopyStringToBuffer(&Master,
"<html>\n"
" <head>\n"
" <meta charset=\"UTF-8\">\n"
"\n"
" <!-- Load the player -->\n"
" <script type=\"text/javascript\" src=\"player.js\"></script>\n"
" <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n"
" <link rel=\"stylesheet\" type=\"text/css\" href=\"%s.css\">\n"
" <link rel=\"stylesheet\" type=\"text/css\" href=\"topics.css\">\n"
" </head>\n"
" <body>\n", HMML.metadata.project);
//NOTE(matt): Here is where we do all our CopyBuffer() calls
CopyBuffer(&Master, &Title);
ClaimedMemory -= Title.Size;
CopyBuffer(&Master, &Player);
ClaimedMemory -= Player.Size;
//
CopyStringToBuffer(&Master,
" <script>\n"
" var player = new Player(document.querySelector(\".player_container\"), onRefChanged);\n"
" window.addEventListener(\"resize\", function() { player.updateSize(); });\n"
" document.addEventListener(\"keypress\", function(ev) {\n"
" switch (ev.key) {\n"
" case 'n':\n"
" case 'd':\n"
" case 's': {\n"
" player.jumpToNextMarker();\n"
" } break;\n"
"\n"
" case 'p':\n"
" case 'a':\n"
" case 'w': {\n"
" player.jumpToPrevMarker();\n"
" } break;\n"
" }\n"
"});\n"
"\n"
"var refTimecodes = document.querySelectorAll(\".refs .ref .timecode\");\n"
"for (var i = 0; i < refTimecodes.length; ++i) {\n"
" refTimecodes[i].addEventListener(\"click\", function(ev) {\n"
" if (player) {\n"
" var time = ev.currentTarget.getAttribute(\"data-timestamp\");\n"
" player.setTime(parseInt(time, 10));\n"
" player.play();\n"
" ev.preventDefault();\n"
" ev.stopPropagation();\n"
" return false;\n"
" }\n"
" });\n"
"}\n"
"\n"
"var refSources = document.querySelectorAll(\".refs .ref\");\n"
"for (var i = 0; i < refSources.length; ++i) {\n"
" refSources[i].addEventListener(\"click\", function(ev) {\n"
" if (player) {\n"
" player.pause();\n"
" }\n"
" });\n"
"}\n"
"\n"
"function onRefChanged(ref) {\n"
" var sourceMenus = document.querySelectorAll(\".refs_container\");\n"
" for (var MenuIndex = 0; MenuIndex < sourceMenus.length; ++MenuIndex)\n"
" {\n"
" var SetMenu = 0;\n"
" if (ref !== undefined && ref !== null) {\n"
" var refElements = sourceMenus[MenuIndex].querySelectorAll(\".refs .ref\");\n"
" var refs = ref.split(\",\");\n"
"\n"
" for (var i = 0; i < refElements.length; ++i) {\n"
" if (refs.includes(refElements[i].getAttribute(\"data-id\"))) {\n"
" refElements[i].classList.add(\"current\");\n"
" SetMenu = 1;\n"
" } else {\n"
" refElements[i].classList.remove(\"current\");\n"
" }\n"
" }\n"
" if(SetMenu) {\n"
" sourceMenus[MenuIndex].classList.add(\"current\");\n"
" } else {\n"
" sourceMenus[MenuIndex].classList.remove(\"current\");\n"
" }\n"
"\n"
" } else {\n"
" sourceMenus[MenuIndex].classList.remove(\"current\");\n"
" var refs = sourceMenus[MenuIndex].querySelectorAll(\".refs .ref\");\n"
" for (var i = 0; i < refs.length; ++i) {\n"
" refs[i].classList.remove(\"current\");\n"
" }\n"
" }\n"
" }\n"
"}\n"
" </script>\n"
" </body>\n"
"</html>\n");
FILE *OutFile;
if(!(OutFile = fopen("out.html", "w")))
{
perror(Args[0]);
hmml_free(&HMML);
free(MemoryArena);
return 1;
}
fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile);
fclose(OutFile);
ClaimedMemory -= Master.Size;
}
else
{
fprintf(stderr, "%s:%d: %s\n", Args[FileIndex], HMML.error.line, HMML.error.message);
}
hmml_free(&HMML);
}
free(MemoryArena);
}