A new user interface for you! Read more...

File git20140823.patch of Package vdr-plugin-epgfixer

diff --git a/HISTORY b/HISTORY
index e04b138..1a047bc 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,6 +1,22 @@
 VDR Plugin 'epgfixer' Revision History
 --------------------------------------
 
+2014-xx-xx: Version x.x.x
+
+- Fix crash when cloning to a non-existent channel, (thanks to Guy Martin).
+- Fix using channelIDs in EPG cloning (thanks to Guy Martin).
+- Support regular expressions which are used only if the content of another EPG
+  field matches to an additional regular expression.
+- Fix possible null pointer error.
+- Fix header includes.
+- Makefile fixes. Install example config files automatically.
+- Fix crash in config editing menus if using color buttons when config is empty.
+- Fix SVDRP command REL (thanks to Ville Skyttä).
+- Fix LoadConfigFile() return value on success (thanks to Ville Skyttä).
+- Support for new Makefile of VDR >= 1.7.34.
+- Report an error if PCRE library is not available.
+- Fix replacing at the end of string when using s///. Fix variable name clash.
+
 2012-11-28: Version 0.3.1
 
 - Fix memory leak.
diff --git a/Makefile b/Makefile
index d2e4ecf..9b1b134 100644
--- a/Makefile
+++ b/Makefile
@@ -15,44 +15,51 @@ PLUGIN = epgfixer
 VERSION = $(shell grep 'static const char VERSION\[\] *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g')
 GITTAG  = $(shell git describe --always 2>/dev/null)
 
-### The C++ compiler and options:
-
-CXX      ?= g++
-CXXFLAGS ?= -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses
-
 ### The directory environment:
 
-VDRDIR ?= ../../..
-LIBDIR ?= ../../lib
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
+CFGDIR = $(call PKGCFG,configdir)
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
 TMPDIR ?= /tmp
 
+### The compiler options:
+
+export CFLAGS   = $(call PKGCFG,cflags)
+export CXXFLAGS = $(call PKGCFG,cxxflags)
+
 ### Regexp
 ifeq (exists, $(shell pkg-config libpcre && echo exists))
 	REGEXLIB = pcre
+else
+$(error PCRE library required)
 endif
 
-### Make sure that necessary options are included:
+### The version number of VDR's plugin API:
 
-include $(VDRDIR)/Make.global
+APIVERSION = $(call PKGCFG,apiversion)
 
 ### Allow user defined options to overwrite defaults:
 
--include $(VDRDIR)/Make.config
-
-### The version number of VDR's plugin API (taken from VDR's "config.h"):
-
-APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+-include $(PLGCFG)
 
 ### The name of the distribution archive:
 
 ARCHIVE = $(PLUGIN)-$(VERSION)
 PACKAGE = vdr-$(ARCHIVE)
 
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
 ### Includes and Defines (add further entries here):
 
-INCLUDES += -I$(VDRDIR)/include
+INCLUDES +=
 
-DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
 
 ifneq ($(strip $(GITTAG)),)
 DEFINES += -DGITVERSION='"-GIT-$(GITTAG)"'
@@ -64,34 +71,34 @@ OBJS = $(PLUGIN).o blacklist.o charset.o config.o epgclone.o epghandler.o regexp
 
 ifeq ($(REGEXLIB), pcre)
 LIBS += $(shell pcre-config --libs-posix)
-INCLUDE += $(shell pcre-config --cflags)
+INCLUDES += $(shell pcre-config --cflags)
 DEFINES += -DHAVE_PCREPOSIX
 endif
 
 ### The main target:
 
-all: libvdr-$(PLUGIN).so i18n
+all: $(SOFILE) i18n
 
 ### Implicit rules:
 
 %.o: %.c
-	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
 
 ### Dependencies:
 
 MAKEDEP = $(CXX) -MM -MG
 DEPFILE = .dependencies
 $(DEPFILE): Makefile
-	@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
 
 -include $(DEPFILE)
 
 ### Internationalization (I18N):
 
 PODIR     = po
