namespace MkvPropEditWrapper.MkvInfo; public class MkvInfoReader { public const char LineDelimiter = '\n'; private readonly ReadOnlyMemory _memory; private ReadOnlyMemory _remainingMemory; private readonly MkvNodeRoot _root; private MkvNode _currentNode; private int _currentPosition; public MkvInfoReader(string mkvInfoOutput) { _memory = mkvInfoOutput.AsMemory(); _remainingMemory = _memory; _root = new MkvNodeRoot(); _currentNode = _root; _currentPosition = 0; } public MkvNodeRoot Read() { if (_root.Children.Count > 0) { return _root; } while (_currentPosition < _memory.Length) { var line = ReadLine(); // Skip empty lines if (line.Length < 1) { continue; } ProcessLine(line); } return _root; } private void ProcessLine(ReadOnlyMemory line) { var lineSpan = line.Span; // Find depth int depth = lineSpan.IndexOf('+'); // Climb up the node tree while (depth <= _currentNode.Depth) { _currentNode = _currentNode.Parent!; } // Node content var content = line[(depth + 2)..]; var contentSpan = content.Span; // Check for property int propertyIndex = contentSpan.IndexOf(':'); MkvNode node; // Property node if (propertyIndex != -1) { var name = content[..propertyIndex]; var value = content[(propertyIndex + 1)..]; node = new MkvProperty(_currentNode, name, value); } // Generic node else { node = new MkvNode(_currentNode, content); } _currentNode.Children.Add(node); _currentNode = node; } /// /// Read in the span till next \n or EOF while incrementing _currentPosition /// /// private ReadOnlyMemory ReadLine() { var remainingSpan = _remainingMemory.Span; ReadOnlyMemory line; int index = remainingSpan.IndexOf(LineDelimiter); // Reach the end if (index == -1) { line = _remainingMemory; _currentPosition = _memory.Length; } else { line = _remainingMemory[..(index - 1)]; // -1 to skip \r _currentPosition += index + 1; // Skip \n } // Move forward in the span _remainingMemory = _memory[_currentPosition..]; return line; } }