/* * Nextview GUI: Output of programme data in XML * * 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 XML. * * Author: Tom Zoerner * * $Id: dumpxml.c,v 1.12.1.3 2005/04/02 16:56:46 tom Exp $ */ #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/pidescr.h" #include "epgui/dumphtml.h" #include "epgui/dumptext.h" #include "epgui/dumpxml.h" #include "epgtcl/dlg_dump.h" #define IS_XML_DTD5(X) ((X) != EPGTCL_XMLTV_DTD_6) // typedef struct { FILE * fp; int xmlDtdVersion; } DUMP_XML_CB_INFO; // ---------------------------------------------------------------------------- // Print Short- and Long-Info texts for XMLTV // static void EpgDumpXml_AppendInfoTextCb( void *vp, const char * pDesc, bool addSeparator ) { DUMP_XML_CB_INFO * pCbInfo = vp; FILE * fp; const char * pNewline; if ((pCbInfo != NULL) && (pCbInfo->fp != NULL) && (pDesc != NULL)) { fp = pCbInfo->fp; if (addSeparator) { if ( IS_XML_DTD5(pCbInfo->xmlDtdVersion) ) fprintf(fp, "\n\n"); else fprintf(fp, "

\n\t

"); } // 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); if ( IS_XML_DTD5(pCbInfo->xmlDtdVersion) ) fprintf(fp, "\n\n"); else fprintf(fp, "

\n

"); // skip to text following the newline pDesc = pNewline + 1; } // write the segment behind the last newline EpgDumpHtml_WriteString(fp, pDesc, -1); } } // ---------------------------------------------------------------------------- // Helper func: read integer from global Tcl var // - DTD version constants are defined at Tcl script level // static int EpgDumpXml_QueryXmlDtdVersion( Tcl_Interp *interp ) { Tcl_Obj * pVarObj; int value; pVarObj = Tcl_GetVar2Ex(interp, "dumpxml_format", NULL, TCL_GLOBAL_ONLY); if ( (pVarObj == NULL) || (Tcl_GetIntFromObj(interp, pVarObj, &value) != TCL_OK) ) { debug1("EpgDumpXml-QueryLocaltimeSetting: failed to parse Tcl var 'dumpxml_format': %s", ((pVarObj != NULL) ? Tcl_GetString(pVarObj) : "*undef*")); value = EPGTCL_XMLTV_DTD_5_GMT; } return value; } // ---------------------------------------------------------------------------- // Write timestamp into string // - definition of date/time format in XMLTV DTD 0.5: // "All dates and times in this DTD follow the same format, loosely based // on ISO 8601. They can be 'YYYYMMDDhhmmss' or some initial // substring, for example if you only know the year and month you can // have 'YYYYMM'. You can also append a timezone to the end; if no // explicit timezone is given, UTC is assumed. Examples: // '200007281733 BST', '200209', '19880523083000 +0300'. (BST == +0100.)" // - unfortunately the authors of some XMLTV parsers have overlooked this // paragraph, so we need as a work-around the possibility to export times // in the local time zone // static void EpgDumpXml_PrintTimestamp( char * pBuf, uint maxLen, time_t then, int xmlDtdVersion ) { size_t len; sint lto; char ltoSign; if (xmlDtdVersion == EPGTCL_XMLTV_DTD_5_LTZ) { // print times in localtime lto = EpgLtoGet(then) / 60; if (lto < 0) { lto = 0 - lto; ltoSign = '-'; } else ltoSign = '+'; len = strftime(pBuf, maxLen, "%Y%m%d%H%M%S", localtime(&then)); if (len + 1 + 6 <= maxLen) { sprintf(pBuf + len, " %c%02d%02d", ltoSign, lto/60, lto%60); } } else if (xmlDtdVersion == EPGTCL_XMLTV_DTD_5_GMT) { strftime(pBuf, maxLen, "%Y%m%d%H%M%S +0000", gmtime(&then)); } else if (xmlDtdVersion == EPGTCL_XMLTV_DTD_6) { strftime(pBuf, maxLen, "%Y-%m-%dT%H:%M:%SZ", gmtime(&then)); } else { debug1("EpgDumpXml-PrintTimestamp: invalid XMLTV DTD version %d", xmlDtdVersion); if (maxLen >= 1) *pBuf = 0; } } // ---------------------------------------------------------------------------- // Write XML file header // static void EpgDumpXml_WriteHeader( EPGDB_CONTEXT * pDbContext, const AI_BLOCK * pAiBlock, const OI_BLOCK * pOiBlock, FILE * fp, int xmlDtdVersion ) { uchar start_str[50]; time_t lastAiUpdate; // content provider string comm[0] = 0; if (EpgDbContextIsMerged(pDbContext) == FALSE) { EpgDumpHtml_RemoveQuotes(AI_GET_NETWOP_NAME(pAiBlock, pAiBlock->thisNetwop), comm, TCL_COMM_BUF_SIZE - 2); strcat(comm, "/"); } EpgDumpHtml_RemoveQuotes(AI_GET_SERVICENAME(pAiBlock), comm + strlen(comm), TCL_COMM_BUF_SIZE - strlen(comm)); // date when data was captured lastAiUpdate = EpgDbGetAiUpdateTime(pDbContext); EpgDumpXml_PrintTimestamp(start_str, sizeof(start_str), lastAiUpdate, xmlDtdVersion); if ( IS_XML_DTD5(xmlDtdVersion) ) { fprintf(fp, "\n" "\n" "\n", start_str); } else // XML DTD 6 { fprintf(fp, "\n" "\n" "\n" "\n" "\t\n" "\t\t

