public void SupportsAlternateDelimiters( string input, int firstIndex, int lastIndex, string startDelimiter, string endDelimiter) { // Given Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(input)); IShortcodeCollection shortcodeCollection = new TestShortcodeCollection(); shortcodeCollection.Add("name", typeof(TestShortcode)); ShortcodeParser parser = new ShortcodeParser( startDelimiter, endDelimiter, shortcodeCollection); // When List <ShortcodeLocation> result = parser.Parse(stream); // Then result.Single().FirstIndex.ShouldBe(firstIndex); result.Single().LastIndex.ShouldBe(lastIndex); }
protected ParserState(ShortcodeParser shortcodeParser) { _shortcodeParser = shortcodeParser; _textParser = shortcodeParser.TextParser; }
public ParsingAttributesState(ShortcodeParser shortcodeParser) : base(shortcodeParser) { }
public LookingForTagState(ShortcodeParser shortcodeParser) : base(shortcodeParser) { }
/// <inheritdoc /> public IShortcodeResult Execute(KeyValuePair <string, string>[] args, string content, IDocument document, IExecutionContext context) { ConvertingDictionary dictionary = args.ToDictionary( context, "Class", "HeaderRows", "FooterRows", "HeaderCols", "HeaderClass", "BodyClass", "FooterClass"); string[] lines = content .Trim() .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToArray(); // Table XElement table = new XElement( "table", dictionary.XAttribute("class")); int line = 0; // Header int headerRows = dictionary.Get("HeaderRows", 0); XElement header = null; for (int c = 0; c < headerRows && line < lines.Length; c++, line++) { // Create the section if (c == 0) { header = new XElement( "thead", dictionary.XAttribute("class", "HeaderClass")); table.Add(header); } // Create the current row XElement row = new XElement("tr"); header.Add(row); // Add the columns foreach (string col in ShortcodeParser.SplitArguments(lines[line], 0).ToValueArray()) { row.Add(new XElement("th", col)); } } // Body int bodyRows = lines.Length - line - dictionary.Get("FooterRows", 0); XElement body = null; for (int c = 0; c < bodyRows && line < lines.Length; c++, line++) { // Create the section if (c == 0) { body = new XElement( "tbody", dictionary.XAttribute("class", "BodyClass")); table.Add(body); } // Create the current row XElement row = new XElement("tr"); body.Add(row); // Add the columns int th = dictionary.Get("HeaderCols", 0); foreach (string col in ShortcodeParser.SplitArguments(lines[line], 0).ToValueArray()) { row.Add(new XElement(th-- > 0 ? "th" : "td", col)); } } // Footer XElement footer = null; for (int c = 0; line < lines.Length; c++, line++) { // Create the section if (c == 0) { footer = new XElement( "tfoot", dictionary.XAttribute("class", "FooterClass")); table.Add(footer); } // Create the current row XElement row = new XElement("tr"); footer.Add(row); // Add the columns int th = dictionary.Get("HeaderCols", 0); foreach (string col in ShortcodeParser.SplitArguments(lines[line], 0).ToValueArray()) { row.Add(new XElement(th-- > 0 ? "th" : "td", col)); } } return(context.GetShortcodeResult(table.ToString())); }
// The inputStream will be disposed if this returns <c>true</c> but will not otherwise private bool ProcessShortcodes(Stream inputStream, IDocument input, IExecutionContext context, out IDocument result) { // Parse the input stream looking for shortcodes ShortcodeParser parser = new ShortcodeParser(_startDelimiter, _endDelimiter, context.Shortcodes); if (!inputStream.CanSeek) { inputStream = new SeekableStream(inputStream, true); } List <ShortcodeLocation> locations = parser.Parse(inputStream); // Reset the position because we're going to use the stream again when we do replacements inputStream.Position = 0; // Return the original document if we didn't find any if (locations.Count == 0) { result = null; return(false); } // Otherwise, create a shortcode instance for each named shortcode Dictionary <string, IShortcode> shortcodes = locations .Select(x => x.Name) .Distinct(StringComparer.OrdinalIgnoreCase) .ToDictionary(x => x, x => context.Shortcodes.CreateInstance(x), StringComparer.OrdinalIgnoreCase); // Execute each of the shortcodes in order List <InsertingStreamLocation> insertingLocations = locations .Select(x => { // Execute the shortcode IShortcodeResult shortcodeResult = shortcodes[x.Name].Execute(x.Arguments, x.Content, input, context); // Merge output metadata with the current input document // Creating a new document is the easiest way to ensure all the metadata from shortcodes gets accumulated correctly if (shortcodeResult?.Metadata != null) { input = context.GetDocument(input, shortcodeResult.Metadata); } // Recursively parse shortcodes Stream shortcodeResultStream = shortcodeResult?.Stream; if (shortcodeResultStream != null) { // Don't process nested shortcodes if it's the raw shortcode if (!x.Name.Equals(nameof(Core.Shortcodes.Contents.Raw), StringComparison.OrdinalIgnoreCase)) { if (!shortcodeResultStream.CanSeek) { shortcodeResultStream = new SeekableStream(shortcodeResultStream, true); } if (ProcessShortcodes(shortcodeResultStream, input, context, out IDocument nestedResult)) { input = nestedResult; shortcodeResultStream = nestedResult.GetStream(); // Will get disposed in the replacement operation below } else { shortcodeResultStream.Position = 0; } } return(new InsertingStreamLocation(x.FirstIndex, x.LastIndex, shortcodeResultStream)); } return(new InsertingStreamLocation(x.FirstIndex, x.LastIndex, null)); }) .ToList(); // Dispose any shortcodes that implement IDisposable foreach (IDisposable disposableShortcode in shortcodes.Values.Select(x => x as IDisposable).Where(x => x != null)) { disposableShortcode.Dispose(); } // Construct a new stream with the shortcode results inserted // We have to use all TextWriter/TextReaders over the streams to ensure consistent encoding Stream resultStream = context.GetContentStream(); char[] buffer = new char[4096]; using (TextWriter writer = new StreamWriter(resultStream, Encoding.UTF8, 4096, true)) { // The input stream will get disposed when the reader is using (TextReader reader = new StreamReader(inputStream)) { int position = 0; int length = 0; foreach (InsertingStreamLocation insertingLocation in insertingLocations) { // Copy up to the start of this shortcode length = insertingLocation.FirstIndex - position; Read(reader, writer, length, ref buffer); position += length; // Copy the shortcode content to the result stream if (insertingLocation.Stream != null) { // This will dispose the shortcode content stream when done using (TextReader insertingReader = new StreamReader(insertingLocation.Stream)) { Read(insertingReader, writer, null, ref buffer); } } // Skip the shortcode text length = insertingLocation.LastIndex - insertingLocation.FirstIndex + 1; Read(reader, null, length, ref buffer); position += length; } // Copy remaining Read(reader, writer, null, ref buffer); } } result = context.GetDocument(input, resultStream); return(true); }
public ParsingCloseTagState(ShortcodeParser shortcodeParser) : base(shortcodeParser) { }
public void Parse_NullContentReturnsEmptyString( ShortcodeParser sut ) { sut.Parse(null, null).Should().Be(MvcHtmlString.Empty); }
public ParsingOpenTagState(ShortcodeParser shortcodeParser) : base(shortcodeParser) { }