/* * Nextview GUI: Output of PI data in HTML * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License Version 2 as * published by the Free Software Foundation. You find a copy of this * license in the file COPYRIGHT in the root directory of this release. * * THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, * BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * Description: * * This module implements methods to export PI in HTML. * * Author: Tom Zoerner * * $Id: dumphtml.c,v 1.3 2004/12/29 12:04:51 tom Exp tom $ */ #define DEBUG_SWITCH DEBUG_SWITCH_EPGUI #define DPRINTF_OFF #include #include #include #include #include #include #include #include "epgctl/mytypes.h" #include "epgctl/debug.h" #include "epgctl/epgversion.h" #include "epgdb/epgblock.h" #include "epgdb/epgdbfil.h" #include "epgdb/epgdbif.h" #include "epgdb/epgtscqueue.h" #include "epgdb/epgdbmerge.h" #include "epgui/epgmain.h" #include "epgui/pdc_themes.h" #include "epgui/pifilter.h" #include "epgui/pibox.h" #include "epgui/pidescr.h" #include "epgui/pioutput.h" #include "epgui/dumphtml.h" // ---------------------------------------------------------------------------- // Append a text string to the HTML output file, while quoting HTML special chars // - string is terminated wither by zero or by strlen parameter; // special value "-1" can be used to ignore strlen // - HTML special chars are: < > & converted to: < > & // void EpgDumpHtml_WriteString( FILE *fp, const char * pText, sint strlen ) { char outbuf[256], *po; uint outlen; po = outbuf; outlen = sizeof(outbuf); while ((*pText != 0) && (strlen-- != 0)) { if (outlen < 6) { // buffer is almost full -> flush it fwrite(outbuf, sizeof(char), sizeof(outbuf) - outlen, fp); po = outbuf; outlen = sizeof(outbuf); } if (*pText == '<') { pText++; strcpy(po, "<"); po += 4; outlen -= 4; } else if (*pText == '>') { pText++; strcpy(po, ">"); po += 4; outlen -= 4; } else if (*pText == '&') { pText++; strcpy(po, "&"); po += 5; outlen -= 5; } else { *(po++) = *(pText++); outlen -= 1; } } // flush the output buffer if (outlen != sizeof(outbuf)) { fwrite(outbuf, sizeof(char), sizeof(outbuf) - outlen, fp); } } // ---------------------------------------------------------------------------- // Copy string with double quotes (") replaced with single quotes (') // - for use inside HTML tags, e.g. // void EpgDumpHtml_RemoveQuotes( const uchar * pStr, uchar * pBuf, uint maxOutLen ) { while ((*pStr != 0) && (maxOutLen > 1)) { if (*pStr == '"') { *(pBuf++) = '\''; pStr++; } else *(pBuf++) = *(pStr++); maxOutLen -= 1; } if (maxOutLen > 0) *pBuf = 0; } // ---------------------------------------------------------------------------- // Print Short- and Long-Info texts or separators // - note: in HTML output there is no special separator between descriptions // of different providers in a merged database, hence the "addSeparator" // parameter is not used // static void EpgDumpHtml_AppendInfoTextCb( void *vp, const char * pDesc, bool addSeparator ) { FILE * fp = (FILE *) vp; const char * pNewline; if ((fp != NULL) && (pDesc != NULL)) { fprintf(fp, "\n\n

\n"); // replace newline characters with paragraph tags while ( (pNewline = strchr(pDesc, '\n')) != NULL ) { // print text up to (and excluding) the newline EpgDumpHtml_WriteString(fp, pDesc, pNewline - pDesc); fprintf(fp, "\n

\n"); // skip to text following the newline pDesc = pNewline + 1; } // write the segement behind the last newline EpgDumpHtml_WriteString(fp, pDesc, -1); fprintf(fp, "\n

