/* * Copyright (C) 2007-2024 C# Port * Original Copyright (C) 2007-2018 Siemens AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ using V2GDecoderNet.EXI; using System.Text; using System.Xml; namespace V2GDecoderNet.V2G { /// /// EXI Decoder for converting EXI binary data to XML /// public class EXIDecoder { private readonly EXIConfig _config; public EXIDecoder(EXIConfig? config = null) { _config = config ?? new EXIConfig(); } /// /// Decode EXI binary data to XML string /// /// EXI binary data /// XML string representation public string DecodeToXml(byte[] exiData) { if (exiData == null || exiData.Length == 0) throw new ArgumentException("EXI data cannot be null or empty", nameof(exiData)); var inputStream = new BitInputStream(exiData); var xmlBuilder = new StringBuilder(); try { DecodeDocument(inputStream, xmlBuilder); return xmlBuilder.ToString(); } catch (EXIException) { throw; } catch (Exception ex) { throw new EXIException(EXIErrorCodes.EXI_ERROR_UNKOWN_EVENT, "Error during EXI decoding", ex); } } /// /// Decode EXI binary data to XmlDocument /// /// EXI binary data /// XmlDocument public XmlDocument DecodeToXmlDocument(byte[] exiData) { string xmlString = DecodeToXml(exiData); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlString); return xmlDoc; } /// /// Validate EXI header and extract options /// /// Input bit stream /// EXI header information public EXIHeader DecodeHeader(BitInputStream inputStream) { var header = new EXIHeader(); // Check for EXI cookie ($EXI) byte[] cookie = inputStream.ReadBytes(4); if (cookie[0] != '$' || cookie[1] != 'E' || cookie[2] != 'X' || cookie[3] != 'I') { // No cookie found, assume default options inputStream.SetPosition(0); header.HasCookie = false; return header; } header.HasCookie = true; // Read format version header.FormatVersion = inputStream.ReadBits(4); // Read options presence flag bool hasOptions = inputStream.ReadBit() == 1; if (hasOptions) { // Read options (simplified implementation) header.PreserveComments = inputStream.ReadBit() == 1; header.PreservePIs = inputStream.ReadBit() == 1; header.PreserveDTD = inputStream.ReadBit() == 1; header.PreservePrefixes = inputStream.ReadBit() == 1; // Skip remaining option bits for now inputStream.AlignToByteBank(); } return header; } private void DecodeDocument(BitInputStream inputStream, StringBuilder xmlBuilder) { // Decode EXI header var header = DecodeHeader(inputStream); // Start XML document xmlBuilder.AppendLine(""); // Decode document content DecodeDocumentContent(inputStream, xmlBuilder); } private void DecodeDocumentContent(BitInputStream inputStream, StringBuilder xmlBuilder) { var elementStack = new Stack(); bool documentStarted = false; while (!inputStream.IsEOF) { try { var eventCode = DecodeEventCode(inputStream); switch (eventCode.Event) { case EXIEvent.START_DOCUMENT: documentStarted = true; break; case EXIEvent.END_DOCUMENT: return; case EXIEvent.START_ELEMENT: case EXIEvent.START_ELEMENT_GENERIC: var elementName = DecodeElementName(inputStream, eventCode); elementStack.Push(elementName); xmlBuilder.Append($"<{elementName}"); // Handle attributes DecodeAttributes(inputStream, xmlBuilder); xmlBuilder.AppendLine(">"); break; case EXIEvent.END_ELEMENT: if (elementStack.Count > 0) { var endElementName = elementStack.Pop(); xmlBuilder.AppendLine($""); } break; case EXIEvent.CHARACTERS: var text = DecodeCharacters(inputStream); xmlBuilder.Append(XmlEscape(text)); break; default: // Skip unsupported events break; } } catch (EXIException ex) when (ex.ErrorCode == EXIErrorCodes.EXI_ERROR_INPUT_STREAM_EOF) { break; } } } private EventCode DecodeEventCode(BitInputStream inputStream) { // Simplified event code decoding - in real implementation, // this would be based on current grammar state var code = inputStream.ReadBits(2); return new EventCode { Event = code switch { 0 => EXIEvent.START_ELEMENT, 1 => EXIEvent.END_ELEMENT, 2 => EXIEvent.CHARACTERS, 3 => EXIEvent.END_DOCUMENT, _ => EXIEvent.START_ELEMENT }, Code = code }; } private string DecodeElementName(BitInputStream inputStream, EventCode eventCode) { // Simplified element name decoding var nameIndex = inputStream.ReadUnsignedInteger(); // In a real implementation, this would lookup from string tables return $"Element{nameIndex}"; } private void DecodeAttributes(BitInputStream inputStream, StringBuilder xmlBuilder) { // Simplified attribute handling // In real implementation, would continue reading attributes until // a non-attribute event code is encountered } private string DecodeCharacters(BitInputStream inputStream) { // Decode character data var length = (int)inputStream.ReadUnsignedInteger(); var charData = inputStream.ReadBytes(length); return _config.Strings switch { EXIConfig.StringRepresentation.ASCII => Encoding.ASCII.GetString(charData), EXIConfig.StringRepresentation.UCS => Encoding.UTF8.GetString(charData), _ => Encoding.UTF8.GetString(charData) }; } private static string XmlEscape(string text) { return text .Replace("&", "&") .Replace("<", "<") .Replace(">", ">") .Replace("\"", """) .Replace("'", "'"); } } /// /// EXI Header information /// public class EXIHeader { public bool HasCookie { get; set; } public uint FormatVersion { get; set; } public bool PreserveComments { get; set; } public bool PreservePIs { get; set; } public bool PreserveDTD { get; set; } public bool PreservePrefixes { get; set; } } /// /// EXI Event Code /// public class EventCode { public EXIEvent Event { get; set; } public uint Code { get; set; } } }