2014-05-29 19:36:29 +02:00
using System ;
2015-01-14 21:38:01 +01:00
using System.Collections.Generic ;
2014-05-29 19:36:29 +02:00
using System.IO ;
2014-05-25 16:13:15 +02:00
using System.Text ;
using System.Xml ;
using ChanSort.Api ;
2021-01-23 14:22:18 +01:00
using ChanSort.Loader.LG ;
2014-05-25 16:13:15 +02:00
namespace ChanSort.Loader.GlobalClone
{
2020-05-03 12:12:09 +02:00
class GcXmlSerializer : SerializerBase
2014-05-25 16:13:15 +02:00
{
2019-08-29 16:57:20 +02:00
private readonly ChannelList atvChannels = new ChannelList ( SignalSource . AnalogCT , "Analog" ) ;
private readonly ChannelList dtvTvChannels = new ChannelList ( SignalSource . DvbCT | SignalSource . TvAndData , "DTV" ) ;
2014-05-25 16:13:15 +02:00
private readonly ChannelList dtvRadioChannels = new ChannelList ( SignalSource . DvbCT | SignalSource . Radio , "Radio" ) ;
2019-08-29 16:57:20 +02:00
private readonly ChannelList satTvChannels = new ChannelList ( SignalSource . DvbS | SignalSource . TvAndData , "Sat-TV" ) ;
2014-05-25 16:13:15 +02:00
private readonly ChannelList satRadioChannels = new ChannelList ( SignalSource . DvbS | SignalSource . Radio , "Sat-Radio" ) ;
private XmlDocument doc ;
2014-05-29 19:36:29 +02:00
private readonly DvbStringDecoder dvbStringDecoder = new DvbStringDecoder ( Encoding . Default ) ;
2015-01-14 21:38:01 +01:00
private string modelName ;
private readonly Dictionary < int , string > satPositionByIndex = new Dictionary < int , string > ( ) ;
2015-09-19 23:24:31 +02:00
private bool usesBinaryDataInUtf8Envelope = false ;
2014-05-25 16:13:15 +02:00
#region ctor ( )
2020-05-03 12:12:09 +02:00
public GcXmlSerializer ( string inputFile ) : base ( inputFile )
2014-05-25 16:13:15 +02:00
{
2015-06-13 18:37:59 +02:00
this . Features . ChannelNameEdit = ChannelNameEditMode . All ;
2019-11-24 20:00:48 +01:00
this . Features . DeleteMode = DeleteMode . FlagWithoutPrNr ;
2019-11-08 02:31:44 +01:00
this . Features . CanHaveGaps = true ;
2019-11-24 20:00:48 +01:00
this . Features . CanSkipChannels = true ;
this . Features . CanLockChannels = true ;
this . Features . CanHideChannels = true ;
2023-12-18 17:03:05 +01:00
this . Features . FavoritesMode = FavoritesMode . Flags ; // will be overwritten when a <favoriteIdx> element is found
2014-05-25 16:13:15 +02:00
this . DataRoot . AddChannelList ( this . atvChannels ) ;
this . DataRoot . AddChannelList ( this . dtvTvChannels ) ;
this . DataRoot . AddChannelList ( this . dtvRadioChannels ) ;
this . DataRoot . AddChannelList ( this . satTvChannels ) ;
this . DataRoot . AddChannelList ( this . satRadioChannels ) ;
}
#endregion
#region Load ( )
2014-05-29 19:36:29 +02:00
2014-05-25 16:13:15 +02:00
public override void Load ( )
{
bool fail = false ;
try
{
this . doc = new XmlDocument ( ) ;
2015-09-19 23:24:31 +02:00
string textContent = File . ReadAllText ( this . FileName , Encoding . UTF8 ) ;
2019-11-11 16:06:11 +01:00
if ( textContent [ 0 ] ! = '<' )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "Invalid GlobalClone/XML file format. Maybe a binary xx*.TLL file?" ) ;
2015-09-19 23:24:31 +02:00
textContent = ReplaceInvalidXmlCharacters ( textContent ) ;
var settings = new XmlReaderSettings { CheckCharacters = false } ;
2022-11-29 22:00:16 +01:00
using var reader = XmlReader . Create ( new StringReader ( textContent ) , settings ) ;
doc . Load ( reader ) ;
2014-05-25 16:13:15 +02:00
}
catch
{
fail = true ;
}
var root = doc . FirstChild ;
if ( root is XmlDeclaration )
root = root . NextSibling ;
if ( fail | | root = = null | | root . LocalName ! = "TLLDATA" )
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "\"" + this . FileName + "\" is not a supported GlobalClone XML file" ) ;
2014-05-25 16:13:15 +02:00
foreach ( XmlNode child in root . ChildNodes )
{
switch ( child . LocalName )
{
2014-05-29 19:36:29 +02:00
case "ModelInfo" :
this . ReadModelInfo ( child ) ;
break ;
2015-01-14 21:38:01 +01:00
case "SatelliteDB" :
this . ReadSatelliteDB ( child ) ;
break ;
2014-05-25 16:13:15 +02:00
case "CHANNEL" :
this . ReadChannelLists ( child ) ;
break ;
}
}
2015-09-19 23:24:31 +02:00
this . Features . ChannelNameEdit = usesBinaryDataInUtf8Envelope ? ChannelNameEditMode . Analog : ChannelNameEditMode . All ;
2014-05-25 16:13:15 +02:00
}
#endregion
2014-05-29 19:36:29 +02:00
#region ReadModelInfo ( )
private void ReadModelInfo ( XmlNode modelInfoNode )
{
2015-09-19 23:24:31 +02:00
// show warning about broken import function in early webOS firmware
2014-05-29 19:36:29 +02:00
var regex = new System . Text . RegularExpressions . Regex ( @"\d{2}([A-Z]{2})(\d{2})\d[0-9A-Z].*" ) ;
2015-09-19 23:24:31 +02:00
var series = "" ;
2014-05-29 19:36:29 +02:00
foreach ( XmlNode child in modelInfoNode . ChildNodes )
{
switch ( child . LocalName )
{
case "ModelName" :
2015-01-14 21:38:01 +01:00
this . modelName = child . InnerText ;
var match = regex . Match ( this . modelName ) ;
2014-05-29 19:36:29 +02:00
if ( match . Success )
{
2015-09-19 23:24:31 +02:00
series = match . Groups [ 1 ] . Value ;
2015-01-14 21:38:01 +01:00
if ( ( series = = "LB" | | series = = "UB" ) & & StringComparer . InvariantCulture . Compare ( match . Groups [ 2 ] . Value , "60" ) > = 0 )
2021-07-24 18:59:03 +02:00
Api . View . Default . MessageBox ( Resource . GcSerializer_webOsFirmwareWarning , "LG GlobalClone" , View . MessageBoxButtons . OK , View . MessageBoxIcon . Information ) ;
2014-05-29 19:36:29 +02:00
}
break ;
}
}
2015-09-19 23:24:31 +02:00
// ask whether binary TLL file should be deleted
var dir = Path . GetDirectoryName ( this . FileName ) ? ? "." ;
var binTlls = Directory . GetFiles ( dir , "xx" + series + "*.tll" ) ;
2021-08-31 22:13:28 +02:00
if ( binTlls . Length > 0 & & ! ( binTlls . Length = = 1 & & Path . GetFileName ( binTlls [ 0 ] ) . ToLowerInvariant ( ) = = Path . GetFileName ( this . FileName ) . ToLowerInvariant ( ) ) )
2015-09-19 23:24:31 +02:00
{
2021-01-23 14:22:18 +01:00
var txt = Resource . GcSerializer_ReadModelInfo_ModelWarning ;
2021-07-24 18:59:03 +02:00
if ( Api . View . Default ! = null & & Api . View . Default . MessageBox ( txt , "LG GlobalClone" , View . MessageBoxButtons . YesNo , View . MessageBoxIcon . Information ) = = View . DialogResult . Yes )
2015-09-19 23:24:31 +02:00
{
foreach ( var file in binTlls )
File . Move ( file , file + "_bak" ) ;
}
}
2014-05-29 19:36:29 +02:00
}
#endregion
2015-01-14 21:38:01 +01:00
#region ReadSatelliteDB ( )
private void ReadSatelliteDB ( XmlNode node )
{
foreach ( XmlNode child in node . ChildNodes )
{
switch ( child . LocalName )
{
case "SATDBInfo" :
this . ReadSatDbInfo ( child ) ;
break ;
}
}
}
private void ReadSatDbInfo ( XmlNode node )
{
foreach ( XmlNode child in node . ChildNodes )
{
switch ( child . LocalName )
{
case "SatRecordInfo" :
int i = 0 ;
foreach ( XmlNode satNode in child . ChildNodes )
this . ReadSatRecordInfo ( i + + , satNode ) ;
break ;
}
}
}
private void ReadSatRecordInfo ( int i , XmlNode satRecordInfoNode )
{
string orbitalPos = "" ;
foreach ( XmlNode child in satRecordInfoNode . ChildNodes )
{
switch ( child . LocalName )
{
case "Angle" :
orbitalPos + = child . InnerText ;
break ;
case "AnglePrec" :
orbitalPos + = "." + child . InnerText ;
break ;
case "DirEastWest" :
orbitalPos + = child . InnerText = = "0" ? "W" : "E" ;
break ;
}
}
this . satPositionByIndex [ i ] = orbitalPos ;
}
#endregion
2014-05-25 16:13:15 +02:00
#region ReadChannelLists ( )
private void ReadChannelLists ( XmlNode channelNode )
{
foreach ( XmlNode chanListNode in channelNode . ChildNodes )
{
switch ( chanListNode . LocalName )
{
case "ATV" :
this . ReadChannelList ( chanListNode , true ) ;
break ;
case "DTV" :
this . ReadChannelList ( chanListNode , false ) ;
break ;
2015-01-14 21:38:01 +01:00
case "DTVATV" :
// TODO: US DTV_ATSC files contain such lists
break ;
2014-05-25 16:13:15 +02:00
}
}
2020-05-06 22:07:48 +02:00
// when the user selects a predefined "provider" during the TV's setup, an empty list will be exported and can't be edited
int total = 0 ;
foreach ( var list in this . DataRoot . ChannelLists )
total + = list . Channels . Count ;
if ( total = = 0 )
{
2021-01-23 14:22:18 +01:00
Api . View . Default . MessageBox ( Resource . GcSerializer_ReadChannelLists_NoChannelsMsg , Resource . GcSerializer_ReadChannelLists_NoChannelsCap ,
2021-07-24 18:59:03 +02:00
View . MessageBoxButtons . OK , View . MessageBoxIcon . Exclamation ) ;
2020-05-06 22:07:48 +02:00
}
2014-05-25 16:13:15 +02:00
}
#endregion
#region ReadChannelList ( )
private void ReadChannelList ( XmlNode node , bool analog )
{
int i = - 1 ;
foreach ( XmlNode itemNode in node . ChildNodes )
{
if ( itemNode . LocalName ! = "ITEM" )
continue ;
+ + i ;
2023-06-03 10:38:11 +02:00
var ch = new GcChannel < XmlNode > ( analog ? SignalSource . AnalogCT | SignalSource . Tv : SignalSource . Dvb , i , itemNode ) ;
2014-05-29 19:36:29 +02:00
this . ParseChannelInfoNodes ( itemNode , ch ) ;
2014-05-25 16:13:15 +02:00
var list = this . DataRoot . GetChannelList ( ch . SignalSource ) ;
this . DataRoot . AddChannel ( list , ch ) ;
}
}
#endregion
#region ParseChannelInfoNode ( )
2020-05-03 18:07:28 +02:00
private void ParseChannelInfoNodes ( XmlNode itemNode , GcChannel < XmlNode > ch , bool onlyNames = false )
2014-05-25 16:13:15 +02:00
{
2014-05-29 19:36:29 +02:00
bool hasHexName = false ;
int mapType = 0 ;
foreach ( XmlNode info in itemNode . ChildNodes )
2014-05-25 16:13:15 +02:00
{
2014-05-29 19:36:29 +02:00
if ( onlyNames & & info . LocalName ! = "vchName" & & info . LocalName ! = "hexVchName" )
continue ;
switch ( info . LocalName )
{
// common to ATV and DTV
case "prNum" :
2017-11-30 14:50:22 +01:00
ch . OldProgramNr = int . Parse ( info . InnerText ) ;
if ( ch . OldProgramNr ! = - 1 ) // older versions of ChanSort accidentally saved -1 instead of IsDeleted=1
ch . OldProgramNr & = 0x3FFF ;
2014-05-29 19:36:29 +02:00
break ;
case "vchName" :
// In old file format versions, this field contains binary data stuffed into UTF8 envelopes. that data is correct
// In newer file formats, this field contains plain text but fails to hold localized characters. The hexVchName field, if present, contains the correct data then.
if ( ! hasHexName )
ch . Name = ParseName ( info . InnerText ) ;
break ;
case "sourceIndex" :
var source = int . Parse ( info . InnerText ) ;
if ( source = = 2 )
ch . SignalSource | = SignalSource . Cable ;
else if ( source = = 7 )
ch . SignalSource | = SignalSource . Sat ;
else
ch . SignalSource | = SignalSource . Antenna ;
break ;
case "mapType" :
mapType = int . Parse ( info . InnerText ) ;
break ;
case "mapAttr" :
if ( mapType = = 1 )
ch . Favorites = ( Favorites ) int . Parse ( info . InnerText ) ;
break ;
case "isBlocked" :
ch . Lock = int . Parse ( info . InnerText ) = = 1 ;
break ;
case "isSkipped" :
ch . Skip = int . Parse ( info . InnerText ) = = 1 ;
break ;
// ATV
case "pllData" :
ch . FreqInMhz = ( decimal ) int . Parse ( info . InnerText ) / 20 ;
break ;
// DTV
case "original_network_id" :
ch . OriginalNetworkId = int . Parse ( info . InnerText ) ;
break ;
case "transport_id" :
ch . TransportStreamId = int . Parse ( info . InnerText ) ;
break ;
2019-11-24 20:00:48 +01:00
case "service_id" : // also same value in "programNo"
2014-05-29 19:36:29 +02:00
ch . ServiceId = int . Parse ( info . InnerText ) ;
break ;
case "serviceType" :
ch . ServiceType = int . Parse ( info . InnerText ) ;
2019-11-10 20:09:27 +01:00
ch . SignalSource | = LookupData . Instance . IsRadioTvOrData ( ch . ServiceType ) ;
2014-05-29 19:36:29 +02:00
break ;
case "frequency" :
ch . FreqInMhz = int . Parse ( info . InnerText ) ;
if ( ( ch . SignalSource & SignalSource . Sat ) = = 0 )
ch . FreqInMhz / = 1000 ;
break ;
case "isInvisable" : // that spelling error is part of the XML
ch . Hidden = int . Parse ( info . InnerText ) = = 1 ;
break ;
2015-01-14 21:38:01 +01:00
case "isNumUnSel" :
// ?
break ;
2014-05-29 19:36:29 +02:00
case "isDisabled" :
2017-11-30 14:50:22 +01:00
ch . IsDisabled = int . Parse ( info . InnerText ) ! = 0 ;
break ;
case "isDeleted" :
2014-05-29 19:36:29 +02:00
ch . IsDeleted = int . Parse ( info . InnerText ) ! = 0 ;
break ;
2015-01-14 21:38:01 +01:00
case "usSatelliteHandle" :
int satIndex = int . Parse ( info . InnerText ) ;
string satPos = this . satPositionByIndex . TryGet ( satIndex ) ;
ch . SatPosition = satPos ? ? satIndex . ToString ( ) ; // fallback to ensure unique UIDs
ch . Satellite = satPos ;
break ;
2014-05-29 19:36:29 +02:00
// not present in all XML files. if present, the <vchName> might be empty or corrupted
case "hexVchName" :
var bytes = Tools . HexDecode ( info . InnerText ) ;
string longName , shortName ;
dvbStringDecoder . GetChannelNames ( bytes , 0 , bytes . Length , out longName , out shortName ) ;
ch . Name = longName ;
ch . ShortName = shortName ;
hasHexName = true ;
break ;
2019-08-13 13:29:59 +02:00
default :
if ( info . LocalName . StartsWith ( "favoriteIdx" ) )
{
int n = info . LocalName [ 11 ] - 'A' ;
var mask = 1 < < n ;
2021-03-14 22:13:22 +01:00
this . Features . FavoritesMode = FavoritesMode . OrderedPerSource ;
2023-12-18 17:03:05 +01:00
this . Features . MaxFavoriteLists = Math . Max ( this . Features . MaxFavoriteLists , n + 1 ) ;
2019-08-13 13:29:59 +02:00
if ( ( ( int ) ch . Favorites & mask ) ! = 0 ) // xml element holds bad index data (250) when fav is not set
2019-11-24 20:00:48 +01:00
ch . SetOldPosition ( n + 1 , int . Parse ( info . InnerText ) ) ;
2019-08-13 13:29:59 +02:00
}
break ;
2014-05-29 19:36:29 +02:00
}
2014-05-25 16:13:15 +02:00
}
}
#endregion
2014-05-29 19:36:29 +02:00
#region ParseName ( )
2014-05-25 16:13:15 +02:00
private string ParseName ( string input )
{
2014-05-29 19:36:29 +02:00
var bytes = Encoding . UTF8 . GetBytes ( input ) ;
if ( bytes . Length = = 0 | | bytes [ 0 ] < 0xC0 )
return input ;
2015-09-19 23:24:31 +02:00
this . usesBinaryDataInUtf8Envelope = true ;
2014-05-29 19:36:29 +02:00
// older GlobalClone files look like as if the <vchName> is Chinese, but it's a weired "binary inside UTF8 envelope" encoding:
// A 3 byte UTF-8 envelope is used to encode 2 input bytes: 1110aaaa 10bbbbcc 10ccdddd represents the 16bit little endian integer aaaabbbbccccdddd, which represents bytes ccccdddd, aaaabbbb
// If a remaining byte is >= 0x80, it is encoded in a 2 byte UTF-8 envelope: 110000aa 10aabbbb represents the byte aaaabbbb
2015-09-19 23:24:31 +02:00
// If a remaining byte is < 0x80, it is encoded directly into a 1 byte UTF-8 char. (This can cause invalid XML files for values < 0x20.)
2022-11-29 22:00:16 +01:00
using MemoryStream ms = new MemoryStream ( 40 ) ;
for ( int i = 0 , c = bytes . Length ; i < c ; i + + )
2014-05-29 19:36:29 +02:00
{
2022-11-29 22:00:16 +01:00
int b0 = bytes [ i + 0 ] ;
if ( b0 > = 0xE0 ) // 3-byte UTF envelope for 2 input bytes
2014-05-29 19:36:29 +02:00
{
2022-11-29 22:00:16 +01:00
int b1 = bytes [ i + 1 ] ;
int b2 = bytes [ i + 2 ] ;
int ch1 = ( ( b1 & 0x03 ) < < 6 ) | ( b2 & 0x3F ) ;
int ch2 = ( ( b0 & 0x0F ) < < 4 ) | ( ( b1 & 0x3C ) > > 2 ) ;
ms . WriteByte ( ( byte ) ch1 ) ;
ms . WriteByte ( ( byte ) ch2 ) ;
i + = 2 ;
2014-05-29 19:36:29 +02:00
}
2022-11-29 22:00:16 +01:00
else if ( b0 > = 0xC0 ) // 2-byte UTF envelope for 1 input byte >= 0x80
{
int b1 = bytes [ i + 1 ] ;
int ch = ( ( b0 & 0x03 ) < < 6 ) | ( b1 & 0x3F ) ;
ms . WriteByte ( ( byte ) ch ) ;
i + + ;
}
else if ( b0 < 0x80 ) // 1-byte UTF envelope for 1 input byte < 0x80
ms . WriteByte ( bytes [ i ] ) ;
}
2014-05-29 19:36:29 +02:00
2022-11-29 22:00:16 +01:00
string longName , shortName ;
this . dvbStringDecoder . GetChannelNames ( ms . GetBuffer ( ) , 0 , ( int ) ms . Length , out longName , out shortName ) ;
return longName ;
2014-05-25 16:13:15 +02:00
}
2014-05-29 19:36:29 +02:00
#endregion
2014-05-25 16:13:15 +02:00
#region Save ( )
2022-11-29 22:00:16 +01:00
public override void Save ( )
2014-05-25 16:13:15 +02:00
{
foreach ( var list in this . DataRoot . ChannelLists )
{
foreach ( var channel in list . Channels )
{
2020-05-03 18:07:28 +02:00
var ch = channel as GcChannel < XmlNode > ;
2014-05-25 16:13:15 +02:00
if ( ch = = null ) continue ; // ignore proxy channels from reference lists
2015-09-19 23:24:31 +02:00
var nameBytes = Encoding . UTF8 . GetBytes ( ch . Name ) ;
bool nameNeedsEncoding = nameBytes . Length ! = ch . Name . Length ;
2015-11-26 15:56:52 +01:00
string mapType = "" ;
2015-09-19 23:24:31 +02:00
2020-05-03 18:07:28 +02:00
foreach ( XmlNode node in ch . Node . ChildNodes )
2014-05-25 16:13:15 +02:00
{
switch ( node . LocalName )
{
case "prNum" :
var nr = ch . NewProgramNr ;
if ( ( ch . SignalSource & SignalSource . Radio ) ! = 0 )
nr | = 0x4000 ;
node . InnerText = nr . ToString ( ) ;
break ;
2015-09-19 23:24:31 +02:00
case "hexVchName" :
if ( channel . IsNameModified )
node . InnerText = ( nameNeedsEncoding ? "15" : "" ) + Tools . HexEncode ( nameBytes ) ; // 0x15 = DVB encoding indicator for UTF-8
break ;
case "notConvertedLengthOfVchName" :
if ( channel . IsNameModified )
node . InnerText = ( ( nameNeedsEncoding ? 1 : 0 ) + ch . Name . Length ) . ToString ( ) ;
break ;
case "vchName" :
if ( channel . IsNameModified )
2016-08-10 22:27:52 +02:00
node . InnerText = nameNeedsEncoding ? " " : ch . Name ;
if ( node . InnerText = = "" ) // XmlTextReader removed the required space from empty channel names
node . InnerText = " " ;
2015-09-19 23:24:31 +02:00
break ;
2015-01-14 21:38:01 +01:00
case "isInvisable" :
node . InnerText = ch . Hidden ? "1" : "0" ;
break ;
case "isBlocked" :
node . InnerText = ch . Lock ? "1" : "0" ;
break ;
case "isSkipped" :
node . InnerText = ch . Skip ? "1" : "0" ;
break ;
case "isNumUnSel" :
// ?
break ;
2014-05-25 16:13:15 +02:00
case "isDisabled" :
2019-11-24 20:00:48 +01:00
node . InnerText = ch . IsDisabled /* || ch.IsDeleted */ ? "1" : "0" ;
2017-11-30 14:50:22 +01:00
break ;
2015-01-14 21:38:01 +01:00
case "isDeleted" :
2014-05-25 16:13:15 +02:00
node . InnerText = ch . IsDeleted ? "1" : "0" ;
break ;
case "isUserSelCHNo" :
2016-08-10 22:27:52 +02:00
if ( ch . NewProgramNr ! = ch . OldProgramNr )
2019-11-24 20:00:48 +01:00
node . InnerText = ch . IsDeleted ? "0" : "1" ;
2014-05-25 16:13:15 +02:00
break ;
2015-11-26 15:56:52 +01:00
case "mapType" :
mapType = node . InnerText ;
2019-11-24 20:00:48 +01:00
if ( int . TryParse ( mapType , out int value ) )
{
if ( ch . IsDeleted )
value | = 0x02 ; // all channels that have isDeleted=1 had mapType=0x03, all other channels had mapType=0x01
else
value & = ~ 0x02 ;
node . InnerText = value . ToString ( ) ;
}
2015-11-26 15:56:52 +01:00
break ;
case "mapAttr" :
if ( mapType = = "1" )
node . InnerText = ( ( int ) ch . Favorites ) . ToString ( ) ;
break ;
2019-08-13 13:29:59 +02:00
default :
if ( node . LocalName . StartsWith ( "favoriteIdx" ) )
{
int n = node . LocalName [ 11 ] - 'A' ;
var idx = ch . GetPosition ( n + 1 ) ;
if ( idx < = 0 )
idx = 250 ; // this weird value is used by the TV when the fav is not set
node . InnerText = idx . ToString ( ) ;
}
break ;
2014-05-25 16:13:15 +02:00
}
}
}
}
2014-05-30 00:24:29 +02:00
// by default .NET reformats the whole XML. These settings produce the same format as the TV xml files use
var settings = new XmlWriterSettings ( ) ;
settings . Encoding = new UTF8Encoding ( false ) ;
settings . Indent = true ;
settings . NewLineChars = "\r\n" ;
settings . NewLineHandling = NewLineHandling . Replace ;
settings . OmitXmlDeclaration = true ;
settings . IndentChars = "" ;
2015-09-19 23:24:31 +02:00
settings . CheckCharacters = false ;
2022-11-29 22:00:16 +01:00
using StringWriter sw = new StringWriter ( ) ;
using XmlWriter xw = XmlWriter . Create ( sw , settings ) ;
doc . Save ( xw ) ;
xw . Flush ( ) ;
string xml = RestoreInvalidXmlCharacters ( sw . ToString ( ) ) ;
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n" + xml ;
xml = xml . Replace ( "<ATV></ATV>\r\n" , "<ATV>\r\n</ATV>\r\n" ) ;
xml = xml . Replace ( "<DTV></DTV>\r\n" , "<DTV>\r\n</DTV>\r\n" ) ;
xml = xml . Replace ( "<hexAszTkgsMessage type=\"0\"></hexAszTkgsMessage>" , "<hexAszTkgsMessage type=\"0\"> </hexAszTkgsMessage>" ) ;
xml = xml . Replace ( "<aszTkgsMessage type=\"0\"></aszTkgsMessage>" , "<aszTkgsMessage type=\"0\"> </aszTkgsMessage>" ) ;
2019-11-24 20:00:48 +01:00
2022-11-29 22:00:16 +01:00
if ( ! xml . EndsWith ( "\r\n" ) )
xml + = "\r\n" ;
File . WriteAllText ( this . FileName , xml , settings . Encoding ) ;
2014-05-25 16:13:15 +02:00
}
#endregion
2014-05-29 19:36:29 +02:00
#region DefaultEncoding
public override Encoding DefaultEncoding
{
get { return base . DefaultEncoding ; }
set
{
if ( ReferenceEquals ( value , this . DefaultEncoding ) )
return ;
base . DefaultEncoding = value ;
this . dvbStringDecoder . DefaultEncoding = value ;
this . ChangeEncoding ( ) ;
}
}
#endregion
#region ChangeEncoding ( )
private void ChangeEncoding ( )
{
foreach ( var list in this . DataRoot . ChannelLists )
{
2017-11-30 14:50:22 +01:00
if ( list . IsMixedSourceFavoritesList )
continue ;
2014-05-29 19:36:29 +02:00
foreach ( var channel in list . Channels )
{
2020-05-03 18:07:28 +02:00
var gcChannel = channel as GcChannel < XmlNode > ;
2014-05-29 19:36:29 +02:00
if ( gcChannel ! = null )
2020-05-03 18:07:28 +02:00
this . ParseChannelInfoNodes ( gcChannel . Node , gcChannel , true ) ;
2014-05-29 19:36:29 +02:00
}
}
}
#endregion
2015-09-19 23:24:31 +02:00
#region ReplaceInvalidXmlCharacters ( )
private string ReplaceInvalidXmlCharacters ( string input )
{
StringBuilder output = new StringBuilder ( ) ;
foreach ( var c in input )
{
if ( c > = ' ' | | c = = '\r' | | c = = '\n' | | c = = '\t' )
output . Append ( c ) ;
else
output . AppendFormat ( "&#x{0:d}{1:d};" , c > > 4 , c & 0x0F ) ;
}
return output . ToString ( ) ;
}
#endregion
#region RestoreInvalidXmlCharacters ( )
private string RestoreInvalidXmlCharacters ( string input )
{
StringBuilder output = new StringBuilder ( ) ;
int prevIdx = 0 ;
while ( true )
{
int nextIdx = input . IndexOf ( "&#" , prevIdx ) ;
if ( nextIdx < 0 )
break ;
output . Append ( input , prevIdx , nextIdx - prevIdx ) ;
int numBase = 10 ;
char inChar ;
int outChar = 0 ;
for ( nextIdx + = 2 ; ( inChar = input [ nextIdx ] ) ! = ';' ; nextIdx + + )
{
if ( inChar = = 'x' | | inChar = = 'X' )
numBase = 16 ;
else
outChar = outChar * numBase + HexNibble ( inChar ) ;
}
var binChar = ( char ) outChar ;
output . Append ( binChar ) ;
prevIdx = nextIdx + 1 ;
}
output . Append ( input , prevIdx , input . Length - prevIdx ) ;
return output . ToString ( ) ;
}
private int HexNibble ( char hexDigit )
{
2021-08-31 22:13:28 +02:00
return hexDigit > = '0' & & hexDigit < = '9' ? hexDigit - '0' : ( Char . ToUpperInvariant ( hexDigit ) - 'A' ) + 10 ;
2015-09-19 23:24:31 +02:00
}
#endregion
2014-05-25 16:13:15 +02:00
}
}