Mapping our markdown tag types to HTML tag types
In our requirements, we set out a master list of tags that our parser is going to handle. In order to identify these tags, we are going to add an enumeration consisting of the tags we are making available to our users:
enum TagType {
Paragraph,
Header1,
Header2,
Header3,
HorizontalRule
}
From our requirements, we also know that we need to translate between these tags and their equivalent opening and closing HTML tags. The way that we are going to do this is to map tagType to an equivalent HTML tag. To do this, we are going to create a class that has the sole responsibility of handling this mapping for us. The following code shows this:
class TagTypeToHtml {
private readonly tagType : Map<TagType, string> = new Map<TagType, string>();
constructor() {
this.tagType.set(TagType.Header1, "h1");
this.tagType.set(TagType.Header2, "h2");
this.tagType.set(TagType.Header3, "h3");
this.tagType.set(TagType.Paragraph, "p");
this.tagType.set(TagType.HorizontalRule, "hr")
}
}
We also need a way to retrieve opening and closing tags from this class. We're going to start by creating a method to get the opening tag from tagType, as follows:
public OpeningTag(tagType : TagType) : string {
let tag = this.tagType.get(tagType);
if (tag !== null) {
return `<${tag}>`;
}
return `<p>`;
}
This method is pretty straightforward. It starts by trying to get tagType from the map. With the code we currently have, we will always have an entry in the map, but we could extend the enumeration in the future and forget to add the tag to the list of tags. That is why we check to see if the tag is present; if it is, we return the tag enclosed in <>. If the tag is not present, we return a paragraph tag as a default.
Now, let's look at ClosingTag:
public ClosingTag(tagType : TagType) : string {
let tag = this.tagType.get(tagType);
if (tag !== null) {
return `</${tag}>`;
}
return `</p>`;
}
Looking at these two methods, we can see that they are almost identical. When we think about the problem of creating our HTML tag, we realize that the only difference between an opening and a closing tag is that the closing tag has a / in it. With that in mind, we can change the code to use a helper method that accepts whether the tag starts with < or </:
private GetTag(tagType : TagType, openingTagPattern : string) : string {
let tag = this.tagType.get(tagType);
if (tag !== null) {
return `${openingTagPattern}${tag}>`;
}
return `${openingTagPattern}p>`;
}
All that remains is for us to add methods to retrieve the opening and closing tags:
public OpeningTag(tagType : TagType) : string {
return this.GetTag(tagType, `<`);
}
public ClosingTag(tagType : TagType) : string {
return this.GetTag(tagType, `</`);
}
Pulling this all together, the code for our TagTypeToHtml class now looks like this:
class TagTypeToHtml {
private readonly tagType : Map<TagType, string> = new Map<TagType, string>();
constructor() {
this.tagType.set(TagType.Header1, "h1");
this.tagType.set(TagType.Header2, "h2");
this.tagType.set(TagType.Header3, "h3");
this.tagType.set(TagType.Paragraph, "p");
this.tagType.set(TagType.HorizontalRule, "hr")
}
public OpeningTag(tagType : TagType) : string {
return this.GetTag(tagType, `<`);
}
public ClosingTag(tagType : TagType) : string {
return this.GetTag(tagType, `</`);
}
private GetTag(tagType : TagType, openingTagPattern : string) : string {
let tag = this.tagType.get(tagType);
if (tag !== null) {
return `${openingTagPattern}${tag}>`;
}
return `${openingTagPattern}p>`;
}
}
This handy acronym serves to guide us on how to structure classes and the most important part, in my opinion, is the Single Responsibility Principle, which states that a class should do one thing and one thing only. While I would certainly recommend reading about this topic (and we will touch on other aspects of it as we progress), in my opinion, the most important part of SOLID design is that classes are responsible for one thing and one thing only; everything else flows out of that principle. Classes that only do one thing are generally much easier to test and they are a lot easier to understand. That does not mean that they should only have one method. They can have many methods, as long as they are all related to the purpose of the class. We will cover this topic again and again throughout the book because it is so important.