PROWAREtech

articles » current » dot-net » gemini-text-to-html

.NET: Convert Google's Gemini Markdown Text to HTML

A simple class to format Gemini text into HTML for viewing on the Internet; written in C#.

Easily convert Gemini text to HTML for the Web. How to generate Gemini text.

The GeminiTextToHtmlConverter is a static class that converts text written in a Markdown-like format (called Gemini) into HTML. It provides a single public method ToHtml that takes a string of Gemini-formatted text and returns the HTML equivalent. The core functionality handles basic text parsing and maintains state for features like code blocks and lists.

The converter supports several formatting features including headings (using = symbols), unordered lists (using - or * markers), ordered lists (using numbers with periods), code blocks (wrapped in triple backticks), and inline formatting like bold (using ** or __) and italic (using * or _) text. It also handles HTML entity escaping for special characters and supports both inline links [text](url) and reference-style links [text][1].

The implementation processes the text line by line, using a StringBuilder for efficient string concatenation. It maintains state variables to track whether it's currently processing a code block or list, ensuring proper HTML tag nesting and closure. The code uses regular expressions for parsing inline formatting and links, and includes helper methods for processing different aspects of the text such as ProcessInlineFormatting, ProcessLinks, and EscapeHtml.

Example Usage

namespace GenerativeAIConsole
{
    internal class Program
    {
        static void Main(string[] args)
        {
			while (true)
			{
				Console.Write("Enter question: ");
				var line = Console.ReadLine();
				if (line == null || line.Length == 0)
					break;
				try
				{
					var apiKey = "ENTER_API_KEY_HERE";
					var model = new ML.GenAI.GenerativeModel() { ApiKey = apiKey, Model = ML.GenAI.Model.GeminiPro };
					var response = model.GenerateContent(line).Result;
					string text = string.IsNullOrEmpty(response.Text) ? "**no response**" : response.Text;
					Console.WriteLine(ML.GeminiAI.GeminiTextToHtmlConverter.ToHtml(text));
				}
				catch (Exception ex)
				{
					Console.WriteLine(ex.Message);
				}
			}
		}
	}
}

ML.GeminiAI Code

It is a simple class - just one public method!


// GeminiTextParser.cs
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.FileSystemGlobbing.Internal;

namespace ML.GeminiAI
{
	public static class GeminiTextToHtmlConverter
	{
		private static readonly Dictionary<string, string> HtmlEntities = new()
		{
			{ "&", "&amp;" },
			{ "<", "&lt;" },
			{ ">", "&gt;" },
			{ "\"", "&quot;" },
			{ "'", "&#39;" }
		};

