diff --git a/readme.md b/readme.md
index 3f2b061..4454214 100644
--- a/readme.md
+++ b/readme.md
@@ -11,7 +11,7 @@ About ChanSort
--------------
ChanSort is a Windows application that allows you to reorder your TV's channel list.
Most modern TVs can transfer channel lists via USB stick, which you can plug into your PC.
-ChanSort supports various file formats from **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
+ChanSort supports various file formats from **Philips** (new), **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
Hisense, Samsung, LG, Panasonic, Toshiba and the Linux VDR project.

@@ -96,6 +96,10 @@ Models that export a .zip file containing chmgt.db, dvbSysData.db and dvbMainDat
**ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken**
These brands use .sdx files (currently only satellite lists are supported)
+**Philips**
+Philips uses countless incompatible file formats for various TV models.
+ChanSort currently supports 2 different versions of .xml files, other formats are not supported.
+
**VDR (Linux Video Disk Recorder)**
Supports the channels.conf file format.
Implementation for this was provided by TCr82 from the VDR project.
diff --git a/readme_de.md b/readme_de.md
index fadc284..54e794e 100644
--- a/readme_de.md
+++ b/readme_de.md
@@ -11,7 +11,7 @@ Links
--------------
ChanSort ist eine Windows-Anwendung, die das Sortieren von Fernsehsenderlisten erlaubt.
Die meisten modernen Fernseher können Senderlisten auf einen USB-Stick übertragen, den man danach am PC anschließt.
-ChanSort unterstützt diverse Dateiformate von **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
+ChanSort unterstützt diverse Dateiformate von **Philips** (neu), **Sony** (new), **ITT, Medion, Nabo, ok., PEAQ, Schaub-Lorenz, Silva-Schneider, Telefunken** (new),
Hisense, Samsung, LG, Panasonic, Toshiba und dem Linux VDR Projekt.