-LOCALEDIR = $(VDRDIR)/locale
 I18Npo    = $(wildcard $(PODIR)/*.po)
-I18Nmsgs  = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
 I18Npot   = $(PODIR)/$(PLUGIN).pot
 
 %.mo: %.po
@@ -101,21 +108,30 @@ $(I18Npot): $(wildcard *.c)
 	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name='vdr-$(PLUGIN)' --package-version='$(VERSION)' --msgid-bugs-address='<see README>' -o $@ `ls $^`
 
 %.po: $(I18Npot)
-	msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
 	@touch $@
 
-$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
-	@mkdir -p $(dir $@)
-	cp $< $@
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
 
 .PHONY: i18n
-i18n: $(I18Nmsgs) $(I18Npot)
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
 
 ### Targets:
 
-libvdr-$(PLUGIN).so: $(OBJS)
+$(SOFILE): $(OBJS)
 	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ $(LIBS)
-	@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+
+install-lib: $(SOFILE)
+	install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install-conf:
+	@mkdir -p $(DESTDIR)$(CFGDIR)/plugins/$(PLUGIN)
+	@cp -pn $(PLUGIN)/* $(DESTDIR)$(CFGDIR)/plugins/$(PLUGIN)/
+
+install: install-lib install-i18n install-conf
 
 dist: $(I18Npo) clean
 	@-rm -rf $(TMPDIR)/$(ARCHIVE)
diff --git a/Makefile.pre.1.7.34 b/Makefile.pre.1.7.34
new file mode 100644
index 0000000..cf18f31
--- /dev/null
+++ b/Makefile.pre.1.7.34
@@ -0,0 +1,134 @@
+#
+# Makefile for epgfixer, a Video Disk Recorder plugin
+#
+
+# epgfixer
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+# IMPORTANT: the presence of this macro is important for the Make.config
+# file. So it must be defined, even if it is not used here!
+#
+PLUGIN = epgfixer
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char VERSION\[\] *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g')
+GITTAG  = $(shell git describe --always 2>/dev/null)
+
+### The C++ compiler and options:
+
+CXX      ?= g++
+CXXFLAGS ?= -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses
+
+### The directory environment:
+
+VDRDIR ?= ../../..
+LIBDIR ?= ../../lib
+TMPDIR ?= /tmp
+
+### Regexp
+ifeq (exists, $(shell pkg-config libpcre && echo exists))
+	REGEXLIB = pcre
+else
+$(error PCRE library required)
+endif
+
+### Make sure that necessary options are included:
+
+include $(VDRDIR)/Make.global
+
+### Allow user defined options to overwrite defaults:
+
+-include $(VDRDIR)/Make.config
+
+### The version number of VDR's plugin API (taken from VDR's "config.h"):
+
+APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES += -I$(VDRDIR)/include
+
+DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+ifneq ($(strip $(GITTAG)),)
+DEFINES += -DGITVERSION='"-GIT-$(GITTAG)"'
+endif
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o blacklist.o charset.o config.o epgclone.o epghandler.o regexp.o setup_menu.o tools.o
+
+ifeq ($(REGEXLIB), pcre)
+LIBS += $(shell pcre-config --libs-posix)
+INCLUDE += $(shell pcre-config --cflags)
+DEFINES += -DHAVE_PCREPOSIX
+endif
+
+### The main target:
+
+all: libvdr-$(PLUGIN).so i18n
+
+### Implicit rules:
+
+%.o: %.c
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+LOCALEDIR = $(VDRDIR)/locale
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmsgs  = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name='vdr-$(PLUGIN)' --package-version='$(VERSION)' --msgid-bugs-address='<see README>' -o $@ `ls $^`
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	@mkdir -p $(dir $@)
+	cp $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmsgs) $(I18Npot)
+
+### Targets:
+
+libvdr-$(PLUGIN).so: $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ $(LIBS)
+	@cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION)
+
+dist: $(I18Npo) clean
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@mkdir $(TMPDIR)/$(ARCHIVE)
+	@cp -a * $(TMPDIR)/$(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot
+
+cppcheck:
+	@cppcheck --enable=information,style,unusedFunction -v -f $(OBJS:%.o=%.c)
diff --git a/README b/README
index 783707b..8d83db8 100644
--- a/README
+++ b/README
@@ -39,9 +39,8 @@ cd /put/your/path/here/VDR/PLUGINS/src
 tar -xzf /put/your/path/here/vdr-epgfixer-X.Y.Z.tgz
 ln -s epgfixer-X.Y.Z epgfixer
 cd /put/your/path/here/VDR
-cp -R PLUGINS/src/epgfixer/epgfixer /path/to/vdrconf/plugins/
 make
-make plugins
+make install
 ./vdr -P epgfixer
 
 Configuration:
@@ -61,9 +60,15 @@ General syntax of configuration files:
   numbers but not both.
 - Channel intervals (e.g. 1-10) can be used for channel numbers.
 
-Syntax of regexp.conf line is "Channel_list:Parsed_epg_field=Regexp" with:
-- Parsed_epg_field is the EPG field for which the regular expression is applied
+Syntax of regexp.conf line is 
+"Channel_list:Parsed_field?Conditional_field~Conditional_Regexp=Regexp" with:
+- Parsed_field is the EPG field for which the regular expression is applied
   with available field names title, shorttext and description.
+- Conditional_field and Conditional_Regexp are optional parameters to control 
+  when Regexp is applied.
+- Conditional_field is identical to Parsed_field.
+- Conditional_Regexp has to be a Perl-style 'm/PATTERN/' as described below
+  except it is only used for matching (i.e. backreferences are ignored).
 - Regular expressions can be used in different ways: Perl-style 
   's/PATTERN/REPLACEMENT/' and 'm/PATTERN/' operators or simply using 'PATTERN'
   of which the latter two use named backreferences.
diff --git a/blacklist.h b/blacklist.h
index 1d60185..da82219 100644
--- a/blacklist.h
+++ b/blacklist.h
@@ -8,8 +8,10 @@
 #ifndef __EPGFIXER_BLACKLIST_H_
 #define __EPGFIXER_BLACKLIST_H_
 
+#include <vdr/channels.h>
+#include <vdr/tools.h>
+
 #include "tools.h"
-#include <vdr/epg.h>
 
 class cBlacklist : public cListItem
 {
diff --git a/charset.c b/charset.c
index 90c4a04..fec5864 100644
--- a/charset.c
+++ b/charset.c
@@ -5,6 +5,7 @@
  *
  */
 
+#include <string.h>
 #include "charset.h"
 
 /* Global instance */
diff --git a/charset.h b/charset.h
index 2207250..395c838 100644
--- a/charset.h
+++ b/charset.h
@@ -8,8 +8,9 @@
 #ifndef __EPGFIXER_CHARSET_H_
 #define __EPGFIXER_CHARSET_H_
 
-#include "tools.h"
 #include <vdr/epg.h>
+#include <vdr/tools.h>
+#include "tools.h"
 
 class cCharSet : public cListItem
 {
diff --git a/config.c b/config.c
index aa40420..5c6e586 100644
--- a/config.c
+++ b/config.c
@@ -6,7 +6,6 @@
  */
 
 #include "config.h"
-#include <string.h>
 
 /* Global instance */
 cEpgfixerSetup EpgfixerSetup;
@@ -24,44 +23,3 @@ cEpgfixerSetup::cEpgfixerSetup()
   components = 0;
   striphtml = 0;
 }
-
-cString cEpgfixerSetup::m_ProcessedArgs;
-
-bool cEpgfixerSetup::ProcessArg(const char *Name, const char *Value)
-{
-  if (SetupParse(Name, Value)) {
-     m_ProcessedArgs = cString::sprintf("%s%s ", *m_ProcessedArgs ? *m_ProcessedArgs : " ", Name);
-     return true;
-     }
-  return false;
-}
-
-bool cEpgfixerSetup::ProcessArgs(int argc, char *argv[])
-{
-  return true;
-}
-
-bool cEpgfixerSetup::SetupParse(const char *Name, const char *Value)
-{
-  const char *pt;
-  if (*m_ProcessedArgs && NULL != (pt = strstr(m_ProcessedArgs + 1, Name)) &&
-      *(pt - 1) == ' ' && *(pt + strlen(Name)) == ' ') {
-     dsyslog("Skipping configuration entry %s=%s (overridden in command line)", Name, Value);
-     return true;
-     }
-
-  if (!strcasecmp(Name, "RemoveQuotesFromShortText"))                quotedshorttext = atoi(Value);
-  else if (!strcasecmp(Name, "MoveDescriptionFromShortText"))        blankbeforedescription = atoi(Value);
-  else if (!strcasecmp(Name, "RemoveRepeatedTitleFromShortText"))    repeatedtitle = atoi(Value);
-  else if (!strcasecmp(Name, "RemoveDoubleQuotesFromShortText"))     doublequotedshorttext = atoi(Value);
-  else if (!strcasecmp(Name, "RemoveUselessFormatting"))             removeformatting = atoi(Value);
-  else if (!strcasecmp(Name, "MoveLongShortTextToDescription"))      longshorttext = atoi(Value);
-  else if (!strcasecmp(Name, "PreventEqualShortTextAndDescription")) equalshorttextanddescription = atoi(Value);
-  else if (!strcasecmp(Name, "ReplaceBackticksWithSingleQuotes"))    nobackticks = atoi(Value);
-  else if (!strcasecmp(Name, "FixStreamComponentDescriptions"))      components = atoi(Value);
-  else if (!strcasecmp(Name, "StripHTMLEntities"))                   striphtml = atoi(Value);
-  else
-     return false;
-
-  return true;
-}
diff --git a/config.h b/config.h
index ad0a778..670db11 100644
--- a/config.h
+++ b/config.h
@@ -8,11 +8,8 @@
 #ifndef __EPGFIXER_CONFIG_H_
 #define __EPGFIXER_CONFIG_H_
 
-#include "regexp.h"
-
-class cEpgfixerSetup
+struct cEpgfixerSetup
 {
-public:
   int quotedshorttext;
   int blankbeforedescription;
   int repeatedtitle;
@@ -24,12 +21,6 @@ public:
   int components;
   int striphtml;
   cEpgfixerSetup();
-  bool SetupParse(const char *Name, const char *Value);
-  bool ProcessArgs(int argc, char *argv[]);
-
-protected:
-  bool ProcessArg(const char *Name, const char *Value);
-  static cString m_ProcessedArgs;
 };
 
 // Global instance
diff --git a/epgclone.c b/epgclone.c
index 427665a..46397d1 100644
--- a/epgclone.c
+++ b/epgclone.c
@@ -5,6 +5,8 @@
  *
  */
 
+#include <stdlib.h>
+#include <string.h>
 #include "epgclone.h"
 
 /* Global instance */
@@ -46,11 +48,22 @@ void cEpgClone::CloneEvent(cEvent *Source, cEvent *Dest) {
   if (Source->Seen())
      Dest->SetSeen();
   tChannelID channelID;
-  if (dest_num)
-     channelID = Channels.GetByNumber(dest_num)->GetChannelID();
+  if (dest_num) {
+     cChannel *dest_chan = Channels.GetByNumber(dest_num);
+     if (dest_chan)
+        channelID = Channels.GetByNumber(dest_num)->GetChannelID();
+     else
+        channelID = tChannelID::InvalidID;
+     }
+  else
+     channelID = tChannelID::FromString(dest_str);
+  if (channelID == tChannelID::InvalidID) {
+     enabled = false;
+     delete Dest;
+     error("Destination channel %s not found for cloning, disabling cloning!", (dest_num ? *itoa(dest_num) : dest_str));
+     }
   else
-     channelID.FromString(dest_str);
-  AddEvent(Dest, channelID);
+     AddEvent(Dest, channelID);
 }
 
 bool cEpgClone::Apply(cEvent *Event)
diff --git a/epgclone.h b/epgclone.h
index 894547c..13e97cd 100644
--- a/epgclone.h
+++ b/epgclone.h
@@ -8,8 +8,9 @@
 #ifndef __EPGFIXER_EPGCLONE_H_
 #define __EPGFIXER_EPGCLONE_H_
 
-#include "tools.h"
 #include <vdr/epg.h>
+#include <vdr/tools.h>
+#include "tools.h"
 
 class cEpgClone : public cListItem
 {
diff --git a/epgfixer.c b/epgfixer.c
index 3abe6f2..ecf3a40 100644
--- a/epgfixer.c
+++ b/epgfixer.c
@@ -5,14 +5,16 @@
  *
  */
 
+#include <getopt.h>
+#include <stdlib.h>
 #include <vdr/plugin.h>
 #include <vdr/i18n.h>
 #include "blacklist.h"
 #include "charset.h"
 #include "epgclone.h"
+#include "epghandler.h"
 #include "regexp.h"
 #include "setup_menu.h"
-#include "epghandler.h"
 
 #if defined(APIVERSNUM) && APIVERSNUM < 10726
 #error "VDR-1.7.26 API version or greater is required!"
@@ -143,7 +145,28 @@ cMenuSetupPage *cPluginEpgfixer::SetupMenu(void)
 bool cPluginEpgfixer::SetupParse(const char *Name, const char *Value)
 {
   // Parse your own setup parameters and store their values.
-  return EpgfixerSetup.SetupParse(Name, Value);
+  cString m_ProcessedArgs;
+  const char *pt;
+  if (*m_ProcessedArgs && NULL != (pt = strstr(m_ProcessedArgs + 1, Name)) &&
+      *(pt - 1) == ' ' && *(pt + strlen(Name)) == ' ') {
+     dsyslog("Skipping configuration entry %s=%s (overridden in command line)", Name, Value);
+     return true;
+     }
+
+  if (!strcasecmp(Name, "RemoveQuotesFromShortText"))                EpgfixerSetup.quotedshorttext = atoi(Value);
+  else if (!strcasecmp(Name, "MoveDescriptionFromShortText"))        EpgfixerSetup.blankbeforedescription = atoi(Value);
+  else if (!strcasecmp(Name, "RemoveRepeatedTitleFromShortText"))    EpgfixerSetup.repeatedtitle = atoi(Value);
+  else if (!strcasecmp(Name, "RemoveDoubleQuotesFromShortText"))     EpgfixerSetup.doublequotedshorttext = atoi(Value);
+  else if (!strcasecmp(Name, "RemoveUselessFormatting"))             EpgfixerSetup.removeformatting = atoi(Value);
+  else if (!strcasecmp(Name, "MoveLongShortTextToDescription"))      EpgfixerSetup.longshorttext = atoi(Value);
+  else if (!strcasecmp(Name, "PreventEqualShortTextAndDescription")) EpgfixerSetup.equalshorttextanddescription = atoi(Value);
+  else if (!strcasecmp(Name, "ReplaceBackticksWithSingleQuotes"))    EpgfixerSetup.nobackticks = atoi(Value);
+  else if (!strcasecmp(Name, "FixStreamComponentDescriptions"))      EpgfixerSetup.components = atoi(Value);
+  else if (!strcasecmp(Name, "StripHTMLEntities"))                   EpgfixerSetup.striphtml = atoi(Value);
+  else
+     return false;
+
+  return true;
 }
 
 bool cPluginEpgfixer::Service(const char *Id, void *Data)
@@ -155,7 +178,7 @@ bool cPluginEpgfixer::Service(const char *Id, void *Data)
 const char **cPluginEpgfixer::SVDRPHelpPages(void)
 {
   static const char *HelpPages[] = {
-    "RLAL\n"
+    "REL\n"
     "    Reload all configs.",
     "RLRE\n"
     "    Reload regexp.conf.",
@@ -205,7 +228,7 @@ cString cPluginEpgfixer::SVDRPCommand(const char *Command, const char *Option, i
      }
   }
   else if (strcasecmp(Command, "REL") == 0) {
-     if (EpgfixerCharSets.ReloadConfigFile() &&
+     if (EpgfixerRegexps.ReloadConfigFile() &&
          EpgfixerCharSets.ReloadConfigFile() &&
          EpgfixerBlacklists.ReloadConfigFile() &&
          EpgfixerEpgClones.ReloadConfigFile()) {
diff --git a/epgfixer/regexp.conf b/epgfixer/regexp.conf
index 0e3df18..5886428 100644
--- a/epgfixer/regexp.conf
+++ b/epgfixer/regexp.conf
@@ -1,6 +1,3 @@
-# Example of disabled regexp:
-#!title=m/^(?:Movie: |Document: )(?<title>.*)$/
-
 # Remove "Movie: " or "Document: " from the beginning of title field for 
 # channels 1, 3, 5, 6 and 7:
 #1,3,5-7:title=m/^(?:Movie: |Document: )(?<title>.*)$/
@@ -21,3 +18,10 @@
 
 # Matches 'foo' case-insensitively
 #description=m/foo/i
+
+# Example of a conditional regexp
+# regexp "s/foo/bar/" is applied to description only if title matches "foo"
+#description?title~m/foo/=s/foo/bar/
+
+# Example of disabled regexp:
+#!title=m/^(?:Movie: |Document: )(?<title>.*)$/
diff --git a/epghandler.c b/epghandler.c
index da62dd5..de898fc 100644
--- a/epghandler.c
+++ b/epghandler.c
@@ -5,272 +5,10 @@
  *
  */
 
-#include "epghandler.h"
 #include "blacklist.h"
-#include "charset.h"
-#include "config.h"
 #include "epgclone.h"
-#include "regexp.h"
-#include <vdr/tools.h>
-#include <string.h>
-
-//
-// Original VDR bug fixes adapted from epg.c of VDR
-// by Klaus Schmidinger
-//
-
-static void StripControlCharacters(char *s)
-{
-  if (s) {
-     int len = strlen(s);
-     while (len > 0) {
-           int l = Utf8CharLen(s);
-           uchar *p = (uchar *)s;
-           if (l == 2 && *p == 0xC2) // UTF-8 sequence
-              p++;
-           if (*p == 0x86 || *p == 0x87) {
-              memmove(s, p + 1, len - l + 1); // we also copy the terminating 0!
-              len -= l;
-              l = 0;
-              }
-           s += l;
-           len -= l;
-           }
-     }
-}
-
-void cEpgfixerEpgHandler::FixOriginalEpgBugs(cEvent *event)
-{
-  // Copy event title, shorttext and description to temporary variables
-  // we don't want any "(null)" titles
-  char *title = event->Title() ? strdup(event->Title()) : strdup("No title");
-  char *shortText = event->ShortText() ? strdup(event->ShortText()) : NULL;
-  char *description = event->Description() ? strdup(event->Description()) : NULL;
-
-  // Some TV stations apparently have their own idea about how to fill in the
-  // EPG data. Let's fix their bugs as good as we can:
-
-  // Some channels put the ShortText in quotes and use either the ShortText
-  // or the Description field, depending on how long the string is:
-  //
-  // Title
-  // "ShortText". Description
-  //
-  if (EpgfixerSetup.quotedshorttext && (shortText == NULL) != (description == NULL)) {
-     char *p = shortText ? shortText : description;
-     if (*p == '"') {
-        const char *delim = "\".";
-        char *e = strstr(p + 1, delim);
-        if (e) {
-           *e = 0;
-           char *s = strdup(p + 1);
-           char *d = strdup(e + strlen(delim));
-           free(shortText);
-           free(description);
-           shortText = s;
-           description = d;
-           }
-        }
-     }
-
-  // Some channels put the Description into the ShortText (preceded
-  // by a blank) if there is no actual ShortText and the Description
-  // is short enough:
-  //
-  // Title
-  //  Description
-  //
-  if (EpgfixerSetup.blankbeforedescription && shortText && !description) {
-     if (*shortText == ' ') {
-        memmove(shortText, shortText + 1, strlen(shortText));
-        description = shortText;
-        shortText = NULL;
-        }
-     }
-
-  // Sometimes they repeat the Title in the ShortText:
-  //
-  // Title
-  // Title
-  //
-  if (EpgfixerSetup.repeatedtitle && shortText && strcmp(title, shortText) == 0) {
-     free(shortText);
-     shortText = NULL;
-     }
-
-  // Some channels put the ShortText between double quotes, which is nothing
-  // but annoying (some even put a '.' after the closing '"'):
-  //
-  // Title
-  // "ShortText"[.]
-  //
-  if (EpgfixerSetup.doublequotedshorttext && shortText && *shortText == '"') {
-     int l = strlen(shortText);
-     if (l > 2 && (shortText[l - 1] == '"' || (shortText[l - 1] == '.' && shortText[l - 2] == '"'))) {
-        memmove(shortText, shortText + 1, l);
-        char *p = strrchr(shortText, '"');
-        if (p)
-           *p = 0;
-        }
-     }
-
-  // Some channels apparently try to do some formatting in the texts,
-  // which is a bad idea because they have no way of knowing the width
-  // of the window that will actually display the text.
-  // Remove excess whitespace:
-  if (EpgfixerSetup.removeformatting) {
-     title = compactspace(title);
-     shortText = compactspace(shortText);
-     description = compactspace(description);
-     }
-
-#define MAX_USEFUL_EPISODE_LENGTH 40
-  // Some channels put a whole lot of information in the ShortText and leave
-  // the Description totally empty. So if the ShortText length exceeds
-  // MAX_USEFUL_EPISODE_LENGTH, let's put this into the Description
-  // instead:
-  if (EpgfixerSetup.longshorttext && !isempty(shortText) && isempty(description)) {
-     if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
-        free(description);
-        description = shortText;
-        shortText = NULL;
-        }
-     }
-
-  // Some channels put the same information into ShortText and Description.
-  // In that case we delete one of them:
-  if (EpgfixerSetup.equalshorttextanddescription && shortText && description && strcmp(shortText, description) == 0) {
-     if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
-        free(shortText);
-        shortText = NULL;
-        }
-     else {
-        free(description);
-        description = NULL;
-        }
-     }
-
-  // Some channels use the ` ("backtick") character, where a ' (single quote)
-  // would be normally used. Actually, "backticks" in normal text don't make
-  // much sense, so let's replace them:
-  if (EpgfixerSetup.nobackticks) {
-     strreplace(title, '`', '\'');
-     strreplace(shortText, '`', '\'');
-     strreplace(description, '`', '\'');
-     }
-
-  // The stream components have a "description" field which some channels
-  // apparently have no idea of how to set correctly:
-  const cComponents *components = event->Components();
-  if (EpgfixerSetup.components && components) {
-     for (int i = 0; i < components->NumComponents(); ++i) {
-         tComponent *p = components->Component(i);
-         switch (p->stream) {
-           case 0x01: { // video
-                if (p->description) {
-                   if (strcasecmp(p->description, "Video") == 0 ||
-                        strcasecmp(p->description, "Bildformat") == 0) {
-                      // Yes, we know it's video - that's what the 'stream' code
-                      // is for! But _which_ video is it?
-                      free(p->description);
-                      p->description = NULL;
-                      }
-                   }
-                if (!p->description) {
-                   switch (p->type) {
-                     case 0x01:
-                     case 0x05: p->description = strdup("4:3"); break;
-                     case 0x02:
-                     case 0x03:
-                     case 0x06:
-                     case 0x07: p->description = strdup("16:9"); break;
-                     case 0x04:
-                     case 0x08: p->description = strdup(">16:9"); break;
-                     case 0x09:
-                     case 0x0D: p->description = strdup("HD 4:3"); break;
-                     case 0x0A:
-                     case 0x0B:
-                     case 0x0E:
-                     case 0x0F: p->description = strdup("HD 16:9"); break;
-                     case 0x0C:
-                     case 0x10: p->description = strdup("HD >16:9"); break;
-                     default: ;
-                     }
-                   }
-                }
-                break;
-           case 0x02: { // audio
-                if (p->description) {
-                   if (strcasecmp(p->description, "Audio") == 0) {
-                      // Yes, we know it's audio - that's what the 'stream' code
-                      // is for! But _which_ audio is it?
-                      free(p->description);
-                      p->description = NULL;
-                      }
-                   }
-                if (!p->description) {
-                   switch (p->type) {
-                     case 0x05: p->description = strdup("Dolby Digital"); break;
-                     default: ; // all others will just display the language
-                     }
-                   }
-                }
-                break;
-           default: ;
-           }
-         }
-     }
-
-  // VDR can't usefully handle newline characters in the title, shortText or component description of EPG
-  // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel):
-  strreplace(title, '\n', ' ');
-  strreplace(shortText, '\n', ' ');
-  if (components) {
-     for (int i = 0; i < components->NumComponents(); ++i) {
-         tComponent *p = components->Component(i);
-         if (p->description)
-            strreplace(p->description, '\n', ' ');
-         }
-     }
-  // Same for control characters:
-  StripControlCharacters(title);
-  StripControlCharacters(shortText);
-  StripControlCharacters(description);
-  // Set modified data back to event
-  event->SetTitle(title);
-  event->SetShortText(shortText);
-  event->SetDescription(description);
-
-  free(title);
-  free(shortText);
-  free(description);
-}
-
-bool cEpgfixerEpgHandler::FixBugs(cEvent *Event)
-{
-  return EpgfixerRegexps.Apply(Event);
-}
-
-bool cEpgfixerEpgHandler::FixCharSets(cEvent *Event)
-{
-  return EpgfixerCharSets.Apply(Event);
-}
-
-void cEpgfixerEpgHandler::StripHTML(cEvent *Event)
-{
-  if (EpgfixerSetup.striphtml) {
-     char *tmpstring = NULL;
-     tmpstring = Event->Title() ? strdup(Event->Title()) : NULL;
-     Event->SetTitle(striphtml(tmpstring));
-     FREE(tmpstring);
-     tmpstring = Event->ShortText() ? strdup(Event->ShortText()) : NULL;
-     Event->SetShortText(striphtml(tmpstring));
-     FREE(tmpstring);
-     tmpstring = Event->Description() ? strdup(Event->Description()) : NULL;
-     Event->SetDescription(striphtml(tmpstring));
-     FREE(tmpstring);
-     }
-}
+#include "tools.h"
+#include "epghandler.h"
 
 bool cEpgfixerEpgHandler::FixEpgBugs(cEvent *Event)
 {
diff --git a/epghandler.h b/epghandler.h
index 2e03d25..de090af 100644
--- a/epghandler.h
+++ b/epghandler.h
@@ -8,16 +8,11 @@
 #ifndef __EPGFIXER_EPGHANDLER_H
 #define __EPGFIXER_EPGHANDLER_H
 
+#include <vdr/channels.h>
 #include <vdr/epg.h>
 
 class cEpgfixerEpgHandler : public cEpgHandler
 {
-private:
-  void FixOriginalEpgBugs(cEvent *event);
-  bool FixCharSets(cEvent *Event);
-  bool FixBugs(cEvent *Event);
-  void StripHTML(cEvent *Event);
-
 public:
   cEpgfixerEpgHandler(void) {};
   virtual bool HandleEvent(cEvent *Event);
diff --git a/regexp.c b/regexp.c
index 4631cbd..7f9ad87 100644
--- a/regexp.c
+++ b/regexp.c
@@ -5,6 +5,8 @@
  *
  */
 
+#include <stdlib.h>
+#include <string.h>
 #include "regexp.h"
 
 // for PCRE without JIT support
@@ -26,18 +28,24 @@ const char *strBackrefs[] = { "atitle", "ptitle", "title", "ashorttext", "pshort
 
 cRegexp::cRegexp()
 {
+  cmodifiers = 0;
   modifiers = 0;
+  cregexp = NULL;
   regexp = NULL;
   replace = NONE;
   replacement = NULL;
+  csource = REGEXP_UNDEFINED;
   source = REGEXP_UNDEFINED;
+  cre = NULL;
   re = NULL;
+  csd = NULL;
   sd = NULL;
 }
 
 cRegexp::~cRegexp(void)
 {
   Free();
+  free(cregexp);
   free(regexp);
   free(replacement);
   FreeCompiled();
@@ -55,8 +63,23 @@ void cRegexp::Compile()
      }
   else {
      sd = pcre_study(re, PCRE_STUDY_JIT_COMPILE, (const char **)&error);
-     if (error)
+     if (error) {
         error("PCRE study error: %s", error);
+        }
+     else {
+        if (cregexp) {
+           cre = pcre_compile(cregexp, cmodifiers, &error, &erroffset, NULL);
+           if (error) {
+              error("PCRE compile error: %s at offset %i", error, erroffset);
+              enabled = false;
+              }
+           else {
+              csd = pcre_study(cre, PCRE_STUDY_JIT_COMPILE, (const char **)&error);
+              if (error)
+                 error("PCRE study error: %s", error);
+              }
+           }
+        }
      }
 }
 
@@ -66,6 +89,10 @@ void cRegexp::FreeCompiled()
      pcre_free(re);
      re = NULL;
      }
+  if (cre) {
+     pcre_free(cre);
+     cre = NULL;
+     }
   if (sd) {
 #ifdef PCRE_CONFIG_JIT
      pcre_free_study(sd);
@@ -74,6 +101,51 @@ void cRegexp::FreeCompiled()
 #endif
      sd = NULL;
      }
+  if (csd) {
+#ifdef PCRE_CONFIG_JIT
+     pcre_free_study(csd);
+#else
+     pcre_free(csd);
+#endif
+     csd = NULL;
+     }
+}
+
+int cRegexp::ParseModifiers(char *modstring, int substitution)
+{
+  int i = 0;
+  int mods = 0;
+  // handle all modifiers
+  while (*(modstring + i) != 0) {
+       switch (*(modstring + i)) {
+         case 'g':
+           if (substitution)
+              replace = GLOBAL;
+           break;
+         case 'i':
+           mods |= PCRE_CASELESS;
+           break;
+         case 'm':
+           mods |= PCRE_MULTILINE;
+           break;
+         case 's':
+           mods |= PCRE_DOTALL;
+           break;
+         case 'u':
+           mods |= PCRE_UTF8;
+           break;
+         case 'x':
+           mods |= PCRE_EXTENDED;
+           break;
+         case 'X':
+           mods |= PCRE_EXTRA;
+           break;
+         default:
+           break;
+         }
+       i++;
+       }
+  return mods;
 }
 
 void cRegexp::ParseRegexp(char *restring)
@@ -85,37 +157,7 @@ void cRegexp::ParseRegexp(char *restring)
         char *l = strrchr(restring, '/');
         if (l) {
            *l = 0;
-           int i = 1;
-           // handle all modifiers
-           while (*(l + i) != 0) {
-                 switch (*(l + i)) {
-                   case 'g':
-                     if (restring[0] == 's')
-                        replace = GLOBAL;
-                     break;
-                   case 'i':
-                     modifiers = modifiers | PCRE_CASELESS;
-                     break;
-                   case 'm':
-                     modifiers = modifiers | PCRE_MULTILINE;
-                     break;
-                   case 's':
-                     modifiers = modifiers | PCRE_DOTALL;
-                     break;
-                   case 'u':
-                     modifiers = modifiers | PCRE_UTF8;
-                     break;
-                   case 'x':
-                     modifiers = modifiers | PCRE_EXTENDED;
-                     break;
-                   case 'X':
-                     modifiers = modifiers | PCRE_EXTRA;
-                     break;
-                   default:
-                     break;
-                   }
-                 i++;
-                 }
+           modifiers = ParseModifiers(l + 1, restring[0] == 's');
            }
         // parse regexp format 's///'
         if (restring[0] == 's') {
@@ -145,12 +187,15 @@ void cRegexp::ParseRegexp(char *restring)
 
 void cRegexp::SetFromString(char *s, bool Enabled)
 {
+  cmodifiers = 0;
   modifiers = 0;
+  FREE(cregexp);
   FREE(regexp);
   replace = NONE;
   FREE(replacement);
   Free();
   FreeCompiled();
+  csource = REGEXP_UNDEFINED;
   source = REGEXP_UNDEFINED;
   cListItem::SetFromString(s, Enabled);
   if (enabled) {
@@ -167,6 +212,28 @@ void cRegexp::SetFromString(char *s, bool Enabled)
            field = f + 1;
            numchannels = LoadChannelsFromString(chanfield);
            }
+        // parse field conditional
+        char *cond = strchr(field, '?');
+        if (cond) {
+           *cond = 0;
+           cond += 1;
+           char *m = strrchr(cond, '/');
+           if (m) {
+              *m = 0;
+              cmodifiers = ParseModifiers(m + 1);
+              }
+           char *cs = strchr(cond, '~');
+           if (cs) {
+              *cs = 0;
+              cregexp = strdup(cs + 3);
+              }
+           if (strcmp(cond, "title") == 0)
+              csource = REGEXP_TITLE;
+           if (strcmp(cond, "shorttext") == 0)
+              csource = REGEXP_SHORTTEXT;
+           if (strcmp(cond, "description") == 0)
+              csource = REGEXP_DESCRIPTION;
+           }
         if (strcmp(field, "title") == 0)
            source = REGEXP_TITLE;
         if (strcmp(field, "shorttext") == 0)
@@ -198,13 +265,31 @@ bool cRegexp::Apply(cEvent *Event)
        }
      if (!*tmpstring)
         tmpstring = "";
+     int tmpstringlen = strlen(*tmpstring);
      int ovector[OVECCOUNT];
      int rc = 0;
+     cString ctmpstring;
+     switch (csource) {
+       case REGEXP_TITLE:
+         ctmpstring = Event->Title();
+         break;
+       case REGEXP_SHORTTEXT:
+         ctmpstring = Event->ShortText();
+         break;
+       case REGEXP_DESCRIPTION:
+         ctmpstring = Event->Description();
+         break;
+       default:
+         ctmpstring = "";
+         break;
+       }
+     if (cre && ((rc = pcre_exec(cre, csd, *ctmpstring, strlen(*ctmpstring), 0, 0, ovector, OVECCOUNT)) != 1))
+        return false;
+
      if (replace != NONE) {// find and replace
         int last_match_end = -1;
         int options = 0;
         int start_offset = 0;
-        int tmpstringlen = strlen(*tmpstring);
         cString resultstring = "";
         // loop through matches
         while ((rc = pcre_exec(re, sd, *tmpstring, tmpstringlen, start_offset, options, ovector, OVECCOUNT)) > 0) {
@@ -221,7 +306,7 @@ bool cRegexp::Apply(cEvent *Event)
               start_offset = ovector[1];
               }
         // replace EPG field if regexp matched
-        if (last_match_end > 0 && (last_match_end < tmpstringlen - 1)) {
+        if (last_match_end > 0 && (last_match_end <= tmpstringlen)) {
            resultstring = cString::sprintf("%s%s", *resultstring, tmpstring + last_match_end);
            switch (source) {
              case REGEXP_TITLE:
@@ -240,8 +325,8 @@ bool cRegexp::Apply(cEvent *Event)
            }
         }
      else {// use backreferences
-        const char *string;
-        rc = pcre_exec(re, sd, *tmpstring, strlen(*tmpstring), 0, 0, ovector, OVECCOUNT);
+        const char *capturestring;
+        rc = pcre_exec(re, sd, *tmpstring, tmpstringlen, 0, 0, ovector, OVECCOUNT);
         if (rc == 0) {
            error("maximum number of captured substrings is %d\n", OVECCOUNT / 3 - 1);
            }
@@ -250,51 +335,51 @@ bool cRegexp::Apply(cEvent *Event)
            // loop through all possible backreferences
            // TODO allow duplicate backreference names?
            while (i < 10) {
-             if (pcre_get_named_substring(re, tmpstring, ovector, rc, strBackrefs[i], &string) != PCRE_ERROR_NOSUBSTRING) {
+             if (pcre_get_named_substring(re, tmpstring, ovector, rc, strBackrefs[i], &capturestring) != PCRE_ERROR_NOSUBSTRING) {
                 switch (i) {
                   case ATITLE:
                   case PTITLE:
                     if (Event->Title()) {
                        if (i == ATITLE)
-                          Event->SetTitle(*cString::sprintf("%s %s", Event->Title(), string));
+                          Event->SetTitle(*cString::sprintf("%s %s", Event->Title(), capturestring));
                        else
-                          Event->SetTitle(*cString::sprintf("%s %s", string, Event->Title()));
+                          Event->SetTitle(*cString::sprintf("%s %s", capturestring, Event->Title()));
                        break;
                        }
                   case TITLE:
-                    Event->SetTitle(string);
+                    Event->SetTitle(capturestring);
                     break;
                   case ASHORTTEXT:
                   case PSHORTTEXT:
                     if (Event->ShortText()) {
                        if (i == ASHORTTEXT)
-                          Event->SetShortText(*cString::sprintf("%s %s", Event->ShortText(), string));
+                          Event->SetShortText(*cString::sprintf("%s %s", Event->ShortText(), capturestring));
                        else
-                          Event->SetShortText(*cString::sprintf("%s %s", string, Event->ShortText()));
+                          Event->SetShortText(*cString::sprintf("%s %s", capturestring, Event->ShortText()));
                        break;
                        }
                   case SHORTTEXT:
-                    Event->SetShortText(string);
+                    Event->SetShortText(capturestring);
                     break;
                   case ADESCRIPTION:
                   case PDESCRIPTION:
                     if (Event->Description()) {
                        if (i == ADESCRIPTION)
-                          Event->SetDescription(*cString::sprintf("%s %s", Event->Description(), string));
+                          Event->SetDescription(*cString::sprintf("%s %s", Event->Description(), capturestring));
                        else
-                          Event->SetDescription(*cString::sprintf("%s %s", string, Event->Description()));
+                          Event->SetDescription(*cString::sprintf("%s %s", capturestring, Event->Description()));
                        break;
                        }
                   case DESCRIPTION:
-                    Event->SetDescription(string);
+                    Event->SetDescription(capturestring);
                     break;
                   case RATING:
-                    Event->SetParentalRating(atoi(string));
+                    Event->SetParentalRating(atoi(capturestring));
                     break;
                   default:
                     break;
                   }
-                pcre_free_substring(string);
+                pcre_free_substring(capturestring);
                 }
               ++i;
               }
diff --git a/regexp.h b/regexp.h
index 7077a00..18fc08d 100644
--- a/regexp.h
+++ b/regexp.h
@@ -8,27 +8,34 @@
 #ifndef __EPGFIXER_REGEXP_H_
 #define __EPGFIXER_REGEXP_H_
 
-#include "tools.h"
-#include <vdr/epg.h>
-
 #ifdef HAVE_PCREPOSIX
 #include <pcre.h>
 #endif
 
+#include <vdr/epg.h>
+#include <vdr/tools.h>
+#include "tools.h"
+
 typedef enum { REGEXP_TITLE, REGEXP_SHORTTEXT, REGEXP_DESCRIPTION, REGEXP_UNDEFINED } sources;
 
 class cRegexp : public cListItem
 {
 private:
+  char *cregexp;
   char *regexp;
   char *replacement;
   int replace;
+  int cmodifiers;
   int modifiers;
+  int csource;
   int source;
+  pcre *cre;
   pcre *re;
+  pcre_extra *csd;
   pcre_extra *sd;
   void Compile();
   void FreeCompiled();
+  int ParseModifiers(char *modstring, int substitution = 0);
   void ParseRegexp(char *restring);
 
 public:
diff --git a/setup_menu.c b/setup_menu.c
index cecae25..8b60870 100644
--- a/setup_menu.c
+++ b/setup_menu.c
@@ -5,14 +5,18 @@
  *
  */
 
-#include "setup_menu.h"
+#include <stdio.h>
 #include <vdr/config.h>
+#include <vdr/eit.h>
 #include <vdr/i18n.h>
+#include <vdr/menu.h>
+#include <vdr/skins.h>
 #include "blacklist.h"
 #include "charset.h"
 #include "epgclone.h"
 #include "regexp.h"
 #include "tools.h"
+#include "setup_menu.h"
 
 //--- cMenuSetupConfigEditor ------------------------------------------------------
 
@@ -78,6 +82,8 @@ protected:
   }
   void Set(void)
   {
+    int current = Current();
+
     Clear();
     int i = 0;
     LISTITEM *item = (LISTITEM *)list->First();
@@ -86,7 +92,12 @@ protected:
           item = (LISTITEM *)item->Next();
           ++i;
           }
-    SetHelp(trVDR("Button$On/Off"), trVDR("Button$New"), trVDR("Button$Delete"), tr("Button$Cancel"));
+    if (list->Count() > 0)
+        SetHelp(trVDR("Button$On/Off"), trVDR("Button$New"), trVDR("Button$Delete"), tr("Button$Cancel"));
+    else
+        SetHelp(NULL,trVDR("Button$New"), NULL, tr("Button$Cancel"));
+
+    SetCurrent(Get(current));
     Display();
   }
 public:
@@ -111,9 +122,11 @@ public:
     if (state == osUnknown) {
        switch (Key) {
          case kRed:
-           list->Get(Current())->ToggleEnabled();
-           Set();
-           Display();
+           if (list->Count() > 0) {
+              list->Get(Current())->ToggleEnabled();
+              Set();
+              Display();
+              }
            state = osContinue;
            break;
          case kGreen:
@@ -126,12 +139,14 @@ public:
            state = osContinue;
            break;
          case kYellow:
-           Store();
-           FreeArray();
-           list->Del(list->Get(Current()),true);
-           LoadListToArray();
-           Set();
-           Display();
+           if (list->Count() > 0) {
+              Store();
+              FreeArray();
+              list->Del(list->Get(Current()),true);
+              LoadListToArray();
+              Set();
+              Display();
+              }
            state = osContinue;
            break;
          case kBlue:
@@ -156,12 +171,14 @@ public:
 
 cMenuSetupEpgfixer::cMenuSetupEpgfixer(void)
 {
-  memcpy(&newconfig, &EpgfixerSetup, sizeof(cEpgfixerSetup));
+  newconfig = EpgfixerSetup;
   Set();
 }
 
 void cMenuSetupEpgfixer::Set(void)
 {
+  int current = Current();
+
   Clear();
   help.Clear();
   Add(new cOsdItem(tr("Regular expressions"), osUser1));
@@ -207,12 +224,14 @@ void cMenuSetupEpgfixer::Set(void)
                             &newconfig.striphtml));
   help.Append(tr("Convert HTML entities from all fields to matching regular characters."));
   SetHelp(tr("Button$Load"),NULL,NULL, tr("Button$Clear EPG"));
+
+  SetCurrent(Get(current));
   Display();
 }
 
 void cMenuSetupEpgfixer::Store(void)
 {
-  memcpy(&EpgfixerSetup, &newconfig, sizeof(cEpgfixerSetup));
+  EpgfixerSetup = newconfig;
 
   SetupStore("RemoveQuotesFromShortText",           EpgfixerSetup.quotedshorttext);
   SetupStore("MoveDescriptionFromShortText",        EpgfixerSetup.blankbeforedescription);
diff --git a/setup_menu.h b/setup_menu.h
index 6eef26f..c9f7332 100644
--- a/setup_menu.h
+++ b/setup_menu.h
@@ -8,8 +8,8 @@
 #ifndef __EPGFIXER_SETUP_MENU_H
 #define __EPGFIXER_SETUP_MENU_H
 
-#include <vdr/menu.h>
 #include <vdr/menuitems.h>
+#include <vdr/tools.h>
 #include "config.h"
 
 class cMenuSetupEpgfixer : public cMenuSetupPage
diff --git a/tools.c b/tools.c
index 4080c03..9c9133d 100644
--- a/tools.c
+++ b/tools.c
@@ -5,8 +5,271 @@
  *
  */
 
-#include "tools.h"
+#include <stdlib.h>
+#include <string.h>
 #include <vdr/thread.h>
+#include "charset.h"
+#include "config.h"
+#include "regexp.h"
+#include "tools.h"
+
+//
+// Original VDR bug fixes adapted from epg.c of VDR
+// by Klaus Schmidinger
+//
+
+static void StripControlCharacters(char *s)
+{
+  if (s) {
+     int len = strlen(s);
+     while (len > 0) {
+           int l = Utf8CharLen(s);
+           uchar *p = (uchar *)s;
+           if (l == 2 && *p == 0xC2) // UTF-8 sequence
+              p++;
+           if (*p == 0x86 || *p == 0x87) {
+              memmove(s, p + 1, len - l + 1); // we also copy the terminating 0!
+              len -= l;
+              l = 0;
+              }
+           s += l;
+           len -= l;
+           }
+     }
+}
+
+void FixOriginalEpgBugs(cEvent *event)
+{
+  // Copy event title, shorttext and description to temporary variables
+  // we don't want any "(null)" titles
+  char *title = event->Title() ? strdup(event->Title()) : strdup("No title");
+  char *shortText = event->ShortText() ? strdup(event->ShortText()) : NULL;
+  char *description = event->Description() ? strdup(event->Description()) : NULL;
+
+  // Some TV stations apparently have their own idea about how to fill in the
+  // EPG data. Let's fix their bugs as good as we can:
+
+  // Some channels put the ShortText in quotes and use either the ShortText
+  // or the Description field, depending on how long the string is:
+  //
+  // Title
+  // "ShortText". Description
+  //
+  if (EpgfixerSetup.quotedshorttext && (shortText == NULL) != (description == NULL)) {
+     char *p = shortText ? shortText : description;
+     if (*p == '"') {
+        const char *delim = "\".";
+        char *e = strstr(p + 1, delim);
+        if (e) {
+           *e = 0;
+           char *s = strdup(p + 1);
+           char *d = strdup(e + strlen(delim));
+           free(shortText);
+           free(description);
+           shortText = s;
+           description = d;
+           }
+        }
+     }
+
+  // Some channels put the Description into the ShortText (preceded
+  // by a blank) if there is no actual ShortText and the Description
+  // is short enough:
+  //
+  // Title
+  //  Description
+  //
+  if (EpgfixerSetup.blankbeforedescription && shortText && !description) {
+     if (*shortText == ' ') {
+        memmove(shortText, shortText + 1, strlen(shortText));
+        description = shortText;
+        shortText = NULL;
+        }
+     }
+
+  // Sometimes they repeat the Title in the ShortText:
+  //
+  // Title
+  // Title
+  //
+  if (EpgfixerSetup.repeatedtitle && shortText && strcmp(title, shortText) == 0) {
+     free(shortText);
+     shortText = NULL;
+     }
+
+  // Some channels put the ShortText between double quotes, which is nothing
+  // but annoying (some even put a '.' after the closing '"'):
+  //
+  // Title
+  // "ShortText"[.]
+  //
+  if (EpgfixerSetup.doublequotedshorttext && shortText && *shortText == '"') {
+     int l = strlen(shortText);
+     if (l > 2 && (shortText[l - 1] == '"' || (shortText[l - 1] == '.' && shortText[l - 2] == '"'))) {
+        memmove(shortText, shortText + 1, l);
+        char *p = strrchr(shortText, '"');
+        if (p)
+           *p = 0;
+        }
+     }
+
+  // Some channels apparently try to do some formatting in the texts,
+  // which is a bad idea because they have no way of knowing the width
+  // of the window that will actually display the text.
+  // Remove excess whitespace:
+  if (EpgfixerSetup.removeformatting) {
+     title = compactspace(title);
+     shortText = compactspace(shortText);
+     description = compactspace(description);
+     }
+
+#define MAX_USEFUL_EPISODE_LENGTH 40
+  // Some channels put a whole lot of information in the ShortText and leave
+  // the Description totally empty. So if the ShortText length exceeds
+  // MAX_USEFUL_EPISODE_LENGTH, let's put this into the Description
+  // instead:
+  if (EpgfixerSetup.longshorttext && !isempty(shortText) && isempty(description)) {
+     if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
+        free(description);
+        description = shortText;
+        shortText = NULL;
+        }
+     }
+
+  // Some channels put the same information into ShortText and Description.
+  // In that case we delete one of them:
+  if (EpgfixerSetup.equalshorttextanddescription && shortText && description && strcmp(shortText, description) == 0) {
+     if (strlen(shortText) > MAX_USEFUL_EPISODE_LENGTH) {
+        free(shortText);
+        shortText = NULL;
+        }
+     else {
+        free(description);
+        description = NULL;
+        }
+     }
+
+  // Some channels use the ` ("backtick") character, where a ' (single quote)
+  // would be normally used. Actually, "backticks" in normal text don't make
+  // much sense, so let's replace them:
+  if (EpgfixerSetup.nobackticks) {
+     strreplace(title, '`', '\'');
+     strreplace(shortText, '`', '\'');
+     strreplace(description, '`', '\'');
+     }
+
+  // The stream components have a "description" field which some channels
+  // apparently have no idea of how to set correctly:
+  const cComponents *components = event->Components();
+  if (EpgfixerSetup.components && components) {
+     for (int i = 0; i < components->NumComponents(); ++i) {
+         tComponent *p = components->Component(i);
+         switch (p->stream) {
+           case 0x01: { // video
+                if (p->description) {
+                   if (strcasecmp(p->description, "Video") == 0 ||
+                        strcasecmp(p->description, "Bildformat") == 0) {
+                      // Yes, we know it's video - that's what the 'stream' code
+                      // is for! But _which_ video is it?
+                      free(p->description);
+                      p->description = NULL;
+                      }
+                   }
+                if (!p->description) {
+                   switch (p->type) {
+                     case 0x01:
+                     case 0x05: p->description = strdup("4:3"); break;
+                     case 0x02:
+                     case 0x03:
+                     case 0x06:
+                     case 0x07: p->description = strdup("16:9"); break;
+                     case 0x04:
+                     case 0x08: p->description = strdup(">16:9"); break;
+                     case 0x09:
+                     case 0x0D: p->description = strdup("HD 4:3"); break;
+                     case 0x0A:
+                     case 0x0B:
+                     case 0x0E:
+                     case 0x0F: p->description = strdup("HD 16:9"); break;
+                     case 0x0C:
+                     case 0x10: p->description = strdup("HD >16:9"); break;
+                     default: ;
+                     }
+                   }
+                }
+                break;
+           case 0x02: { // audio
+                if (p->description) {
+                   if (strcasecmp(p->description, "Audio") == 0) {
+                      // Yes, we know it's audio - that's what the 'stream' code
+                      // is for! But _which_ audio is it?
+                      free(p->description);
+                      p->description = NULL;
+                      }
+                   }
+                if (!p->description) {
+                   switch (p->type) {
+                     case 0x05: p->description = strdup("Dolby Digital"); break;
+                     default: ; // all others will just display the language
+                     }
+                   }
+                }
+                break;
+           default: ;
+           }
+         }
+     }
+
+  // VDR can't usefully handle newline characters in the title, shortText or component description of EPG
+  // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel):
+  strreplace(title, '\n', ' ');
+  strreplace(shortText, '\n', ' ');
+  if (components) {
+     for (int i = 0; i < components->NumComponents(); ++i) {
+         tComponent *p = components->Component(i);
+         if (p->description)
+            strreplace(p->description, '\n', ' ');
+         }
+     }
+  // Same for control characters:
+  StripControlCharacters(title);
+  StripControlCharacters(shortText);
+  StripControlCharacters(description);
+  // Set modified data back to event
+  event->SetTitle(title);
+  event->SetShortText(shortText);
+  event->SetDescription(description);
+
+  free(title);
+  free(shortText);
+  free(description);
+}
+
+bool FixBugs(cEvent *Event)
+{
+  return EpgfixerRegexps.Apply(Event);
+}
+
+bool FixCharSets(cEvent *Event)
+{
+  return EpgfixerCharSets.Apply(Event);
+}
+
+void StripHTML(cEvent *Event)
+{
+  if (EpgfixerSetup.striphtml) {
+     char *tmpstring = NULL;
+     tmpstring = Event->Title() ? strdup(Event->Title()) : NULL;
+     Event->SetTitle(striphtml(tmpstring));
+     FREE(tmpstring);
+     tmpstring = Event->ShortText() ? strdup(Event->ShortText()) : NULL;
+     Event->SetShortText(striphtml(tmpstring));
+     FREE(tmpstring);
+     tmpstring = Event->Description() ? strdup(Event->Description()) : NULL;
+     Event->SetDescription(striphtml(tmpstring));
+     FREE(tmpstring);
+     }
+}
 
 //
 // HTML conversion code taken from RSS Reader plugin for VDR
@@ -206,10 +469,19 @@ void cAddEventThread::Action(void)
         cSchedules *schedules = (cSchedules *)cSchedules::Schedules(SchedulesLock);
         Lock();
         while (schedules && (e = list->First()) != NULL) {
-              cSchedule *schedule = (cSchedule *)schedules->GetSchedule(Channels.GetByChannelID(e->GetChannelID()), true);
-              schedule->AddEvent(e->GetEvent());
-              EpgHandlers.SortSchedule(schedule);
-              EpgHandlers.DropOutdated(schedule, e->GetEvent()->StartTime(), e->GetEvent()->EndTime(), e->GetEvent()->TableID(), e->GetEvent()->Version());
+              tChannelID chanid = e->GetChannelID();
+              cChannel *chan = Channels.GetByChannelID(chanid);
+              if (!chan) {
+                 error("Destination channel %s not found for cloning!", *chanid.ToString());
+                 }
+              else {
+                 cSchedule *schedule = (cSchedule *)schedules->GetSchedule(chan, true);
+                 if (schedule) {
+                    schedule->AddEvent(e->GetEvent());
+                    EpgHandlers.SortSchedule(schedule);
+                    EpgHandlers.DropOutdated(schedule, e->GetEvent()->StartTime(), e->GetEvent()->EndTime(), e->GetEvent()->TableID(), e->GetEvent()->Version());
+                    }
+                 }
               list->Del(e);
               }
         Unlock();
diff --git a/tools.h b/tools.h
index 119d32d..3b5c594 100644
--- a/tools.h
+++ b/tools.h
@@ -8,15 +8,27 @@
 #ifndef __EPGFIXER_TOOLS_H_
 #define __EPGFIXER_TOOLS_H_
 
+#include <stdio.h>
+#include <unistd.h>
 #include <vdr/epg.h>
 #include <vdr/tools.h>
-#include <unistd.h>
-#include <stdio.h>
 
-#define error(x...) esyslog("EPGFixer: " x);
+#ifdef DEBUG
+#define debug(x...) dsyslog("EPGFixer: " x);
+#else
+#define debug(x...) ;
+#endif
+#define error(x...) esyslog("ERROR: " x);
 
 #define FREE(x) { free(x); x = NULL; }
 
+// --- EPG bug fixes ----------------------------------------------------
+
+void FixOriginalEpgBugs(cEvent *event);
+bool FixCharSets(cEvent *Event);
+bool FixBugs(cEvent *Event);
+void StripHTML(cEvent *Event);
+
 // --- Add event to schedule --------------------------------------------
 
 void AddEvent(cEvent *event, tChannelID ChannelID);
@@ -94,6 +106,7 @@ template<class LISTITEM, class PARAMETER> bool cEpgfixerList<LISTITEM, PARAMETER
         if (count == 0)
           logmsg = cString::sprintf("%s none", *logmsg);
         isyslog("%s", *logmsg);
+        result = true;
         }
      else {
         LOG_ERROR_STR(fileName);