		public static string ToHtml(string textGemini)
		{
			if (string.IsNullOrEmpty(textGemini))
				return string.Empty;

			var lines = textGemini.Split('\n');
			var htmlBuilder = new StringBuilder();
			var inCodeBlock = false;
			var inList = false;
			var inTable = false;
			var listType = string.Empty;

			for (int i = 0; i < lines.Length; i++)
			{
				var line = lines[i];
				var trimmedLine = line.Trim();

				// Check if we're exiting a list
				if (inList && !IsListItem(trimmedLine))
				{
					htmlBuilder.AppendLine(listType == "ul" ? "</ul>" : "</ol>");
					inList = false;
				}

				// Check if we're exiting a table
				if (inTable && !trimmedLine.StartsWith("|"))
				{
					htmlBuilder.AppendLine("</table>");
					inTable = false;
				}

				// Process each line based on its content
				if (line == "" && inCodeBlock)
				{
					htmlBuilder.AppendLine();
				}
				else if (line == "")
				{
					htmlBuilder.AppendLine("<br />");
				}
				else if(!inCodeBlock && trimmedLine == "---")
				{
					htmlBuilder.AppendLine("<hr />");
				}
				else if (!inCodeBlock && trimmedLine.StartsWith("|") && !inTable)
				{
					inTable = true;
					htmlBuilder.AppendLine("<table><tr><td>");
					htmlBuilder.AppendLine(ProcessInlineFormatting(EscapeHtml(trimmedLine.Substring(1, trimmedLine.Length - 2))).Replace("|", "</td><td>"));
					htmlBuilder.AppendLine("</td></tr>");
				}
				else if (!inCodeBlock && trimmedLine.StartsWith("|") && inTable)
				{
					htmlBuilder.AppendLine("<tr><td>");
					htmlBuilder.AppendLine(ProcessInlineFormatting(EscapeHtml(trimmedLine.Substring(1, trimmedLine.Length - 2))).Replace("|", "</td><td>"));
					htmlBuilder.AppendLine("</td></tr>");
				}
				else if (!inCodeBlock && trimmedLine.StartsWith("```"))
				{
					inCodeBlock = true;
					htmlBuilder.AppendLine("<pre><code>");
				}
				else if (inCodeBlock && trimmedLine.StartsWith("```"))
				{
					htmlBuilder.AppendLine(EscapeHtml(trimmedLine.Substring(3)) + "</code></pre>");
					inCodeBlock = false;
				}
				else if (!inCodeBlock && trimmedLine.StartsWith("#"))
				{
					var headingLevel = trimmedLine.TakeWhile(c => c == '#').Count();
					var headingText = trimmedLine.TrimStart('#').Trim();
					htmlBuilder.AppendLine($"<h{headingLevel}>{ProcessInlineFormatting(EscapeHtml(headingText))}</h{headingLevel}>");
				}
				else if (!inCodeBlock && trimmedLine.StartsWith("="))
				{
					var headingLevel = trimmedLine.TakeWhile(c => c == '=').Count();
					var headingText = trimmedLine.TrimStart('=').Trim();
					htmlBuilder.AppendLine($"<h{headingLevel}>{ProcessInlineFormatting(EscapeHtml(headingText))}</h{headingLevel}>");
				}
				else if (!inCodeBlock && trimmedLine.StartsWith(">"))
				{
					var headingText = trimmedLine.TrimStart('>').Trim();
					htmlBuilder.AppendLine($"<blockquote>{ProcessInlineFormatting(EscapeHtml(headingText))}</blockquote>");
				}
				else if (!inCodeBlock && IsListItem(trimmedLine))
				{
					var currentListType = trimmedLine.StartsWith("- ") || trimmedLine.StartsWith("* ") ? "ul" : "ol";

					if (!inList)
					{
						listType = currentListType;
						htmlBuilder.AppendLine($"<{listType}>");
						inList = true;
					}
					else if (listType != currentListType)
					{
						htmlBuilder.AppendLine($"</{listType}>");
						listType = currentListType;
						htmlBuilder.AppendLine($"<{listType}>");
					}

					var itemContent = trimmedLine;
					if (listType == "ul")
						itemContent = trimmedLine.TrimStart('-', '*').Trim();
					else
						itemContent = Regex.Replace(trimmedLine, @"^\d+\.\s*", "");

					htmlBuilder.AppendLine($"<li>{ProcessInlineFormatting(EscapeHtml(itemContent))}</li>");
				}
				else if (inCodeBlock)
				{
					htmlBuilder.AppendLine(EscapeHtml(line));
				}
				else if (!string.IsNullOrWhiteSpace(line))
				{
					htmlBuilder.AppendLine($"<p>{ProcessInlineFormatting(EscapeHtml(line))}</p>");
				}
			}

			// Close any open tags
			if (inCodeBlock)
				htmlBuilder.AppendLine("</code></pre>");
			if (inTable)
				htmlBuilder.AppendLine("</table>");
			if (inList)
				htmlBuilder.AppendLine(listType == "ul" ? "</ul>" : "</ol>");

			return htmlBuilder.ToString().TrimEnd();
		}

		private static bool IsListItem(string line)
		{
			return line.StartsWith("- ") || line.StartsWith("* ");
			//return line.StartsWith("- ") || line.StartsWith("* ") || Regex.IsMatch(line, @"^\d+\.\s");
		}

		private static string ProcessInlineFormatting(string text)
		{
			// Process links first
			text = ProcessLinks(text.TrimEnd());

			// Process special formatting
			text = Regex.Replace(text, @"\*\*(.+?)\*\*", m => $"<strong>{m.Groups[1].Value}{m.Groups[2].Value}</strong>");
			text = Regex.Replace(text, @"\*(.+?)\*", m => $"<em>{m.Groups[1].Value}{m.Groups[2].Value}</em>");
			text = Regex.Replace(text, @" __(.+?)__ ", m => $"<u> {m.Groups[1].Value}{m.Groups[2].Value} </u>");
			text = Regex.Replace(text, @" _(.+?)_ ", m => $"<i> {m.Groups[1].Value}{m.Groups[2].Value} </i>");
			text = Regex.Replace(text, @"==(.+?)==", m => $"<mark>{m.Groups[1].Value}{m.Groups[2].Value}</mark>");

			string code = @"(?<!\\)`((?:\\.|[^`])*)`";
			return Regex.Replace(text, code, m =>
			{
				string content = m.Groups[1].Value;
				return $"<code>{content}</code>";
			});
		}

		private static string ProcessLinks(string text)
		{
			// Process inline links [text](url)
			text = Regex.Replace(text, @"\[([^\]]+)\]\(([^)]+)\)", "<a href=\"$2\">$1</a>");

			// Process reference links [text][1]
			text = Regex.Replace(text, @"\[([^\]]+)\]\[(\d+)\]", "<a href=\"#ref$2\">$1</a>");

			return text;
		}

		private static string EscapeHtml(string text)
		{
			foreach (var entity in HtmlEntities)
			{
				text = text.Replace(entity.Key, entity.Value);
			}
			return text;
		}
	}
}

PROWAREtech

Hello there! How can I help you today?
Ask any question

PROWAREtech

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
ACCEPT REJECT