" "Copyright by nexTView EPG content providers: ", start_str); EpgDumpHtml_WriteString(fp, comm, -1); fprintf(fp, "

\n" "\t\n" "\t\n" "\t\t"); EpgDumpHtml_WriteString(fp, AI_GET_SERVICENAME(pAiBlock), -1); fprintf(fp, "\n"); if ((pOiBlock != NULL) && (OI_HAS_HEADER(pOiBlock) || OI_HAS_MESSAGE(pOiBlock))) { fprintf(fp, "\t\t\n"); if (OI_HAS_HEADER(pOiBlock)) { fprintf(fp, "\t\t\t

"); EpgDumpHtml_WriteString(fp, OI_GET_HEADER(pOiBlock), -1); fprintf(fp, "

\n"); } if (OI_HAS_MESSAGE(pOiBlock)) { fprintf(fp, "\t\t\t

"); EpgDumpHtml_WriteString(fp, OI_GET_MESSAGE(pOiBlock), -1); fprintf(fp, "

\n"); } fprintf(fp, "\t\t
\n"); } fprintf(fp, "\t
\n" "\t\n" "\t\t\n" "\t\t\tnxtvepg/" EPG_VERSION_STR "\n" "\t\t\n" "\t\n" "\n"); } } // ---------------------------------------------------------------------------- // Write XML channel table // static void EpgDumpXml_WriteChannel( EPGDB_CONTEXT * pDbContext, const AI_BLOCK * pAiBlock, uint netwopIdx, FILE * fp, int xmlDtdVersion ) { const AI_NETWOP * pNetwop; const uchar * pCfNetname; const char * native; Tcl_DString ds; uchar cni_str[10]; if (netwopIdx < pAiBlock->netwopCount) { pNetwop = AI_GET_NETWOP_N(pAiBlock, netwopIdx); sprintf(cni_str, "0x%04X", pNetwop->cni); pCfNetname = Tcl_GetVar2(interp, "cfnetnames", cni_str, TCL_GLOBAL_ONLY); if (pCfNetname != NULL) { // convert the String from Tcl internal format to Latin-1 native = Tcl_UtfToExternalDString(NULL, pCfNetname, -1, &ds); } else native = AI_GET_STR_BY_OFF(pAiBlock, pNetwop->off_name); fprintf(fp, "\n", pNetwop->cni); if ( IS_XML_DTD5(xmlDtdVersion) == FALSE ) { fprintf(fp, "\t\n", netwopIdx); } fprintf(fp, "\t"); EpgDumpHtml_WriteString(fp, native, -1); fprintf(fp, "\n" "\n"); if (pCfNetname != NULL) Tcl_DStringFree(&ds); } else debug2("EpgDumpXml-WriteChannel: invalid netwop index %d (>= %d)", netwopIdx, pAiBlock->netwopCount); } // ---------------------------------------------------------------------------- // Write a single programme description // static void EpgDumpXml_WriteProgramme( EPGDB_CONTEXT * pDbContext, const AI_BLOCK * pAiBlock, const PI_BLOCK * pPiBlock, FILE * fp, int xmlDtdVersion ) { DUMP_XML_CB_INFO cbInfo; const char * pThemeStr; struct tm vpsTime; uint idx; uchar start_str[50]; uchar stop_str[50]; uchar tmp_str[50]; // start & stop times, channel ID EpgDumpXml_PrintTimestamp(start_str, sizeof(start_str), pPiBlock->start_time, xmlDtdVersion); EpgDumpXml_PrintTimestamp(stop_str, sizeof(stop_str), pPiBlock->stop_time, xmlDtdVersion); if ( IS_XML_DTD5(xmlDtdVersion) ) { if (EpgDbGetVpsTimestamp(&vpsTime, pPiBlock->pil, pPiBlock->start_time)) { int lto = EpgLtoGet(pPiBlock->start_time); strftime(tmp_str, sizeof(tmp_str) - 7, "pdc-start=\"%Y%m%d%H%M%S", &vpsTime); sprintf(tmp_str + strlen(tmp_str), " %+03d%02d\"", lto / (60*60), abs(lto / 60) % 60); } else tmp_str[0] = 0; fprintf(fp, "\n", start_str, stop_str, tmp_str, AI_GET_NETWOP_N(pAiBlock, pPiBlock->netwop_no)->cni); } else // XML DTD 6 { fprintf(fp, "\n", start_str, stop_str, AI_GET_NETWOP_N(pAiBlock, pPiBlock->netwop_no)->cni, ((pPiBlock->feature_flags & PI_FEATURE_LIVE) ? " liveness=\"live\"" : "")); if (EpgDbGetVpsTimestamp(&vpsTime, pPiBlock->pil, pPiBlock->start_time)) { // VPS/PDC timestamp is in localtime (hence no "Z" suffix) strftime(tmp_str, sizeof(tmp_str), "%Y-%m-%dT%H:%M:%S", &vpsTime); fprintf(fp, " \n", tmp_str); } fprintf(fp, " \n", AI_GET_NETWOP_N(pAiBlock, pPiBlock->netwop_no)->cni, pPiBlock->block_no, (int)pPiBlock->start_time, ((pPiBlock->feature_flags & PI_FEATURE_REPEAT) ? " newness=\"repeat\"" : "")); } // programme title and description (quoting "<", ">" and "&" characters) fprintf(fp, "\t"); EpgDumpHtml_WriteString(fp, PI_GET_TITLE(pPiBlock), -1); fprintf(fp, "\n"); if ( PI_HAS_SHORT_INFO(pPiBlock) || PI_HAS_LONG_INFO(pPiBlock) ) { cbInfo.fp = fp; cbInfo.xmlDtdVersion = xmlDtdVersion; if ( IS_XML_DTD5(xmlDtdVersion) ) { fprintf(fp, "\t"); PiDescription_AppendShortAndLongInfoText(pPiBlock, EpgDumpXml_AppendInfoTextCb, &cbInfo, EpgDbContextIsMerged(pDbContext)); fprintf(fp, "\n"); } else { fprintf(fp, "\t

