2016-03-04 20:37:22 +09:00
|
|
|
|
// Copyright (c) Alexandre Mutel. All rights reserved.
|
|
|
|
|
|
// This file is licensed under the BSD-Clause 2 license.
|
|
|
|
|
|
// See the license.txt file in the project root for more information.
|
2016-03-04 22:12:51 +09:00
|
|
|
|
|
2016-03-04 20:37:22 +09:00
|
|
|
|
using System;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
2016-02-25 20:30:37 +09:00
|
|
|
|
|
2016-03-04 22:12:51 +09:00
|
|
|
|
namespace Textamina.Markdig.Helpers
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// A group of <see cref="StringLine"/>.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <seealso cref="System.Collections.IEnumerable" />
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public class StringLineGroup : IEnumerable
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
private static readonly StringLine[] Empty = new StringLine[0];
|
2016-02-25 20:30:37 +09:00
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="StringLineGroup"/> class.
|
|
|
|
|
|
/// </summary>
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public StringLineGroup()
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
Lines = Empty;
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="StringLineGroup"/> class.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="text">The text.</param>
|
|
|
|
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public StringLineGroup(string text)
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
|
|
|
|
|
if (text == null) throw new ArgumentNullException(nameof(text));
|
2016-02-28 20:47:55 +09:00
|
|
|
|
Lines = Empty;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
Add(new StringSlice(text));
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the lines.
|
|
|
|
|
|
/// </summary>
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public StringLine[] Lines { get; private set; }
|
2016-02-25 20:30:37 +09:00
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the number of lines.
|
|
|
|
|
|
/// </summary>
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public int Count { get; private set; }
|
2016-02-25 20:30:37 +09:00
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Clears this instance.
|
|
|
|
|
|
/// </summary>
|
2016-02-27 00:13:01 +09:00
|
|
|
|
public void Clear()
|
|
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
Array.Clear(Lines, 0, Lines.Length);
|
2016-02-27 00:13:01 +09:00
|
|
|
|
Count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Removes the line at the specified index.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="index">The index.</param>
|
2016-02-27 00:13:01 +09:00
|
|
|
|
public void RemoveAt(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Count - 1 == index)
|
|
|
|
|
|
{
|
|
|
|
|
|
Count--;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
Array.Copy(Lines, index + 1, Lines, index, Count - index - 1);
|
|
|
|
|
|
Lines[Count - 1] = new StringLine();
|
2016-02-27 00:13:01 +09:00
|
|
|
|
Count--;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Adds the specified line to this instance.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="line">The line.</param>
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public void Add(ref StringLine line)
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
if (Count == Lines.Length) IncreaseCapacity();
|
|
|
|
|
|
Lines[Count++] = line;
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Adds the specified slice to this instance.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="slice">The slice.</param>
|
2016-02-26 22:06:59 +09:00
|
|
|
|
public void Add(StringSlice slice)
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
if (Count == Lines.Length) IncreaseCapacity();
|
|
|
|
|
|
Lines[Count++] = new StringLine(ref slice);
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override string ToString()
|
2016-02-27 00:33:22 +09:00
|
|
|
|
{
|
|
|
|
|
|
return ToSlice().ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Converts the lines to a single <see cref="StringSlice"/> by concatenating the lines.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="lineOffsets">The position of the `\n` line offsets from the beginning of the returned slice.</param>
|
|
|
|
|
|
/// <returns>A single slice concatenating the lines of this instance</returns>
|
2016-02-28 21:17:07 +09:00
|
|
|
|
public StringSlice ToSlice(List<int> lineOffsets = null)
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
2016-03-04 21:42:35 +09:00
|
|
|
|
// Optimization case when no lines
|
2016-02-26 21:11:54 +09:00
|
|
|
|
if (Count == 0)
|
|
|
|
|
|
{
|
2016-02-28 21:17:07 +09:00
|
|
|
|
if (lineOffsets != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
lineOffsets.Add(1);
|
|
|
|
|
|
}
|
2016-02-27 00:33:22 +09:00
|
|
|
|
return new StringSlice(string.Empty);
|
2016-02-26 21:11:54 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
// Optimization case for a single line.
|
2016-02-26 21:11:54 +09:00
|
|
|
|
if (Count == 1)
|
|
|
|
|
|
{
|
2016-02-28 21:17:07 +09:00
|
|
|
|
if (lineOffsets != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
lineOffsets.Add(Lines[0].Slice.End + 1);
|
|
|
|
|
|
}
|
2016-02-28 20:47:55 +09:00
|
|
|
|
return Lines[0];
|
2016-02-26 21:11:54 +09:00
|
|
|
|
}
|
2016-02-26 07:35:49 +09:00
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
// Else use a builder
|
2016-02-25 22:10:52 +09:00
|
|
|
|
var builder = StringBuilderCache.Local();
|
2016-02-26 07:35:49 +09:00
|
|
|
|
for (int i = 0; i < Count; i++)
|
2016-02-25 20:30:37 +09:00
|
|
|
|
{
|
|
|
|
|
|
if (i > 0)
|
|
|
|
|
|
{
|
2016-02-28 21:17:07 +09:00
|
|
|
|
if (lineOffsets != null)
|
|
|
|
|
|
{
|
2016-02-28 23:16:00 +09:00
|
|
|
|
lineOffsets.Add(builder.Length + 1); // Add 1 for \n and 1 for next line
|
2016-02-28 21:17:07 +09:00
|
|
|
|
}
|
2016-02-26 21:11:54 +09:00
|
|
|
|
builder.Append('\n');
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
2016-02-28 20:47:55 +09:00
|
|
|
|
if (!Lines[i].Slice.IsEmpty)
|
2016-02-26 20:42:28 +09:00
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
builder.Append(Lines[i].Slice.Text, Lines[i].Slice.Start, Lines[i].Slice.Length);
|
2016-02-26 20:42:28 +09:00
|
|
|
|
}
|
2016-02-28 21:17:07 +09:00
|
|
|
|
}
|
|
|
|
|
|
if (lineOffsets != null)
|
|
|
|
|
|
{
|
2016-02-28 23:16:00 +09:00
|
|
|
|
lineOffsets.Add(builder.Length); // Add 1 for \0
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
2016-02-25 22:10:52 +09:00
|
|
|
|
var str = builder.ToString();
|
|
|
|
|
|
builder.Clear();
|
2016-02-27 00:33:22 +09:00
|
|
|
|
return new StringSlice(str);
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
2016-02-26 22:06:59 +09:00
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Converts this instance into a <see cref="ICharIterator"/>.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Iterator ToCharIterator()
|
|
|
|
|
|
{
|
|
|
|
|
|
return new Iterator(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-26 22:06:59 +09:00
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
return Lines.GetEnumerator();
|
2016-02-26 22:06:59 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
private void IncreaseCapacity()
|
2016-02-26 22:06:59 +09:00
|
|
|
|
{
|
2016-03-04 21:42:35 +09:00
|
|
|
|
int newCapacity = Lines.Length == 0 ? 4 : Lines.Length * 2;
|
|
|
|
|
|
var newItems = new StringLine[newCapacity];
|
|
|
|
|
|
if (Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Array.Copy(Lines, 0, newItems, 0, Count);
|
|
|
|
|
|
}
|
|
|
|
|
|
Lines = newItems;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-04 21:42:35 +09:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The iterator used to iterate other the lines.
|
|
|
|
|
|
/// </summary>
|
2016-03-04 22:13:52 +09:00
|
|
|
|
/// <seealso cref="ICharIterator" />
|
2016-02-26 22:06:59 +09:00
|
|
|
|
public struct Iterator : ICharIterator
|
|
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
private readonly StringLineGroup lines;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
private int offset;
|
|
|
|
|
|
|
2016-02-28 20:47:55 +09:00
|
|
|
|
public Iterator(StringLineGroup lines)
|
2016-02-26 22:06:59 +09:00
|
|
|
|
{
|
|
|
|
|
|
this.lines = lines;
|
|
|
|
|
|
Start = -1;
|
|
|
|
|
|
offset = -1;
|
|
|
|
|
|
SliceIndex = 0;
|
|
|
|
|
|
CurrentChar = '\0';
|
|
|
|
|
|
End = -2;
|
|
|
|
|
|
for (int i = 0; i < lines.Count; i++)
|
|
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
End += lines.Lines[i].Slice.Length + 1; // Add chars
|
2016-02-26 22:06:59 +09:00
|
|
|
|
}
|
|
|
|
|
|
NextChar();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int Start { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public char CurrentChar { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public int End { get; private set; }
|
|
|
|
|
|
|
2016-02-27 00:13:01 +09:00
|
|
|
|
public bool IsEmpty => Start > End;
|
|
|
|
|
|
|
2016-02-26 22:06:59 +09:00
|
|
|
|
public int SliceIndex { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public char NextChar()
|
|
|
|
|
|
{
|
|
|
|
|
|
Start++;
|
|
|
|
|
|
offset++;
|
|
|
|
|
|
if (Start <= End)
|
|
|
|
|
|
{
|
2016-02-28 20:47:55 +09:00
|
|
|
|
var slice = (StringSlice)lines.Lines[SliceIndex];
|
2016-02-26 22:06:59 +09:00
|
|
|
|
if (offset < slice.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentChar = slice[slice.Start + offset];
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentChar = '\n';
|
|
|
|
|
|
SliceIndex++;
|
|
|
|
|
|
offset = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentChar = '\0';
|
|
|
|
|
|
Start = End + 1;
|
|
|
|
|
|
SliceIndex = lines.Count;
|
|
|
|
|
|
offset--;
|
|
|
|
|
|
}
|
|
|
|
|
|
return CurrentChar;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool TrimStart()
|
|
|
|
|
|
{
|
|
|
|
|
|
var c = CurrentChar;
|
2016-02-27 00:13:01 +09:00
|
|
|
|
bool hasSpaces = false;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
while (c.IsWhitespace())
|
|
|
|
|
|
{
|
2016-02-27 00:13:01 +09:00
|
|
|
|
hasSpaces = true;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
c = NextChar();
|
|
|
|
|
|
}
|
2016-02-27 00:13:01 +09:00
|
|
|
|
return hasSpaces;
|
2016-02-26 22:06:59 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-25 20:30:37 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|