\n\n\n\n"); } } // ---------------------------------------------------------------------------- // Append HTML for one PI // static void EpgDumpHtml_WritePi( FILE *fp, const PI_BLOCK * pPiBlock, const AI_BLOCK * pAiBlock ) { const uchar *pCfNetname; uchar date_str[20], start_str[20], stop_str[20], cni_str[7], label_str[50]; strftime(date_str, sizeof(date_str), " %a %d.%m", localtime(&pPiBlock->start_time)); strftime(start_str, sizeof(start_str), "%H:%M", localtime(&pPiBlock->start_time)); strftime(stop_str, sizeof(stop_str), "%H:%M", localtime(&pPiBlock->stop_time)); strftime(label_str, sizeof(label_str), "%Y%m%d%H%M", localtime(&pPiBlock->start_time)); sprintf(cni_str, "0x%04X", AI_GET_NETWOP_N(pAiBlock, pPiBlock->netwop_no)->cni); pCfNetname = Tcl_GetVar2(interp, "cfnetnames", cni_str, TCL_GLOBAL_ONLY); if (pCfNetname == NULL) pCfNetname = AI_GET_NETWOP_NAME(pAiBlock, pPiBlock->netwop_no); // start HTML table for PI and append first row: running time, title, network name fprintf(fp, "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n\n", date_str ); // start second row: themes & features fprintf(fp, "\n\n\n"); // start third row: description PiDescription_AppendShortAndLongInfoText(pPiBlock, EpgDumpHtml_AppendInfoTextCb, fp, EpgDbContextIsMerged(pUiDbContext)); fprintf(fp, "
\n" "%s - %s\n" // running time "\n", pPiBlock->netwop_no, label_str, start_str, stop_str); EpgDumpHtml_WriteString(fp, PI_GET_TITLE(pPiBlock), -1); fprintf(fp, "\n" "\n"); EpgDumpHtml_WriteString(fp, pCfNetname, -1); fprintf(fp, "\n" "
\n" "%s\n" // date "
\n"); // append theme list PiDescription_AppendCompressedThemes(pPiBlock, comm, TCL_COMM_BUF_SIZE); EpgDumpHtml_WriteString(fp, comm, -1); // append features list strcpy(comm, " ("); PiDescription_AppendFeatureList(pPiBlock, comm + 2); if (comm[2] != 0) { strcat(comm, ")"); EpgDumpHtml_WriteString(fp, comm, -1); } fprintf(fp, "\n
\n
\n\n\n"); } // ---------------------------------------------------------------------------- // HTML page templates // static const uchar * const html_head = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "Nextview EPG\n" "\n" "\n" "\n" "\n\n"; static const uchar * const html_marker_cont_title = "\n"; static const uchar * const html_marker_cont_desc = "\n"; typedef enum { HTML_DUMP_NONE, HTML_DUMP_DESC, HTML_DUMP_TITLE, HTML_DUMP_END } HTML_DUMP_TYPE; // ---------------------------------------------------------------------------- // Open source and create destination HTML files // static void EpgDumpHtml_Create( const char * pFileName, bool optAppend, FILE ** fppSrc, FILE ** fppDst, const AI_BLOCK * pAiBlock ) { char * pBakName; FILE *fpSrc, *fpDst; time_t now; bool abort = FALSE; fpDst = fpSrc = NULL; pBakName = NULL; if (optAppend) { // open the old file for reading - ignore if not exists fpSrc = fopen(pFileName, "r"); if (fpSrc != NULL) { // exists -> rename old file to .bak and then create new output file pBakName = xmalloc(strlen(pFileName) + 10); strcpy(pBakName, pFileName); strcat(pBakName, ".bak"); #ifndef WIN32 if (rename(pFileName, pBakName) == -1) { sprintf(comm, "tk_messageBox -type ok -icon error -parent .dumphtml " "-message \"Failed to rename old HTML file '%s' to %s: %s\"", pFileName, pBakName, strerror(errno)); eval_check(interp, comm); Tcl_ResetResult(interp); abort = TRUE; } #else // WIN32 cannot rename opened files, hence the special handling fclose(fpSrc); // remove the backup file if it already exists if ( (remove(pBakName) != -1) || (errno == ENOENT) ) { // rename the old file to the backup file name if (rename(pFileName, pBakName) != -1) { // finally open the old file again for reading fpSrc = fopen(pBakName, "r"); } else { sprintf(comm, "tk_messageBox -type ok -icon error -parent .dumphtml " "-message \"Failed to rename old HTML file '%s' to %s: %s\"", pFileName, pBakName, strerror(errno)); eval_check(interp, comm); Tcl_ResetResult(interp); abort = TRUE; } } else { sprintf(comm, "tk_messageBox -type ok -icon error -parent .dumphtml " "-message \"Failed to remove old backup file %s: %s\"", pBakName, strerror(errno)); eval_check(interp, comm); Tcl_ResetResult(interp); abort = TRUE; } #endif } } if (abort == FALSE) { fpDst = fopen(pFileName, "w"); if (fpDst != NULL) { if (fpSrc == NULL) { // new file -> create HTML head now = time(NULL); // copy the service name while replacing double quotes EpgDumpHtml_RemoveQuotes(AI_GET_SERVICENAME(pAiBlock), comm, TCL_COMM_BUF_SIZE); fprintf(fpDst, html_head, ctime(&now), comm); } } else { // access, create or truncate failed -> inform the user sprintf(comm, "tk_messageBox -type ok -icon error -parent .dumphtml -message \"Failed to open file '%s' for writing: %s\"", pFileName, strerror(errno)); eval_check(interp, comm); Tcl_ResetResult(interp); } } if (pBakName != NULL) xfree(pBakName); *fppSrc = fpSrc; *fppDst = fpDst; } // ---------------------------------------------------------------------------- // Skip to next section // - must be called in order for all the sections // - the insertion point is marked by one of the HTML comments defined above // - everything up to the point is copied into a second file // the marker itself is not copied; it must be written anew after the insertion // static void EpgDumpHtml_Skip( FILE * fpSrc, FILE * fpDst, HTML_DUMP_TYPE prevType, HTML_DUMP_TYPE nextType ) { #define HTML_COPY_BUF_SIZE 256 char buffer[HTML_COPY_BUF_SIZE]; const char * pMarker; bool skipLine; int len; // append the marker for the previous section if (prevType == HTML_DUMP_TITLE) fprintf(fpDst, "%s\n", html_marker_cont_title); else if (prevType == HTML_DUMP_DESC) fprintf(fpDst, "%s\n", html_marker_cont_desc); if (fpSrc != NULL) { // determine which marker to search for if (nextType == HTML_DUMP_TITLE) pMarker = html_marker_cont_title; else if (nextType == HTML_DUMP_DESC) pMarker = html_marker_cont_desc; else pMarker = NULL; skipLine = FALSE; // loop over all lines in the old file while (fgets(buffer, sizeof(buffer), fpSrc) != NULL) { if ((skipLine == FALSE) && (pMarker != NULL) && (strcmp(buffer, pMarker) == 0)) { // found the insertion point break; } // check if the line was too long for the buffer // if yes, the result of the next read must be skipped len = strlen(buffer); skipLine = ((len > 0) && (buffer[len - 1] != '\n')); // copy the line into the destination file fwrite(buffer, 1, len, fpDst); } } else { // new file // close title section if (prevType == HTML_DUMP_TITLE) fprintf(fpDst, "\n\n\n"); // create head for requested section if (nextType == HTML_DUMP_TITLE) fprintf(fpDst, "\n"); if (nextType == HTML_DUMP_DESC) fprintf(fpDst, "