"); PiDescription_AppendShortAndLongInfoText(pPiBlock, EpgDumpXml_AppendInfoTextCb, &cbInfo, EpgDbContextIsMerged(pDbContext)); fprintf(fp, "

\n"); } } // theme categories for (idx=0; idx < pPiBlock->no_themes; idx++) { pThemeStr = PdcThemeGet(pPiBlock->themes[idx]); if (pThemeStr != NULL) { if ( IS_XML_DTD5(xmlDtdVersion) ) fprintf(fp, "\t"); else fprintf(fp, "\t", pPiBlock->themes[idx]); EpgDumpHtml_WriteString(fp, pThemeStr, -1); fprintf(fp, "\n"); } } // attributes if ( IS_XML_DTD5(xmlDtdVersion) ) { if ((pPiBlock->feature_flags & (PI_FEATURE_PAL_PLUS | PI_FEATURE_FMT_WIDE)) != 0) { fprintf(fp, "\t\n"); } if ((pPiBlock->feature_flags & PI_FEATURE_SOUND_MASK) == PI_FEATURE_SOUND_STEREO) { fprintf(fp, "\t\n"); } else if ((pPiBlock->feature_flags & PI_FEATURE_SOUND_MASK) == PI_FEATURE_SOUND_SURROUND) { fprintf(fp, "\t\n"); } // else: 2-channel not supported by DTD 0.5 semantics if (pPiBlock->feature_flags & PI_FEATURE_REPEAT) { fprintf(fp, "\t\n"); } if (pPiBlock->feature_flags & PI_FEATURE_SUBTITLES) { fprintf(fp, "\t\n"); } if (pPiBlock->parental_rating == 1) fprintf(fp, "\t\n\t\tgeneral\n\t\n"); else if (pPiBlock->parental_rating > 0) fprintf(fp, "\t\n\t\t%d\n\t\n", pPiBlock->parental_rating * 2); if (pPiBlock->editorial_rating > 0) fprintf(fp, "\t\n\t\t%d/7\n\t\n", pPiBlock->editorial_rating); fprintf(fp, "
\n"); } else { // sorting criteria for (idx=0; idx < pPiBlock->no_sortcrit; idx++) { fprintf(fp, "\t\n", pPiBlock->sortcrits[idx]); } if ((pPiBlock->feature_flags & (PI_FEATURE_PAL_PLUS | PI_FEATURE_FMT_WIDE)) != 0) { fprintf(fp, "\t\n"); } if ((pPiBlock->feature_flags & PI_FEATURE_SOUND_MASK) == PI_FEATURE_SOUND_STEREO) { fprintf(fp, "\t\n"); } else if ((pPiBlock->feature_flags & PI_FEATURE_SOUND_MASK) == PI_FEATURE_SOUND_SURROUND) { fprintf(fp, "\t\n"); } else if ((pPiBlock->feature_flags & PI_FEATURE_SOUND_MASK) == PI_FEATURE_SOUND_2CHAN) { fprintf(fp, "\t\n"); fprintf(fp, "\t\n"); } if (pPiBlock->feature_flags & PI_FEATURE_SUBTITLES) { fprintf(fp, "\t\n"); } if (pPiBlock->parental_rating == 1) fprintf(fp, "\t\n\t\tgeneral\n\t\n"); else if (pPiBlock->parental_rating > 0) fprintf(fp, "\t\n\t\t%d\n\t\n", pPiBlock->parental_rating * 2); if (pPiBlock->editorial_rating > 0) fprintf(fp, "\t\n", pPiBlock->editorial_rating); fprintf(fp, "
\n\n"); } } // ---------------------------------------------------------------------------- // Dump programme titles and/or descriptions into file in XMLTV format // void EpgDumpXml_Standalone( EPGDB_CONTEXT * pDbContext, FILE * fp, EPGTAB_DUMP_MODE dumpMode ) { const AI_BLOCK * pAiBlock; const OI_BLOCK * pOiBlock; const PI_BLOCK * pPiBlock; uint netwopIdx; int xmlDtdVersion; switch (dumpMode) { case EPGTAB_DUMP_XMLTV_ANY: xmlDtdVersion = EpgDumpXml_QueryXmlDtdVersion(interp); break; case EPGTAB_DUMP_XMLTV_DTD_5_GMT: xmlDtdVersion = EPGTCL_XMLTV_DTD_5_GMT; break; case EPGTAB_DUMP_XMLTV_DTD_5_LTZ: xmlDtdVersion = EPGTCL_XMLTV_DTD_5_LTZ; break; case EPGTAB_DUMP_XMLTV_DTD_6: xmlDtdVersion = EPGTCL_XMLTV_DTD_6; break; default: fatal1("EpgDumpXml-Standalone: invalid mode %d\n", dumpMode); xmlDtdVersion = EPGTCL_XMLTV_DTD_5_GMT; break; } if (fp != NULL) { EpgDbLockDatabase(pDbContext, TRUE); pAiBlock = EpgDbGetAi(pDbContext); if (pAiBlock != NULL) { // get "OSD information" with service name and message pOiBlock = EpgDbGetOi(pDbContext, 0); // header with source info EpgDumpXml_WriteHeader(pDbContext, pAiBlock, pOiBlock, fp, xmlDtdVersion); // channel table for (netwopIdx = 0; netwopIdx < pAiBlock->netwopCount; netwopIdx++) { EpgDumpXml_WriteChannel(pDbContext, pAiBlock, netwopIdx, fp, xmlDtdVersion); } // loop across all PI and dump their info pPiBlock = EpgDbSearchFirstPi(pDbContext, NULL); while (pPiBlock != NULL) { EpgDumpXml_WriteProgramme(pDbContext, pAiBlock, pPiBlock, fp, xmlDtdVersion); pPiBlock = EpgDbSearchNextPi(pDbContext, NULL, pPiBlock); } // footer fprintf(fp, "\n"); } EpgDbLockDatabase(pDbContext, FALSE); } else fatal0("EpgDumpXml-Standalone: illegal NULL ptr param"); } // ---------------------------------------------------------------------------- // Dump programme titles and/or descriptions into file in XMLTV format // static int EpgDumpXml_DumpDatabase( ClientData ttp, Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[] ) { const char * const pUsage = "Usage: C_DumpXml "; const char * pFileName; FILE * fpDst; Tcl_DString ds; int result; if ( (objc != 1+1) || (pFileName = Tcl_GetString(objv[1])) == NULL ) { // parameter count is invalid Tcl_SetResult(interp, (char *)pUsage, TCL_STATIC); result = TCL_ERROR; } else { pFileName = Tcl_UtfToExternalDString(NULL, pFileName, -1, &ds); // Open source and create destination XML file fpDst = fopen(pFileName, "w"); if (fpDst != NULL) { EpgDumpXml_Standalone(pUiDbContext, fpDst, EPGTAB_DUMP_XMLTV_ANY); fclose(fpDst); } else { // access, create or truncate failed -> notify the user sprintf(comm, "tk_messageBox -type ok -icon error -parent .dumpxml -message \"Failed to open file '%s' for writing: %s\"", pFileName, strerror(errno)); eval_check(interp, comm); Tcl_ResetResult(interp); } Tcl_DStringFree(&ds); result = TCL_OK; } return result; } // ---------------------------------------------------------------------------- // Free resources allocated by this module (cleanup during program exit) // void EpgDumpXml_Destroy( void ) { } // ---------------------------------------------------------------------------- // Create the Tcl/Tk commands provided by this module // - this should be called only once during start-up // void EpgDumpXml_Init( void ) { Tcl_CreateObjCommand(interp, "C_DumpXml", EpgDumpXml_DumpDatabase, (ClientData) NULL, NULL); }