-
Notifications
You must be signed in to change notification settings - Fork 14
Home
Welcome to the jsgenerator wiki!
Here you'll know more how it works under the hood.
This project is built on top of jsoup library, a Java HTML parser / jsoup GitHub Repository. It's all about using Nodes to generate JavaScript. We invite you to have a look at how the jsoup Node class looks like.
The core method here is the method named convert of ConvertService interface implemented by ConvertServiceImpl class:
/**
* Converts the Html string to Js string.
*
* @param content the Html string
* @return a String Object containing Js
*
* @throws NoHTMLCodeException if content is null
*
*/
public String convert(String content) throws NoHTMLCodeException {
if (content == null) {
throw new NoHTMLCodeException("There is no html content.");
}
if (content.isBlank()) {
throw new NoHTMLCodeException("The content has nothing to translate.");
}
Element htmlDoc = Jsoup.parse(content, "", Parser.xmlParser());
/*
* trim() is added to delete leading and trailing space. Before adding that, the
* generated Js code contained these not important spaces. It was difficult to
* test this method.
*/
String result = parseElement(htmlDoc).trim();
/*
* Without this line, the result of convert method with same parameter changed
* everytime if we used the same object to call this function. The result of
* convert with same parameter was constant if we used different objects. To
* avoid this issue, we were forced to create different objects but that's not
* how it should be done.
*
* Now, the program clears the list of used tags in order to always get an empty
* list when we call this method.
*/
usedTags.clear();
return result;
}First, the string content parameter is turned to an object of type Element extending the Node class by the static parse method of Jsoup class from the jsoup library:
Element htmlDoc = Jsoup.parse(content, "", Parser.xmlParser());Secondly, we use our in-house parsing method named parseElement to start creating the JavaScript code:
String result = parseElement(htmlDoc).trim(); /**
* Goes through the Jsoup Elements and converts them to JSElement objects.
*
* @param element Jsoup Element
* @return the generated code in JS
* @throws HTMLUnknownElementException if an invalid HTML tag is used
*
*/
private String parseElement(Element element) throws HTMLUnknownElementException {
logger.log(Level.INFO, " **** METHOD -- parseElement(Element element) -- Analyzing element : tagName = "
+ element.tagName() + " -> " + element + "\n" + "---------------" + "\n");
/*
* If the element is not the root and is unknown then there is a problem.
* Without the first condition "!element.root().equals(element)", there will be
* an exception thrown if the element is the root
*/
if (!element.root().equals(element) && !element.tag().isKnownTag()) {
throw new HTMLUnknownElementException(
"\"" + element + " -> " + element.tagName() + "\"" + " is not a valid HTML Element.");
}
StringBuilder generatedCode = new StringBuilder();
for (Element child : element.children()) {
generatedCode.append(parseElement(child)).append("\n"); // recursive
JSElement childJsElement = new JSElement(child, jsVariableDeclaration);
// parse this current element
generatedCode.append(parse(usedTags, childJsElement));
// append this current element's children code to parent code
String appends = appendChild(childJsElement);
if (!appends.equals("")) {
generatedCode.append(appends);
}
}
return generatedCode.toString();
}Each Element object has its children so we are making a recursive call to generate JavaScript variables for all of them. Let's focus on the 2 most important lines of this method, we are calling 2 in-house methods that will go straight to the point:
// parse this current element
generatedCode.append(parse(usedTags, childJsElement));
// append this current element's children code to parent code
String appends = appendChild(childJsElement);
Here, we are creating the JavaScript variables and setting attributes:
/**
* For this element, it returns the code to append the element to the parent
*
* @param usedTags List of used tags in the document
* @param jsElement
* @return code to append the element to the parent
* @throws HTMLUnknownElementException if an invalid HTML tag is used
*/
private String parse(List<String> usedTags, JSElement jsElement) throws HTMLUnknownElementException {
if (!jsElement.getElement().tag().isKnownTag()) {
throw new HTMLUnknownElementException(
"\"" + jsElement.getElement().tagName() + "\"" + " is not a valid HTML Element.");
}
// search tag name among used tags
usedTags.stream().filter(s -> s.equals(jsElement.getElement().tagName()))
.forEach(s -> jsElement.getElement().tagName(jsElement.getElement().tagName() + "_"));
// tag name
String tag = jsElement.getElement().tagName();
usedTags.add(tag);
/*
* Tag name should not contain _ TODO: Check that
*/
StringBuilder generatedCode = new StringBuilder(jsElement.getJsVariableDeclaration().getKeyword() + " " + tag
+ " = document.createElement(\"" + tag.replace("_", "") + "\");\n");
// attributes: attributes of the element
Attributes attributes = jsElement.getElement().attributes();
// Given an element, it adds the attributes to the element
for (Attribute attribute : attributes) {
generatedCode.append(tag).append(".setAttribute(\"").append(attribute.getKey()).append("\", \"")
.append(attribute.getValue()).append("\");\n");
}
return generatedCode.toString();
}
Here, we are appending children of type Node whether each child is an Element or TextNode:
/**
* For this element, it returns the code to append the child of type Element or
* TextNode
*
* @param jsElement
* @return code to append the child of type Element or TextNode
*/
private String appendChild(JSElement jsElement) {
StringBuilder generatedCode = new StringBuilder();
boolean hasChld = jsElement.getElement().childrenSize() > 0;
// tag name
String tag = jsElement.getElement().tagName();
// If the tag is not self closing
if (!jsElement.getElement().tag().isSelfClosing()) {
if (jsElement.getElement().childNodes().size() > 0) {
for (Node childNode : jsElement.getElement().childNodes()) {
if (childNode instanceof Element) {
Element childElement = (Element) childNode;
generatedCode.append(jsElement.getElement().tagName()).append(".appendChild(")
.append(childElement.tagName()).append(");\n");
}
// text nodes: text nodes of the element (content in between tags)
if (childNode instanceof TextNode) {
TextNode textNode = (TextNode) childNode;
/*
* We concat trailing space because trim() removes all leading and trailing
* spaces even the ones that are mandatory.
*
*/
if (!textNode.isBlank()) {
generatedCode.append(tag).append(".appendChild(document.createTextNode(\"")
.append(textNode.toString().replace("\n", "").trim().concat(" ")).append("\"));\n");
}
}
}
}
}
return generatedCode.toString();
}