\n\n"); } } // ---------------------------------------------------------------------------- // Finish the HTML output file // static void EpgDumpHtml_Close( FILE * fpSrc, FILE * fpDst, const AI_BLOCK * pAiBlock ) { if (fpDst != NULL) { if (fpSrc == NULL) { // newly created file -> finish HTML page fprintf(fpDst, "

\n© Nextview EPG by "); EpgDumpHtml_WriteString(fpDst, AI_GET_SERVICENAME(pAiBlock), -1); fprintf(fpDst, "\n

\n\n\n"); } else { // nothing to do here - footer already copied from original file fclose(fpSrc); } fclose(fpDst); } } // ---------------------------------------------------------------------------- // Dump HTML for one programme column // - also supports user-defined columns // - XXX TODO: use for colors and add support for all-column fore-/background colors // static void EpgDumpHtml_Title( FILE * fpDst, const PI_BLOCK * pPiBlock, const PIBOX_COL_CFG * pColTab, uint colCount, uint hyperlinkColIdx, bool optTextFmt ) { PIBOX_COL_TYPES type; Tcl_Obj * pFmtObj; Tcl_Obj ** pFmtObjv; Tcl_Obj * pImageObj; Tcl_Obj ** pImgObjv; Tcl_Obj * pImgSpec; const char * pFmtStr; bool hasBold, hasEm, hasStrike, hasColor; int fmtObjc; int imgObjc; uint colIdx; uint len; sint fmtIdx; if ((fpDst != NULL) && (pPiBlock != NULL) && (pColTab != NULL)) { fprintf(fpDst, "\n"); // add table columns in the same configuration as for the internal listbox for (colIdx=0; colIdx < colCount; colIdx++) { fprintf(fpDst, "\n", ((colIdx == hyperlinkColIdx) ? "\n" : "")); } fprintf(fpDst, "\n\n"); } else fatal3("EpgDumpHtml-Title: illegal NULL ptr param: fp=%d pPi=%d, pCol=%d", (fpDst != NULL), (pPiBlock != NULL), (pColTab != NULL)); } // ---------------------------------------------------------------------------- // Dump programme titles and/or descriptions into file in HTML format // static int EpgDumpHtml_DumpDatabase( ClientData ttp, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ) { const char * const pUsage = "Usage: C_DumpHtml " " " " "; const AI_BLOCK *pAiBlock; const PI_BLOCK *pPiBlock; const char * pFileName; Tcl_DString ds; const PIBOX_COL_CFG * pColTab; Tcl_Obj ** pColObjv; FILE *fpSrc, *fpDst; sint piIdx; int doTitles, doDesc, optAppend, optSelOnly, optMaxCount, optHyperlinks, optTextFmt; int colCount; int result; if (objc != 1+9) { // parameter count is invalid Tcl_SetResult(interp, (char *)pUsage, TCL_STATIC); result = TCL_ERROR; } else if ( (pFileName = Tcl_GetString(objv[1])) == NULL ) { // internal error: can not get filename string Tcl_SetResult(interp, (char *)pUsage, TCL_STATIC); result = TCL_ERROR; } else if ( (Tcl_GetBooleanFromObj(interp, objv[2], &doTitles) != TCL_OK) || (Tcl_GetBooleanFromObj(interp, objv[3], &doDesc) != TCL_OK) || (Tcl_GetBooleanFromObj(interp, objv[4], &optAppend) != TCL_OK) || (Tcl_GetBooleanFromObj(interp, objv[5], &optSelOnly) != TCL_OK) || (Tcl_GetIntFromObj (interp, objv[6], &optMaxCount) != TCL_OK) || (Tcl_GetIntFromObj (interp, objv[7], &optHyperlinks) != TCL_OK) || (Tcl_GetBooleanFromObj(interp, objv[8], &optTextFmt) != TCL_OK) || (Tcl_ListObjGetElements(interp, objv[9], &colCount, &pColObjv) != TCL_OK) ) { // one of the params is not boolean result = TCL_ERROR; } else { EpgDbLockDatabase(pUiDbContext, TRUE); pAiBlock = EpgDbGetAi(pUiDbContext); if (pAiBlock != NULL) { pFileName = Tcl_UtfToExternalDString(NULL, pFileName, -1, &ds); EpgDumpHtml_Create(pFileName, optAppend, &fpSrc, &fpDst, pAiBlock); if (fpDst != NULL) { // add or skip & copy HTML page header EpgDumpHtml_Skip(fpSrc, fpDst, HTML_DUMP_NONE, HTML_DUMP_TITLE); if (doTitles) { // add selected title or table with list of titles // create column config cache from specification in Tcl list pColTab = PiOutput_CfgColumnsCache(colCount, pColObjv); if (optSelOnly == FALSE) pPiBlock = EpgDbSearchFirstPi(pUiDbContext, pPiFilterContext); else pPiBlock = PiBox_GetSelectedPi(); for (piIdx=0; ((piIdx < optMaxCount) || (optMaxCount < 0)) && (pPiBlock != NULL); piIdx++) { EpgDumpHtml_Title(fpDst, pPiBlock, pColTab, colCount, optHyperlinks, optTextFmt); pPiBlock = EpgDbSearchNextPi(pUiDbContext, pPiFilterContext, pPiBlock); } // free column config cache PiOutput_CfgColumnsClear(pColTab, colCount); } // skip & copy existing descriptions if in append mode EpgDumpHtml_Skip(fpSrc, fpDst, HTML_DUMP_TITLE, HTML_DUMP_DESC); if (doDesc) { if (optSelOnly == FALSE) pPiBlock = EpgDbSearchFirstPi(pUiDbContext, pPiFilterContext); else pPiBlock = PiBox_GetSelectedPi(); // add descriptions for all programmes matching the current filter for (piIdx=0; ((piIdx < optMaxCount) || (optMaxCount < 0)) && (pPiBlock != NULL); piIdx++) { EpgDumpHtml_WritePi(fpDst, pPiBlock, pAiBlock); pPiBlock = EpgDbSearchNextPi(pUiDbContext, pPiFilterContext, pPiBlock); } } EpgDumpHtml_Skip(fpSrc, fpDst, HTML_DUMP_DESC, HTML_DUMP_END); EpgDumpHtml_Close(fpSrc, fpDst, pAiBlock); } Tcl_DStringFree(&ds); } EpgDbLockDatabase(pUiDbContext, FALSE); result = TCL_OK; } return result; } // ---------------------------------------------------------------------------- // Free resources allocated by this module (cleanup during program exit) // void EpgDumpHtml_Destroy( void ) { } // ---------------------------------------------------------------------------- // Create the Tcl/Tk commands provided by this module // - this should be called only once during start-up // void EpgDumpHtml_Init( void ) { Tcl_CreateObjCommand(interp, "C_DumpHtml", EpgDumpHtml_DumpDatabase, (ClientData) NULL, NULL); }
\n"); if (colIdx == hyperlinkColIdx) { // if requested add hyperlink to the description on this column uchar label_str[50]; strftime(label_str, sizeof(label_str), "%Y%m%d%H%M", localtime(&pPiBlock->start_time)); fprintf(fpDst, "\n", pPiBlock->netwop_no, label_str); } len = 0; type = pColTab[colIdx].type; pImageObj = pFmtObj = NULL; if (type == PIBOX_COL_WEEKCOL) type = PIBOX_COL_WEEKDAY; // XXX FIXME weekday colors not implemented for HTML yet if ((type == PIBOX_COL_USER_DEF) || (type == PIBOX_COL_REMINDER)) { len = PiOutput_MatchUserCol(pPiBlock, &type, pColTab[colIdx].pDefObj, comm, TCL_COMM_BUF_SIZE, &pImageObj, &pFmtObj); } if ((type != PIBOX_COL_USER_DEF) && (type != PIBOX_COL_REMINDER) && (type != PIBOX_COL_WEEKCOL)) len = PiOutput_PrintColumnItem(pPiBlock, type, comm, TCL_COMM_BUF_SIZE); if (pImageObj != NULL) { // user-defined column consists of an image pImgSpec = Tcl_GetVar2Ex(interp, "pi_img", Tcl_GetString(pImageObj), TCL_GLOBAL_ONLY); if (pImgSpec != NULL) { if ( (Tcl_ListObjGetElements(interp, pImgSpec, &imgObjc, &pImgObjv) == TCL_OK) && (imgObjc == 2) ) { // note: there's intentionally no WIDTH and HEIGHT tags so that the user can use different images fprintf(fpDst, "\n", Tcl_GetString(pImageObj)); } } } hasBold = hasEm = hasStrike = hasColor = FALSE; if (optTextFmt && (pFmtObj != NULL)) { Tcl_ListObjGetElements(interp, pFmtObj, &fmtObjc, &pFmtObjv); for (fmtIdx=0; fmtIdx < fmtObjc; fmtIdx++) { pFmtStr = Tcl_GetString(pFmtObjv[fmtIdx]); if (strcmp(pFmtStr, "bold") == 0) { fprintf(fpDst, ""); hasBold = TRUE; } else if (strcmp(pFmtStr, "underline") == 0) { fprintf(fpDst, ""); hasEm = TRUE; } else if (strcmp(pFmtStr, "overstrike") == 0) { fprintf(fpDst, ""); hasStrike = TRUE; } else if (strncmp(pFmtStr, "fg_RGB", 6) == 0) { fprintf(fpDst, "", pFmtStr + 6); hasColor = TRUE; } else if (strncmp(pFmtStr, "fg_", 3) == 0) { fprintf(fpDst, "", pFmtStr + 3); hasColor = TRUE; } } } if (len > 0) EpgDumpHtml_WriteString(fpDst, comm, len); if (hasColor) fprintf(fpDst, ""); if (hasStrike) fprintf(fpDst, ""); if (hasEm) fprintf(fpDst, ""); if (hasBold) fprintf(fpDst, ""); fprintf(fpDst, "%s\n