public void DoesNotRenderLinkIfNoImage() { // Given TestExecutionContext context = new TestExecutionContext(); TestDocument document = new TestDocument(); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("Link", "/c/d"), new KeyValuePair <string, string>("Target", "abc"), new KeyValuePair <string, string>("Rel", "def"), new KeyValuePair <string, string>("Alt", "ghi"), new KeyValuePair <string, string>("Class", "jkl"), new KeyValuePair <string, string>("Height", "100px"), new KeyValuePair <string, string>("Width", "200px") }; Figure shortcode = new Figure(); // When IShortcodeResult result = shortcode.Execute(args, "foo bar", document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe( @"<figure class=""jkl""> <figcaption>foo bar</figcaption> </figure>", StringCompareShould.IgnoreLineEndings); } }
public void RendersLinkWithAlternateHost(string path, string expected) { // Given TestExecutionContext context = new TestExecutionContext(); context.AddTypeConversion <string, FilePath>(x => new FilePath(x)); context.AddTypeConversion <string, bool>(x => bool.Parse(x)); context.Settings[Keys.Host] = "domain.com"; TestDocument document = new TestDocument(); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("Path", path), new KeyValuePair <string, string>("Host", "google.com") }; Link shortcode = new Link(); // When IShortcodeResult result = shortcode.Execute(args, string.Empty, document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe(expected); } }
public void RendersFigureWithoutLink() { // Given TestExecutionContext context = new TestExecutionContext(); TestDocument document = new TestDocument(); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("Src", "/a/b"), new KeyValuePair <string, string>("Alt", "ghi"), new KeyValuePair <string, string>("Class", "jkl"), new KeyValuePair <string, string>("Height", "100px"), new KeyValuePair <string, string>("Width", "200px") }; Figure shortcode = new Figure(); // When IShortcodeResult result = shortcode.Execute(args, "foo bar", document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe( @"<figure class=""jkl""> <img src=""/a/b"" alt=""ghi"" height=""100px"" width=""200px"" /> <figcaption>foo bar</figcaption> </figure>", StringCompareShould.IgnoreLineEndings); } }
public void RendersTableWithoutSettings() { // Given TestExecutionContext context = new TestExecutionContext(); TestDocument document = new TestDocument(); string content = @" 1 2 ""3 4"" a ""b c"" d e f g 5 678 ""h i"" j ""k"" l=m nop "; KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { }; Table shortcode = new Table(); // When IShortcodeResult result = shortcode.Execute(args, content, document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe( @"<table> <tbody> <tr> <td>1</td> <td>2</td> <td>3 4</td> </tr> <tr> <td>a</td> <td>b c</td> <td>d</td> </tr> <tr> <td>e</td> <td>f</td> <td>g</td> </tr> <tr> <td>5</td> <td>678</td> </tr> <tr> <td>h i</td> <td>j</td> <td>k</td> </tr> <tr> <td>l=m</td> <td>nop</td> </tr> </tbody> </table>", StringCompareShould.IgnoreLineEndings); } }
public void IncludesFileRelativeToSource() { // Given TestFileProvider fileProvider = new TestFileProvider(); fileProvider.AddDirectory("/"); fileProvider.AddDirectory("/A"); fileProvider.AddDirectory("/A/B"); fileProvider.AddDirectory("/A/D"); fileProvider.AddFile("/A/B/c.txt", "foo"); TestFileSystem fileSystem = new TestFileSystem { FileProvider = fileProvider }; fileSystem.InputPaths.Clear(); fileSystem.InputPaths.Add("/A"); TestExecutionContext context = new TestExecutionContext { FileSystem = fileSystem }; TestDocument document = new TestDocument() { Source = new FilePath("/A/D/x.txt") }; KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>(null, "../B/c.txt") }; Include shortcode = new Include(); // When IShortcodeResult result = shortcode.Execute(args, string.Empty, document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe("foo"); } }
public void NullStreamForMissingMetadata() { // Given TestExecutionContext context = new TestExecutionContext(); TestDocument document = new TestDocument(new MetadataItems { { "Foo", "Bar" } }); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>(null, "Fizz") }; Core.Shortcodes.Metadata.Meta shortcode = new Core.Shortcodes.Metadata.Meta(); // When IShortcodeResult result = shortcode.Execute(args, string.Empty, document, context); // Then result.Stream.ShouldBeNull(); }
public void RendersGistWithoutFile() { TestExecutionContext context = new TestExecutionContext(); TestDocument document = new TestDocument(); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>(null, "abc"), new KeyValuePair <string, string>(null, "def") }; Gist shortcode = new Gist(); // When IShortcodeResult result = shortcode.Execute(args, null, document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe( "<script src=\"//gist.github.com/def/abc.js\" type=\"text/javascript\"></script>"); } }
public void RendersMetadata() { // Given TestExecutionContext context = new TestExecutionContext(); TestDocument document = new TestDocument(new MetadataItems { { "Foo", "Bar" } }); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>(null, "Foo") }; Core.Shortcodes.Metadata.Meta shortcode = new Core.Shortcodes.Metadata.Meta(); // When IShortcodeResult result = shortcode.Execute(args, string.Empty, document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe("Bar"); } }
public void NullResultIfFileDoesNotExist() { // Given ThrowOnTraceEventType(System.Diagnostics.TraceEventType.Error); TestFileProvider fileProvider = new TestFileProvider(); fileProvider.AddDirectory("/"); fileProvider.AddDirectory("/A"); fileProvider.AddDirectory("/A/B"); fileProvider.AddFile("/A/B/c.txt", "foo"); TestFileSystem fileSystem = new TestFileSystem { FileProvider = fileProvider }; fileSystem.InputPaths.Clear(); fileSystem.InputPaths.Add("/A"); TestExecutionContext context = new TestExecutionContext { FileSystem = fileSystem }; TestDocument document = new TestDocument(); KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>(null, "B/d.txt") }; Include shortcode = new Include(); // When IShortcodeResult result = shortcode.Execute(args, string.Empty, document, context); // Then result.Stream.ShouldBeNull(); }
// 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 void RendersTableWithSettings() { // Given TestExecutionContext context = new TestExecutionContext(); context.AddTypeConversion <string, int>(x => int.Parse(x)); TestDocument document = new TestDocument(); string content = @" 1 2 ""3 4"" a ""b c"" d e f g 5 678 ""h i"" j ""k"" l=m nop "; KeyValuePair <string, string>[] args = new KeyValuePair <string, string>[] { new KeyValuePair <string, string>("Class", "tclass"), new KeyValuePair <string, string>("HeaderRows", "1"), new KeyValuePair <string, string>("FooterRows", "2"), new KeyValuePair <string, string>("HeaderCols", "1"), new KeyValuePair <string, string>("HeaderClass", "hclass"), new KeyValuePair <string, string>("BodyClass", "bclass"), new KeyValuePair <string, string>("FooterClass", "fclass") }; Table shortcode = new Table(); // When IShortcodeResult result = shortcode.Execute(args, content, document, context); // Then using (TextReader reader = new StreamReader(result.Stream)) { reader.ReadToEnd().ShouldBe( @"<table class=""tclass""> <thead class=""hclass""> <tr> <th>1</th> <th>2</th> <th>3 4</th> </tr> </thead> <tbody class=""bclass""> <tr> <th>a</th> <td>b c</td> <td>d</td> </tr> <tr> <th>e</th> <td>f</td> <td>g</td> </tr> <tr> <th>5</th> <td>678</td> </tr> </tbody> <tfoot class=""fclass""> <tr> <th>h i</th> <td>j</td> <td>k</td> </tr> <tr> <th>l=m</th> <td>nop</td> </tr> </tfoot> </table>", StringCompareShould.IgnoreLineEndings); } }