- Reorganize project structure: Port/ → DotNet/, VC/, C++/ - Add comprehensive cross-platform build automation - Windows: build_all.bat, build.bat files for all components - Linux/macOS: build_all.sh, build.sh files for all components - Update all build scripts with correct folder paths - Create test automation scripts (test_all.bat/sh) - Update documentation to reflect new structure - Maintain 100% roundtrip accuracy for test5.exi (pure EXI) - Support both Windows MSBuild and Linux GCC compilation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
263 lines
9.0 KiB
C#
263 lines
9.0 KiB
C#
/*
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// EXI Decoder for converting EXI binary data to XML
|
|
/// </summary>
|
|
public class EXIDecoder
|
|
{
|
|
private readonly EXIConfig _config;
|
|
|
|
public EXIDecoder(EXIConfig? config = null)
|
|
{
|
|
_config = config ?? new EXIConfig();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decode EXI binary data to XML string
|
|
/// </summary>
|
|
/// <param name="exiData">EXI binary data</param>
|
|
/// <returns>XML string representation</returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decode EXI binary data to XmlDocument
|
|
/// </summary>
|
|
/// <param name="exiData">EXI binary data</param>
|
|
/// <returns>XmlDocument</returns>
|
|
public XmlDocument DecodeToXmlDocument(byte[] exiData)
|
|
{
|
|
string xmlString = DecodeToXml(exiData);
|
|
var xmlDoc = new XmlDocument();
|
|
xmlDoc.LoadXml(xmlString);
|
|
return xmlDoc;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate EXI header and extract options
|
|
/// </summary>
|
|
/// <param name="inputStream">Input bit stream</param>
|
|
/// <returns>EXI header information</returns>
|
|
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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
|
|
|
// Decode document content
|
|
DecodeDocumentContent(inputStream, xmlBuilder);
|
|
}
|
|
|
|
private void DecodeDocumentContent(BitInputStream inputStream, StringBuilder xmlBuilder)
|
|
{
|
|
var elementStack = new Stack<string>();
|
|
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($"</{endElementName}>");
|
|
}
|
|
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("'", "'");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// EXI Header information
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// EXI Event Code
|
|
/// </summary>
|
|
public class EventCode
|
|
{
|
|
public EXIEvent Event { get; set; }
|
|
public uint Code { get; set; }
|
|
}
|
|
} |