Skip to content
Fanon Jupkwo edited this page Nov 28, 2022 · 15 revisions

OUTDATED

This wiki is related to the code from the branch named anterior but it is still useful to get a big picture.

How it works ?

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 (not Node JS) to generate JavaScript. We invite you to have a look at how the jsoup Node class looks like.

Jsoup turns HTML elements into Java Objects then JS Generator turns Java Objects into JavaScript variables.

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.");

	}

/* Jsoup turns HTML elements into Java Objects */
	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 another 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 in order to give a name that doesn't already exist to the new variable. If the name exists, we add "_" to make difference.
	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();
    }
    

To declare JS variables, we provide let or var thanks to this enum:

public enum JSVariableDeclaration {

    VAR("var"), LET("let");

    JSVariableDeclaration(String keyword) {

	this.keyword = keyword;

    }

    private String keyword;

    public String getKeyword() {

	return keyword;
    }

}

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();
    }
    
Clone this wiki locally