@@ -91,6 +91,10 @@ Die Marken nutzen .sdx Dateien (derzeit wird nur Satellitenempfang unterst
Modelle, die eine .zip-Datei mit folgendem Inhalt: chmgt.db, dvbSysData.db und dvbMainData.db.
(z.B. RL, SL, TL, UL, VL, WL, XL, YL models of series 8xx/9xx)
+**Philips**
+Philips verwendet unzählige unterschiedliche Dateiformate für diverse TV-Modelle.
+ChanSort unterstützt derzeit 2 Varianten von .xml-Dateien. Andere Formate werden nicht unterstützt.
+
**VDR (Linux Video Disk Recorder)**
Unterstützung des channels.conf Dateiformats.
Die Implementation hierfür wurde vom Mitglied "TCr82" des VDR Projekts beigesteuert.
diff --git a/source/ChanSort.Api/Model/ChannelList.cs b/source/ChanSort.Api/Model/ChannelList.cs
index b94fea4..6587008 100644
--- a/source/ChanSort.Api/Model/ChannelList.cs
+++ b/source/ChanSort.Api/Model/ChannelList.cs
@@ -20,7 +20,7 @@ namespace ChanSort.Api
this.SignalSource = source;
this.ShortCaption = caption;
this.FirstProgramNumber = (source & SignalSource.Digital) != 0 ? 1 : 0;
- this.VisibleColumnFieldNames = DefaultVisibleColumns;
+ this.VisibleColumnFieldNames = DefaultVisibleColumns.ToList(); // create copy of default list, so it can be modified
}
public string ShortCaption { get; }
diff --git a/source/ChanSort.Api/Model/Enums.cs b/source/ChanSort.Api/Model/Enums.cs
index 69466f5..d27c479 100644
--- a/source/ChanSort.Api/Model/Enums.cs
+++ b/source/ChanSort.Api/Model/Enums.cs
@@ -24,6 +24,8 @@ namespace ChanSort.Api
Sat = 0x0040,
IP = 0x0080,
+ MaskAdInput = MaskAnalogDigital | MaskAntennaCableSat,
+
// bit 9+10: TV/Radio
MaskTvRadio = 0x0300,
Tv = 0x0100,
diff --git a/source/ChanSort.Loader.PhilipsXml/ChanSort.Loader.PhilipsXml.csproj b/source/ChanSort.Loader.PhilipsXml/ChanSort.Loader.PhilipsXml.csproj
new file mode 100644
index 0000000..03a9391
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsXml/ChanSort.Loader.PhilipsXml.csproj
@@ -0,0 +1,74 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}
+ Library
+ Properties
+ ChanSort.Loader.PhilipsXml
+ ChanSort.Loader.PhilipsXml
+ v4.0
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {dccffa08-472b-4d17-bb90-8f513fc01392}
+ ChanSort.Api
+
+
+
+
\ No newline at end of file
diff --git a/source/ChanSort.Loader.PhilipsXml/Channel.cs b/source/ChanSort.Loader.PhilipsXml/Channel.cs
new file mode 100644
index 0000000..a33f8e7
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsXml/Channel.cs
@@ -0,0 +1,20 @@
+using System.Xml;
+using ChanSort.Api;
+
+namespace ChanSort.Loader.PhilipsXml
+{
+ internal class Channel : ChannelInfo
+ {
+ public readonly XmlNode SetupNode;
+ public string RawName;
+ public string RawSatellite;
+
+ internal Channel(SignalSource source, int order, int rowId, XmlNode setupNode)
+ {
+ this.SignalSource = source;
+ this.RecordOrder = order;
+ this.RecordIndex = rowId;
+ this.SetupNode = setupNode;
+ }
+ }
+}
diff --git a/source/ChanSort.Loader.PhilipsXml/CustomXmlWriter.cs b/source/ChanSort.Loader.PhilipsXml/CustomXmlWriter.cs
new file mode 100644
index 0000000..43f445e
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsXml/CustomXmlWriter.cs
@@ -0,0 +1,170 @@
+using System;
+using System.IO;
+using System.Xml;
+
+namespace ChanSort.Loader.PhilipsXml
+{
+ ///
+ /// This XmlWriter replaces some characters with Char- or Entity- references the same way
+ /// they are escaped in the original Sony XML files
+ ///
+ class CustomXmlWriter : XmlWriter
+ {
+ private static readonly char[] CharsToEscape = { '\'', '\"', '&', '<', '>' };
+ private static readonly string[] CharEntites = { "apos", "quot", "amp", "lt", "gt" };
+
+ private XmlWriter w;
+ private readonly bool escapeAsEntityRef; // if true, use & otherwise "
+
+ public CustomXmlWriter(TextWriter tw, XmlWriterSettings settings, bool useEntityRef)
+ {
+ this.w = XmlWriter.Create(tw, settings);
+ this.escapeAsEntityRef = useEntityRef;
+ }
+
+ public override void WriteString(string text)
+ {
+ int i = 0, j;
+ while ((j = text.IndexOfAny(CharsToEscape, i)) >= 0)
+ {
+ this.w.WriteString(text.Substring(i, j - i));
+ if (this.escapeAsEntityRef)
+ {
+ // => &
+ int k = Array.IndexOf(CharsToEscape, text[j]);
+ this.w.WriteEntityRef(CharEntites[k]);
+ }
+ else
+ {
+ // => &
+ //this.w.WriteCharEntity(text[j]);
+ this.w.WriteRaw("" + (int)text[j] + ";");
+ }
+
+ i = j + 1;
+ }
+ this.w.WriteString(text.Substring(i));
+ }
+
+ #region 1:1 delegation
+
+ public override void WriteStartDocument()
+ {
+ this.w.WriteStartDocument();
+ }
+
+ public override void WriteStartDocument(bool standalone)
+ {
+ this.w.WriteStartDocument(standalone);
+ }
+
+ public override void WriteEndDocument()
+ {
+ this.w.WriteEndDocument();
+ }
+
+ public override void WriteDocType(string name, string pubid, string sysid, string subset)
+ {
+ this.w.WriteDocType(name, pubid, sysid, subset);
+ }
+
+ public override void WriteStartElement(string prefix, string localName, string ns)
+ {
+ this.w.WriteStartElement(prefix, localName, ns);
+ }
+
+ public override void WriteEndElement()
+ {
+ this.w.WriteEndElement();
+ }
+
+ public override void WriteFullEndElement()
+ {
+ this.w.WriteFullEndElement();
+ }
+
+ public override void WriteStartAttribute(string prefix, string localName, string ns)
+ {
+ this.w.WriteStartAttribute(prefix, localName, ns);
+ }
+
+ public override void WriteEndAttribute()
+ {
+ this.w.WriteEndAttribute();
+ }
+
+ public override void WriteCData(string text)
+ {
+ this.w.WriteCData(text);
+ }
+
+ public override void WriteComment(string text)
+ {
+ this.w.WriteComment(text);
+ }
+
+ public override void WriteProcessingInstruction(string name, string text)
+ {
+ this.w.WriteProcessingInstruction(name, text);
+ }
+
+ public override void WriteEntityRef(string name)
+ {
+ this.w.WriteEntityRef(name);
+ }
+
+ public override void WriteCharEntity(char ch)
+ {
+ this.w.WriteCharEntity(ch);
+ }
+
+ public override void WriteWhitespace(string ws)
+ {
+ this.w.WriteWhitespace(ws);
+ }
+
+ public override void WriteSurrogateCharEntity(char lowChar, char highChar)
+ {
+ this.w.WriteSurrogateCharEntity(lowChar, highChar);
+ }
+
+ public override void WriteChars(char[] buffer, int index, int count)
+ {
+ this.w.WriteChars(buffer, index, count);
+ }
+
+ public override void WriteRaw(char[] buffer, int index, int count)
+ {
+ this.w.WriteRaw(buffer, index, count);
+ }
+
+ public override void WriteRaw(string data)
+ {
+ this.w.WriteRaw(data);
+ }
+
+ public override void WriteBase64(byte[] buffer, int index, int count)
+ {
+ this.w.WriteBase64(buffer, index, count);
+ }
+
+ public override void Close()
+ {
+ this.w.Close();
+ }
+
+ public override void Flush()
+ {
+ this.w.Flush();
+ }
+
+ public override string LookupPrefix(string ns)
+ {
+ return this.w.LookupPrefix(ns);
+ }
+
+ public override WriteState WriteState => this.w.WriteState;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/source/ChanSort.Loader.PhilipsXml/Properties/AssemblyInfo.cs b/source/ChanSort.Loader.PhilipsXml/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c8b3306
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsXml/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ChanSort.Loader.PhilipsXml")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ChanSort.Loader.PhilipsXml")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d7bafd55-50f5-46c3-a76b-2193bed5358f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/source/ChanSort.Loader.PhilipsXml/Serializer.cs b/source/ChanSort.Loader.PhilipsXml/Serializer.cs
new file mode 100644
index 0000000..a68989b
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsXml/Serializer.cs
@@ -0,0 +1,408 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Xml;
+using System.Xml.Schema;
+using ChanSort.Api;
+
+namespace ChanSort.Loader.PhilipsXml
+{
+ /*
+ This loader supports 2 different kinds of XML files from Philips.
+
+ I had a test file "DVBS.xml" with satellite channels and entries like:
+
+
+
+
+
+ The other file was "CM_TPM1013E_LA_CK.xml" with entries like:
+
+
+
+
+
+ */
+ class Serializer : SerializerBase
+ {
+ private readonly ChannelList terrChannels = new ChannelList(SignalSource.DvbT | SignalSource.Tv | SignalSource.Radio, "DVB-T");
+ private readonly ChannelList cableChannels = new ChannelList(SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-C");
+ private readonly ChannelList satChannels = new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S");
+
+ private ChannelList curList;
+
+ private XmlDocument doc;
+ private byte[] content;
+ private string textContent;
+ private string newline;
+ private int formatVersion;
+
+ #region ctor()
+ public Serializer(string inputFile) : base(inputFile)
+ {
+ this.Features.ChannelNameEdit = ChannelNameEditMode.All;
+ this.Features.CanDeleteChannels = true;
+
+ this.DataRoot.AddChannelList(this.terrChannels);
+ this.DataRoot.AddChannelList(this.cableChannels);
+ this.DataRoot.AddChannelList(this.satChannels);
+
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ list.VisibleColumnFieldNames.Remove("PcrPid");
+ list.VisibleColumnFieldNames.Remove("VideoPid");
+ list.VisibleColumnFieldNames.Remove("AudioPid");
+ list.VisibleColumnFieldNames.Remove("Skip");
+ list.VisibleColumnFieldNames.Remove("ShortName");
+ list.VisibleColumnFieldNames.Remove("Provider");
+ }
+ }
+ #endregion
+
+ #region DisplayName
+ public override string DisplayName => "Philips *.xml loader";
+
+ #endregion
+
+
+ #region Load()
+
+ public override void Load()
+ {
+ bool fail = false;
+ try
+ {
+ this.doc = new XmlDocument();
+ this.content = File.ReadAllBytes(this.FileName);
+ this.textContent = Encoding.UTF8.GetString(this.content);
+ this.newline = this.textContent.Contains("\r\n") ? "\r\n" : "\n";
+
+ var settings = new XmlReaderSettings
+ {
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ ValidationFlags = XmlSchemaValidationFlags.None,
+ DtdProcessing = DtdProcessing.Ignore
+ };
+ using (var reader = XmlReader.Create(new StringReader(textContent), settings))
+ {
+ doc.Load(reader);
+ }
+ }
+ catch
+ {
+ fail = true;
+ }
+
+ var root = doc.FirstChild;
+ if (root is XmlDeclaration)
+ root = root.NextSibling;
+ if (fail || root == null || root.LocalName != "ChannelMap")
+ throw new FileLoadException("\"" + this.FileName + "\" is not a supported Philips XML file");
+
+
+ int rowId = 0;
+ foreach (XmlNode child in root.ChildNodes)
+ {
+ switch (child.LocalName)
+ {
+ case "Channel":
+ if (rowId == 0)
+ this.DetectFormatAndFeatures(child);
+ if (this.curList != null)
+ this.ReadChannel(child, rowId++);
+ break;
+ }
+ }
+ }
+ #endregion
+
+ #region DetectFormatAndFeatures()
+
+ private void DetectFormatAndFeatures(XmlNode node)
+ {
+ var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
+ var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
+
+ var fname = Path.GetFileNameWithoutExtension(this.FileName).ToLower();
+ var medium = bcastNode.GetAttribute("medium");
+ if (medium == "" && fname.Length == 4 && fname.StartsWith("dvb"))
+ medium = fname;
+ bool hasEncrypt = false;
+
+ if (setupNode.HasAttribute("ChannelName"))
+ {
+ this.formatVersion = 1;
+ this.DataRoot.SupportedFavorites = Favorites.A;
+ this.DataRoot.SortedFavorites = true;
+
+ var dtype = bcastNode.GetAttribute("DecoderType");
+ if (dtype == "1")
+ medium = "dvbt";
+ else if (dtype == "2")
+ medium = "dvbc";
+ else if (dtype == "3")
+ medium = "dvbs";
+
+ hasEncrypt = setupNode.HasAttribute("Scrambled");
+ }
+ else if (setupNode.HasAttribute("name"))
+ {
+ this.formatVersion = 2;
+ this.DataRoot.SupportedFavorites = 0;
+ this.DataRoot.SortedFavorites = false;
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ list.VisibleColumnFieldNames.Remove("Favorites");
+ list.VisibleColumnFieldNames.Remove("Lock");
+ list.VisibleColumnFieldNames.Remove("Hidden");
+ list.VisibleColumnFieldNames.Remove("ServiceType");
+ list.VisibleColumnFieldNames.Remove("ServiceTypeName");
+ list.VisibleColumnFieldNames.Remove("Encrypted");
+ }
+ }
+ else
+ throw new FileLoadException("Unknown data format");
+
+ switch (medium)
+ {
+ case "dvbt":
+ this.curList = this.terrChannels;
+ break;
+ case "dvbc":
+ this.curList = this.cableChannels;
+ break;
+ case "dvbs":
+ this.curList = this.satChannels;
+ break;
+ default:
+ this.curList = null;
+ break;
+ }
+
+ if (!hasEncrypt)
+ this.curList?.VisibleColumnFieldNames.Remove("Encrypted");
+ }
+ #endregion
+
+ #region ReadChannel()
+ private void ReadChannel(XmlNode node, int rowId)
+ {
+ var setupNode = node["Setup"] ?? throw new FileLoadException("Missing Setup XML element");
+ var bcastNode = node["Broadcast"] ?? throw new FileLoadException("Missing Broadcast XML element");
+ var data = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ foreach (var n in new[] {setupNode, bcastNode})
+ {
+ foreach(XmlAttribute attr in n.Attributes)
+ data.Add(attr.LocalName, attr.Value);
+ }
+
+ var chan = new Channel(this.curList.SignalSource, rowId, rowId, setupNode);
+ chan.OldProgramNr = -1;
+ chan.IsDeleted = false;
+ if (this.formatVersion == 1)
+ this.ParseChannelFormat1(data, chan);
+ else if (this.formatVersion == 2)
+ this.ParseChannelFormat2(data, chan);
+
+ if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbT)
+ chan.ChannelOrTransponder = LookupData.Instance.GetDvbtTransponder(chan.FreqInMhz).ToString();
+ else if ((chan.SignalSource & SignalSource.MaskAdInput) == SignalSource.DvbC)
+ chan.ChannelOrTransponder = LookupData.Instance.GetDvbcChannelName(chan.FreqInMhz);
+
+ DataRoot.AddChannel(this.curList, chan);
+ }
+ #endregion
+
+ #region ParseChannelFormat1
+ private void ParseChannelFormat1(Dictionary data, Channel chan)
+ {
+ chan.RawSatellite = data.TryGet("SatelliteName");
+ chan.Satellite = DecodeName(chan.RawSatellite);
+ chan.OldProgramNr = ParseInt(data.TryGet("ChannelNumber"));
+ chan.RawName = data.TryGet("ChannelName");
+ chan.Name = DecodeName(chan.RawName);
+ chan.Lock = data.TryGet("ChannelLock") == "1";
+ chan.Hidden = data.TryGet("UserHidden") == "1";
+ var fav = ParseInt(data.TryGet("FavoriteNumber"));
+ chan.FavIndex[0] = fav == 0 ? -1 : fav;
+ chan.OriginalNetworkId = ParseInt(data.TryGet("Onid"));
+ chan.TransportStreamId = ParseInt(data.TryGet("Tsid"));
+ chan.ServiceId = ParseInt(data.TryGet("Sid"));
+ var freq = ParseInt(data.TryGet("Frequency"));
+ chan.FreqInMhz = freq;
+ chan.ServiceType = ParseInt(data.TryGet("ServiceType"));
+ chan.SignalSource |= LookupData.Instance.IsRadioOrTv(chan.ServiceType);
+ chan.SymbolRate = ParseInt(data.TryGet("SymbolRate"));
+ if (data.TryGetValue("Polarization", out var pol))
+ chan.Polarity = pol == "0" ? 'H' : 'V';
+ chan.Hidden |= data.TryGet("SystemHidden") == "1";
+
+ chan.Encrypted = data.TryGet("Scrambled") == "1"; // doesn't exist in all format versions
+ }
+ #endregion
+
+ #region ParseChannelFormat2
+ private void ParseChannelFormat2(Dictionary data, Channel chan)
+ {
+ chan.OldProgramNr = ParseInt(data.TryGet("presetnumber"));
+ chan.Name = data.TryGet("name");
+ chan.RawName = chan.Name;
+ chan.FreqInMhz = ParseInt(data.TryGet("frequency"));
+ if (chan.FreqInMhz > 100000)
+ chan.FreqInMhz /= 1000;
+ chan.ServiceId = ParseInt(data.TryGet("serviceID"));
+ chan.OriginalNetworkId = ParseInt(data.TryGet("ONID"));
+ chan.TransportStreamId = ParseInt(data.TryGet("TSID"));
+ chan.ServiceType = ParseInt(data.TryGet("serviceType"));
+ chan.SymbolRate = ParseInt(data.TryGet("symbolrate")) / 1000;
+ }
+ #endregion
+
+ #region ParseInt()
+ private int ParseInt(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ return 0;
+ if (input.Length > 2 && input[0] == '0' && char.ToLower(input[1]) == 'x')
+ return int.Parse(input.Substring(2), NumberStyles.HexNumber);
+ if (int.TryParse(input, out var value))
+ return value;
+ return 0;
+ }
+ #endregion
+
+ #region DecodeName()
+ private string DecodeName(string input)
+ {
+ if (input == null || !input.StartsWith("0x")) // fallback for unknown input
+ return input;
+
+ var hexParts = input.Split(' ');
+ var buffer = new MemoryStream();
+
+ foreach (var part in hexParts)
+ {
+ if (part == "" || part == "0x00")
+ continue;
+ buffer.WriteByte((byte)ParseInt(part));
+ }
+
+ return this.DefaultEncoding.GetString(buffer.GetBuffer(), 0, (int)buffer.Length);
+ }
+ #endregion
+
+ #region DefaultEncoding
+ public override Encoding DefaultEncoding
+ {
+ get => base.DefaultEncoding;
+ set
+ {
+ if (value == this.DefaultEncoding)
+ return;
+ base.DefaultEncoding = value;
+ this.ChangeEncoding();
+ }
+ }
+ #endregion
+
+ #region ChangeEncoding
+ private void ChangeEncoding()
+ {
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ foreach (var channel in list.Channels)
+ {
+ if (!(channel is Channel ch))
+ continue;
+ ch.Name = this.DecodeName(ch.RawName);
+ ch.Satellite = this.DecodeName(ch.RawSatellite);
+ }
+ }
+ }
+ #endregion
+
+ #region Save()
+ public override void Save(string tvOutputFile)
+ {
+ foreach (var list in this.DataRoot.ChannelLists)
+ this.UpdateChannelList(list);
+
+ // by default .NET reformats the whole XML. These settings produce almost same format as the TV xml files use
+ var xmlSettings = new XmlWriterSettings();
+ xmlSettings.Encoding = this.DefaultEncoding;
+ xmlSettings.CheckCharacters = false;
+ xmlSettings.Indent = true;
+ xmlSettings.IndentChars = "";
+ xmlSettings.NewLineHandling = NewLineHandling.None;
+ xmlSettings.NewLineChars = this.newline;
+ xmlSettings.OmitXmlDeclaration = false;
+
+ string xml;
+ using (var sw = new StringWriter())
+ using (var w = new CustomXmlWriter(sw, xmlSettings, false))
+ {
+ this.doc.WriteTo(w);
+ w.Flush();
+ xml = sw.ToString();
+ }
+
+ var enc = new UTF8Encoding(false, false);
+ File.WriteAllText(tvOutputFile, xml, enc);
+ }
+ #endregion
+
+ #region UpdateChannelList()
+ private void UpdateChannelList(ChannelList list)
+ {
+ foreach (var channel in list.Channels)
+ {
+ var ch = channel as Channel;
+ if (ch == null)
+ continue; // might be a proxy channel from a reference list
+
+ if (ch.IsDeleted || ch.NewProgramNr < 0)
+ {
+ ch.SetupNode.ParentNode.ParentNode.RemoveChild(ch.SetupNode.ParentNode);
+ continue;
+ }
+
+ if (this.formatVersion == 1)
+ this.UpdateChannelFormat1(ch);
+ else if (this.formatVersion == 2)
+ this.UpdateChannelFormat2(ch);
+ }
+ }
+ #endregion
+
+ #region UpdateChannelFormat1 and 2
+ private void UpdateChannelFormat1(Channel ch)
+ {
+ ch.SetupNode.Attributes["ChannelNumber"].Value = ch.NewProgramNr.ToString();
+ if (ch.IsNameModified)
+ ch.SetupNode.Attributes["ChannelName"].Value = EncodeName(ch.Name);
+ ch.SetupNode.Attributes["FavoriteNumber"].Value = Math.Max(ch.FavIndex[0], 0).ToString();
+ }
+
+ private void UpdateChannelFormat2(Channel ch)
+ {
+ ch.SetupNode.Attributes["presetnumber"].Value = ch.NewProgramNr.ToString();
+ if (ch.IsNameModified)
+ ch.SetupNode.Attributes["name"].Value = ch.Name;
+ }
+ #endregion
+
+ #region EncodeName
+ private string EncodeName(string name)
+ {
+ var bytes = this.DefaultEncoding.GetBytes(name);
+ var sb = new StringBuilder();
+ foreach (var b in bytes)
+ sb.Append($"0x{b:X2} 0x00 ");
+ return sb.ToString();
+ }
+ #endregion
+ }
+}
diff --git a/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs b/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs
new file mode 100644
index 0000000..c47b78a
--- /dev/null
+++ b/source/ChanSort.Loader.PhilipsXml/SerializerPlugin.cs
@@ -0,0 +1,16 @@
+using ChanSort.Api;
+
+namespace ChanSort.Loader.PhilipsXml
+{
+ public class SerializerPlugin : ISerializerPlugin
+ {
+ public string DllName { get; set; }
+ public string PluginName => "Philips .xml";
+ public string FileFilter => "*.xml";
+
+ public SerializerBase CreateSerializer(string inputFile)
+ {
+ return new Serializer(inputFile);
+ }
+ }
+}
diff --git a/source/ChanSort.Loader.Sony/Serializer.cs b/source/ChanSort.Loader.Sony/Serializer.cs
index a269fd4..0fa19df 100644
--- a/source/ChanSort.Loader.Sony/Serializer.cs
+++ b/source/ChanSort.Loader.Sony/Serializer.cs
@@ -23,7 +23,7 @@ namespace ChanSort.Loader.Sony
private const string SupportedFormatVersions = " e1.1.0 1.0.0 1.1.0 1.2.0 ";
- private readonly ChannelList terrChannels = new ChannelList(SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-T");
+ private readonly ChannelList terrChannels = new ChannelList(SignalSource.DvbT | SignalSource.Tv | SignalSource.Radio, "DVB-T");
private readonly ChannelList cableChannels = new ChannelList(SignalSource.DvbC | SignalSource.Tv | SignalSource.Radio, "DVB-C");
private readonly ChannelList satChannels = new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S");
private readonly ChannelList satChannelsP = new ChannelList(SignalSource.DvbS | SignalSource.Tv | SignalSource.Radio, "DVB-S Preset");
diff --git a/source/ChanSort.sln b/source/ChanSort.sln
index fb494e8..37a1345 100644
--- a/source/ChanSort.sln
+++ b/source/ChanSort.sln
@@ -52,6 +52,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.SilvaSchnei
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.Sony", "ChanSort.Loader.Sony\ChanSort.Loader.Sony.csproj", "{70E29C6B-B926-4859-9548-23375BF1E1B5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChanSort.Loader.PhilipsXml", "ChanSort.Loader.PhilipsXml\ChanSort.Loader.PhilipsXml.csproj", "{D7BAFD55-50F5-46C3-A76B-2193BED5358F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -254,6 +256,18 @@ Global
{70E29C6B-B926-4859-9548-23375BF1E1B5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{70E29C6B-B926-4859-9548-23375BF1E1B5}.Release|x86.ActiveCfg = Release|x86
{70E29C6B-B926-4859-9548-23375BF1E1B5}.Release|x86.Build.0 = Release|x86
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|x86.ActiveCfg = Debug|x86
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Debug|x86.Build.0 = Debug|x86
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7BAFD55-50F5-46C3-A76B-2193BED5358F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/source/ChanSort/ChanSort.csproj b/source/ChanSort/ChanSort.csproj
index 0a2d275..6413713 100644
--- a/source/ChanSort/ChanSort.csproj
+++ b/source/ChanSort/ChanSort.csproj
@@ -386,6 +386,10 @@
{68da8072-3a29-4076-9f64-d66f38349585}
ChanSort.Loader.Panasonic
+
+ {d7bafd55-50f5-46c3-a76b-2193bed5358f}
+ ChanSort.Loader.PhilipsXml
+
{33897002-0537-49a4-b963-a18d17311b3d}
ChanSort.Loader.SamsungJ
diff --git a/source/ChanSort/MainForm.Designer.cs b/source/ChanSort/MainForm.Designer.cs
index d3ba2d2..cf38039 100644
--- a/source/ChanSort/MainForm.Designer.cs
+++ b/source/ChanSort/MainForm.Designer.cs
@@ -161,6 +161,7 @@
this.miShowWarningsAfterLoad = new DevExpress.XtraBars.BarCheckItem();
this.miAllowEditPredefinedLists = new DevExpress.XtraBars.BarButtonItem();
this.miExplorerIntegration = new DevExpress.XtraBars.BarButtonItem();
+ this.miCheckUpdates = new DevExpress.XtraBars.BarButtonItem();
this.mnuAccessibility = new DevExpress.XtraBars.BarSubItem();
this.mnuGotoChannelList = new DevExpress.XtraBars.BarSubItem();
this.mnuInputSource = new DevExpress.XtraBars.BarLinkContainerItem();
@@ -206,7 +207,7 @@
this.pageProgNr = new DevExpress.XtraTab.XtraTabPage();
this.popupInputSource = new DevExpress.XtraBars.PopupMenu(this.components);
this.popupFavList = new DevExpress.XtraBars.PopupMenu(this.components);
- this.miCheckUpdates = new DevExpress.XtraBars.BarButtonItem();
+ this.miUtf8Charset = new DevExpress.XtraBars.BarButtonItem();
((System.ComponentModel.ISupportInitialize)(this.splitContainerControl1)).BeginInit();
this.splitContainerControl1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.grpOutputList)).BeginInit();
@@ -1026,9 +1027,10 @@
this.miCzech,
this.miRomanian,
this.miExplorerIntegration,
- this.miCheckUpdates});
+ this.miCheckUpdates,
+ this.miUtf8Charset});
this.barManager1.MainMenu = this.bar1;
- this.barManager1.MaxItemId = 99;
+ this.barManager1.MaxItemId = 100;
this.barManager1.ShowFullMenus = true;
//
// bar1
@@ -1608,7 +1610,8 @@
this.mnuCharset.ImageOptions.ImageIndex = ((int)(resources.GetObject("mnuCharset.ImageOptions.ImageIndex")));
this.mnuCharset.LinksPersistInfo.AddRange(new DevExpress.XtraBars.LinkPersistInfo[] {
new DevExpress.XtraBars.LinkPersistInfo(this.miCharsetForm),
- new DevExpress.XtraBars.LinkPersistInfo(this.miIsoCharSets, true)});
+ new DevExpress.XtraBars.LinkPersistInfo(this.miUtf8Charset, true),
+ new DevExpress.XtraBars.LinkPersistInfo(this.miIsoCharSets)});
this.mnuCharset.Name = "mnuCharset";
this.mnuCharset.PaintStyle = DevExpress.XtraBars.BarItemPaintStyle.CaptionInMenu;
//
@@ -1653,6 +1656,14 @@
this.miExplorerIntegration.Name = "miExplorerIntegration";
this.miExplorerIntegration.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miExplorerIntegration_ItemClick);
//
+ // miCheckUpdates
+ //
+ this.miCheckUpdates.ButtonStyle = DevExpress.XtraBars.BarButtonStyle.Check;
+ resources.ApplyResources(this.miCheckUpdates, "miCheckUpdates");
+ this.miCheckUpdates.Id = 98;
+ this.miCheckUpdates.Name = "miCheckUpdates";
+ this.miCheckUpdates.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miCheckUpdates_ItemClick);
+ //
// mnuAccessibility
//
resources.ApplyResources(this.mnuAccessibility, "mnuAccessibility");
@@ -2055,13 +2066,12 @@
this.popupFavList.Name = "popupFavList";
this.popupFavList.ShowCaption = true;
//
- // miCheckUpdates
+ // miUtf8Charset
//
- this.miCheckUpdates.ButtonStyle = DevExpress.XtraBars.BarButtonStyle.Check;
- resources.ApplyResources(this.miCheckUpdates, "miCheckUpdates");
- this.miCheckUpdates.Id = 98;
- this.miCheckUpdates.Name = "miCheckUpdates";
- this.miCheckUpdates.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.miCheckUpdates_ItemClick);
+ resources.ApplyResources(this.miUtf8Charset, "miUtf8Charset");
+ this.miUtf8Charset.Id = 99;
+ this.miUtf8Charset.Name = "miUtf8Charset";
+ this.miUtf8Charset.ItemClick += new DevExpress.XtraBars.ItemClickEventHandler(this.MiUtf8Charset_ItemClick);
//
// MainForm
//
@@ -2304,6 +2314,7 @@
private DevExpress.XtraGrid.Columns.GridColumn colPcrPid;
private DevExpress.XtraBars.BarButtonItem miExplorerIntegration;
private DevExpress.XtraBars.BarButtonItem miCheckUpdates;
+ private DevExpress.XtraBars.BarButtonItem miUtf8Charset;
}
}
diff --git a/source/ChanSort/MainForm.cs b/source/ChanSort/MainForm.cs
index 0bacc46..0f25985 100644
--- a/source/ChanSort/MainForm.cs
+++ b/source/ChanSort/MainForm.cs
@@ -412,6 +412,9 @@ namespace ChanSort.Ui
private void UpdateFavoritesEditor(Favorites favorites)
{
+ var miSet = new[] {this.miFavASet, this.miFavBSet, this.miFavCSet, this.miFavDSet, this.miFavESet};
+ var miUnset = new[] { this.miFavAUnset, this.miFavBUnset, this.miFavCUnset, this.miFavDUnset, this.miFavEUnset };
+
this.repositoryItemCheckedComboBoxEdit1.Items.Clear();
this.repositoryItemCheckedComboBoxEdit2.Items.Clear();
byte mask = 0x01;
@@ -424,9 +427,16 @@ namespace ChanSort.Ui
var c = (char) ('A' + bit);
this.repositoryItemCheckedComboBoxEdit1.Items.Add(c);
this.repositoryItemCheckedComboBoxEdit2.Items.Add(c);
+ miSet[bit].Visibility = BarItemVisibility.Always;
+ miUnset[bit].Visibility = BarItemVisibility.Always;
regex += c;
++favCount;
}
+ else
+ {
+ miSet[bit].Visibility = BarItemVisibility.Never;
+ miUnset[bit].Visibility = BarItemVisibility.Never;
+ }
}
regex += "]*";
this.repositoryItemCheckedComboBoxEdit1.Mask.EditMask = regex;
@@ -1257,8 +1267,8 @@ namespace ChanSort.Ui
this.cbCloseGap.Checked = Config.Default.CloseGaps;
this.ClearLeftFilter();
this.ClearRightFilter();
- foreach(var path in Config.Default.MruFiles)
- this.AddFileToMruList(path);
+ this.mruFiles.Clear();
+ this.mruFiles.AddRange(Config.Default.MruFiles);
this.UpdateMruMenu();
this.miExplorerIntegration.Down = Config.Default.ExplorerIntegration;
@@ -2872,6 +2882,11 @@ namespace ChanSort.Ui
#region Character set menu
+ private void MiUtf8Charset_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ TryExecute(() => this.SetDefaultEncoding(Encoding.UTF8));
+ }
+
private void miIsoCharSets_ListItemClick(object sender, ListItemClickEventArgs e)
{
TryExecute(() => this.SetDefaultEncoding(Encoding.GetEncoding(this.isoEncodings[e.Index])));
diff --git a/source/ChanSort/MainForm.resx b/source/ChanSort/MainForm.resx
index 5e3d81a..d74b6bc 100644
--- a/source/ChanSort/MainForm.resx
+++ b/source/ChanSort/MainForm.resx
@@ -647,6 +647,9 @@
9
+
+ UTF-8 (Unicode)
+
ISO character sets
@@ -1255,7 +1258,7 @@
globalImageCollection1
- ChanSort.Ui.GlobalImageCollection, ChanSort, Version=1.0.7134.23381, Culture=neutral, PublicKeyToken=null
+ ChanSort.Ui.GlobalImageCollection, ChanSort, Version=1.0.7156.40088, Culture=neutral, PublicKeyToken=null
gviewRight
@@ -1809,6 +1812,12 @@
DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a
+
+ miCheckUpdates
+
+
+ DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a
+
mnuAccessibility
@@ -1971,10 +1980,10 @@
DevExpress.XtraBars.PopupMenu, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a
-
- miCheckUpdates
+
+ miUtf8Charset
-
+
DevExpress.XtraBars.BarButtonItem, DevExpress.XtraBars.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a
@@ -1984,7 +1993,7 @@
DevExpress.XtraEditors.XtraForm, DevExpress.Utils.v19.1, Version=19.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a
- 07/14/2019 13:16:32
+ 08/05/2019 22:23:17
16, 16
diff --git a/source/changelog.md b/source/changelog.md
index dd4b574..fdc7689 100644
--- a/source/changelog.md
+++ b/source/changelog.md
@@ -1,6 +1,13 @@
ChanSort Change Log
===================
+2019-08-05
+- added partial support for Philips .xml channel lists
+ (There are MANY different file formats, only a few are currently supported)
+- fixed "most-recently-used" getting reversed every time the program was started
+- added "UTF-8 (Unicode)" character set to menu
+- fixed disappearing columns when loading different channel lists without restarting the application
+
2019-07-25
- fix: Application failed to save config and didn't exit when the folder %LOCALAPPDATA%\ChanSort doesn't exist