diff --git a/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.csproj b/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.csproj
index 1335b0e..74eaa91 100644
--- a/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.csproj
+++ b/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.csproj
@@ -101,6 +101,7 @@
+
@@ -111,6 +112,9 @@
+
+ Always
+
diff --git a/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.ini b/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.ini
new file mode 100644
index 0000000..1d0aa92
--- /dev/null
+++ b/source/ChanSort.Loader.Panasonic/ChanSort.Loader.Panasonic.ini
@@ -0,0 +1,3 @@
+[channel_list.xml]
+reorderRecordsByChannelNumber=true
+setIsModified=false
diff --git a/source/ChanSort.Loader.Panasonic/PanasonicPlugin.cs b/source/ChanSort.Loader.Panasonic/PanasonicPlugin.cs
index 50b1b02..6e99643 100644
--- a/source/ChanSort.Loader.Panasonic/PanasonicPlugin.cs
+++ b/source/ChanSort.Loader.Panasonic/PanasonicPlugin.cs
@@ -1,15 +1,30 @@
-using ChanSort.Api;
+using System.IO;
+using System.Text;
+using ChanSort.Api;
namespace ChanSort.Loader.Panasonic
{
public class PanasonicPlugin : ISerializerPlugin
{
public string DllName { get; set; }
- public string PluginName => "Panasonic (*.db, *.bin)";
- public string FileFilter => "*.db;*.bin";
+ public string PluginName => "Panasonic (*.db, *.bin, *.xml)";
+ public string FileFilter => "*.db;*.bin;*.xml";
public SerializerBase CreateSerializer(string inputFile)
{
+ if (Path.GetExtension(inputFile).ToLowerInvariant() == ".xml")
+ {
+ var data = File.ReadAllBytes(inputFile);
+ var header = Encoding.ASCII.GetBytes("\n channelLists = new();
+ public XmlDocument doc;
+ public readonly IniFile ini;
+
+ #region ctor()
+ public XmlSerializer(string inputFile) : base(inputFile)
+ {
+ this.Features.ChannelNameEdit = ChannelNameEditMode.None;
+ this.Features.CanSkipChannels = false;
+ this.Features.CanLockChannels = false;
+ this.Features.CanHideChannels = false;
+ this.Features.DeleteMode = DeleteMode.NotSupported;
+ this.Features.CanSaveAs = true;
+ this.Features.AllowGapsInFavNumbers = true;
+ this.Features.CanEditFavListNames = false;
+
+ string iniFile = Assembly.GetExecutingAssembly().Location.Replace(".dll", ".ini");
+ this.ini = new IniFile(iniFile);
+ }
+ #endregion
+
+ #region GetOrCreateList()
+ private ChannelList GetOrCreateList(string name)
+ {
+ if (this.channelLists.TryGetValue(name, out var list))
+ return list;
+
+ list = new ChannelList(SignalSource.All, name);
+ this.channelLists[name] = list;
+ var cols = list.VisibleColumnFieldNames;
+ cols.Clear();
+ cols.Add("Position");
+ cols.Add("OldPosition");
+ cols.Add(nameof(ChannelInfo.Name));
+ cols.Add(nameof(ChannelInfo.ShortName));
+ this.DataRoot.AddChannelList(list);
+ return list;
+ }
+ #endregion
+
+ #region Load()
+ public override void Load()
+ {
+ bool fail = false;
+ try
+ {
+ doc = new XmlDocument();
+ var content = File.ReadAllBytes(this.FileName);
+ var textContent = Encoding.UTF8.GetString(content);
+
+ 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 != "ChannelList" || !root.HasChildNodes || root.ChildNodes[0].LocalName != "ChannelInfo")
+ throw new FileLoadException("File is not a supported Panasonic XML file");
+
+ foreach (XmlNode child in root.ChildNodes)
+ {
+ switch (child.LocalName)
+ {
+ case "ChannelInfo":
+ this.ReadChannel(child);
+ break;
+ }
+ }
+ }
+ #endregion
+
+ #region ReadChannel()
+ private void ReadChannel(XmlNode node)
+ {
+ var channelType = node.Attributes?["ChannelType"]?.InnerText;
+ var list = this.GetOrCreateList(channelType);
+ var chan = new XmlChannel(list.Count, node);
+ DataRoot.AddChannel(list, chan);
+ }
+ #endregion
+
+
+ #region Save()
+
+ public override void Save(string tvOutputFile)
+ {
+ var sec = ini.GetSection("channel_list.xml");
+ var reorder = sec?.GetBool("reorderRecordsByChannelNumber", true) ?? true;
+ var setIsModified = sec?.GetBool("setIsModified", false) ?? false;
+
+ foreach (var list in this.DataRoot.ChannelLists)
+ {
+ var seq = reorder ? list.Channels.OrderBy(c => c.NewProgramNr).ThenBy(c => c.RecordIndex).ToList() : list.Channels;
+ XmlNode prevNode = null;
+ foreach (var chan in seq)
+ {
+ if (chan is not XmlChannel ch)
+ continue;
+ ch.Node.Attributes["ChannelNumber"].InnerText = ch.NewProgramNr.ToString();
+ if (setIsModified && ch.NewProgramNr != ch.OldProgramNr)
+ ch.Node.Attributes["IsModified"].InnerText = "1";
+ if (reorder)
+ {
+ var parent = ch.Node.ParentNode;
+ parent.RemoveChild(ch.Node);
+ parent.InsertAfter(ch.Node, prevNode);
+ prevNode = ch.Node;
+ }
+ }
+ }
+
+ var xmlSettings = new XmlWriterSettings();
+ xmlSettings.Encoding = new UTF8Encoding(false);
+ xmlSettings.CheckCharacters = false;
+ xmlSettings.Indent = true;
+ xmlSettings.IndentChars = "";
+ xmlSettings.NewLineHandling = NewLineHandling.None;
+ xmlSettings.NewLineChars = "\n";
+ xmlSettings.OmitXmlDeclaration = true;
+ using var w = XmlWriter.Create(tvOutputFile, xmlSettings);
+ doc.WriteTo(w);
+ this.FileName = tvOutputFile;
+ }
+
+ #endregion
+
+
+ #region class XmlChannel
+
+ class XmlChannel : ChannelInfo
+ {
+ internal XmlNode Node;
+
+ public XmlChannel(int index, XmlNode node) : base(0, index, 0, null)
+ {
+ this.Node = node;
+
+ this.OldProgramNr = int.Parse(node.Attributes["ChannelNumber"]?.InnerText);
+ this.Name = node.Attributes["ChannelName"].InnerText;
+ var svlId = node.Attributes["SvlId"].InnerText;
+ this.ShortName = $"SvlId: {svlId}";
+ }
+
+ }
+ #endregion
+ }
+}
diff --git a/source/ChanSort/ReferenceListForm.cs b/source/ChanSort/ReferenceListForm.cs
index bf30711..ec30950 100644
--- a/source/ChanSort/ReferenceListForm.cs
+++ b/source/ChanSort/ReferenceListForm.cs
@@ -48,7 +48,7 @@ namespace ChanSort.Ui
if (disposing)
{
this.components?.Dispose();
- this.serializer.Dispose();
+ this.serializer?.Dispose();
}
base.Dispose(disposing);
}
diff --git a/source/changelog.md b/source/changelog.md
index b00962c..e084ef0 100644
--- a/source/changelog.md
+++ b/source/changelog.md
@@ -7,8 +7,8 @@ ChanSort Change Log
- Philips: improved experimental support for Philips FLASH\_\*/\*.db file formats
(read-only by default, can be enabled in Philips.ini for testing)
- added Polish readme and updated translation (by JakubDriver)
-
-2021-09-13
+- Panasonic: added exerimental support for channel\_list.xml lists (Panasonic Android TVs, 2019 and later)
+ Unfortunately the only information included in this format is the channel number and a truncated channel name.
- column order is now preserved between program starts even when lists with different supported columns
were loaded and columns reordered.
- added option to enabled/disable auto-loading of the last opened list when starting the program