Zettelkasten-Team / Zettelkasten

Zettelkasten-Developer-Builds
http://zettelkasten.danielluedecke.de
GNU General Public License v3.0
716 stars 92 forks source link

Refactor `getEntryHeadline` method for readability and maintainability #517

Open RalfBarkow opened 1 month ago

RalfBarkow commented 1 month ago

The getEntryHeadline method in our codebase is responsible for generating an HTML snippet that includes the rating, word count, timestamp, and manual links for a specific entry. However, the current implementation is lengthy, complex, and difficult to maintain. To improve the code quality, we need to refactor this method by breaking it down into smaller, more manageable helper methods. This will enhance readability, modularity, and maintainability.

Tasks:

  1. Extract Helper Methods: Break down the getEntryHeadline method into smaller helper methods:

    • calculateWordCount
    • appendInitialHtml
    • appendEntryHeading
    • appendEntryTimestamp
    • appendRatingHtml
    • appendManualLinks
  2. Simplify String Manipulations: Streamline string manipulations and avoid repeated operations.

  3. Improve Readability: Use meaningful method names and reduce the size of the main method.

  4. Error Handling: Add error handling or logging for potential exceptions (e.g., NumberFormatException in appendManualLinks).

  5. Testing: Ensure that the refactored methods work as expected and the HTML output remains consistent.

Current Method:

/**
 * This method creates a HTML-layer ({@code div}-tag) which contains the graphical elements for the rating of an entry. This methods returns a HTML-snippet as string which can be inserted anywhere inside a {@code body}-element.
 *
 * @param dataObj a reference to the {@code CDaten}-class, needed for accessing the methods that retrieve the rating-values from entries.
 * @param entrynr the entry-number of the requested entry which rating should be created.
 * @param sourceframe a reference to the frame from where this function call came. needed for the html-formatting, since entries are differently formatted in the search window.
 * @return a HTML-snippet as string which can be inserted anywhere inside a {@code body}-element.
 */
private static String getEntryHeadline(Daten dataObj, int entrynr, int sourceframe) {
    // retrieve rating value
    float ratingvalue = dataObj.getZettelRating(entrynr);
    // init return value which will contain the html-snippet
    StringBuilder htmlrating = new StringBuilder("");
    // ***********************************************
    // count total words of entry
    // ***********************************************
    // get complete entry-content, i.e. title and content
    String wordcoutnstring = dataObj.getZettelTitle(entrynr) + " " + dataObj.getCleanZettelContent(entrynr);
    // split complete content at each word
    String[] words = wordcoutnstring.toLowerCase().
            replace("ä", "ae").
            replace("ö", "oe").
            replace("ü", "ue").
            replace("ß", "ss").
            split("\\W");
    // init wordcounter
    int wordcount = 0;
    // iterate all words of the entry
    for (String word : words) {
        // remove all non-letter-chars
        word = word.replace("([^A-Za-z0-9]+)", "");
        // trim spaces
        word = word.trim();
        // if we have a "word" with more than one char, count it as word...
        if (!word.isEmpty() /* && word.length()>1 */) {
            wordcount++;
        }
    }
    // ***********************************************
    // init div and table tag
    // ***********************************************
    htmlrating.append(System.lineSeparator()).append("<div class=\"entryrating\">");
    htmlrating.append("<table ");
    if (PlatformUtil.isJava7OnMac() || PlatformUtil.isJava7OnWindows()) {
        htmlrating.append("cellspacing=\"0\" ");
    }
    htmlrating.append("class=\"tabentryrating\"><tr>").append(System.lineSeparator());
    // ***********************************************
    // init entry heading with entry nr and word count
    // ***********************************************
    htmlrating.append("<td colspan=\"2\" class=\"leftcellentryrating\">");
    // when the displayed entry differs from the current activated, show this to the user
    if (entrynr != dataObj.getActivatedEntryNumber() && sourceframe != Constants.FRAME_SEARCH) {
        htmlrating.append(resourceMap.getString("zettelDesc"));
        htmlrating.append("<a class=\"elink\" href=\"#activatedEntry\">");
        htmlrating.append(" ").append(String.valueOf(dataObj.getActivatedEntryNumber())).append("&nbsp;</a>&raquo;");
        htmlrating.append("&nbsp;<a class=\"elink\" href=\"#cr_");
        htmlrating.append(String.valueOf(entrynr)).append("\">").append(String.valueOf(entrynr));
        htmlrating.append("&nbsp;</a>(").append(String.valueOf(wordcount)).append(" ").append(resourceMap.getString("activatedZettelWordCount")).append(")");
    } // else show usual entry nr
    else {
        htmlrating.append(resourceMap.getString("zettelDesc"));
        htmlrating.append(" ");
        htmlrating.append(String.valueOf(entrynr));
        htmlrating.append(" (").append(String.valueOf(wordcount)).append(" ").append(resourceMap.getString("activatedZettelWordCount")).append(")");
    }
    htmlrating.append("</td><td class=\"midcellentryrating\">");
    // ***********************************************
    // now we have to add the timestamp of the entry
    // ***********************************************
    htmlrating.append(getEntryTimestamp(dataObj, entrynr));
    // ***********************************************
    // entry rating
    // ***********************************************
    htmlrating.append("</td><td class=\"rightcellentryrating\">");
    // start hyperlink-tag
    htmlrating.append("<a class=\"rlink\" href=\"#rateentry").append(String.valueOf(entrynr)).append("\">");
    htmlrating.append(resourceMap.getString("ratingText")).append(": ");
    // count down 5 steps, so we have a maximum of five images
    int cnt = 5;
    // loop
    while (cnt-- > 0) {
        // add image. therefore, check whether the rating has at least one star
        if (ratingvalue >= 1.0) {
            htmlrating.append(getRatingSymbol(RATING_VALUE_FULL));
        } // if not, check whether at least a half point is needed
        else if (ratingvalue >= 0.5) {
            htmlrating.append(getRatingSymbol(RATING_VALUE_HALF));
        } // or, finally, use the image for no rating-points
        else {
            htmlrating.append(getRatingSymbol(RATING_VALUE_NONE));
        }
        // decrease rating value by one
        ratingvalue--;
    }
    // close hyperlink
    htmlrating.append("</a>");
    // close tag
    htmlrating.append("</td></tr>").append(System.lineSeparator());

    // check whether entry has manual links
    String[] manualLinksAsString = Daten.getManualLinksAsString(entrynr);
    if (manualLinksAsString != null && manualLinksAsString.length > 0) {
        // append manual links
        htmlrating.append("<tr><td class=\"crtitle\" valign=\"top\"><a href=\"#crt\">");
        htmlrating.append(resourceMap.getString("crossRefText")).append(":</a>&nbsp;</td><td class=\"mlink\" colspan=\"3\">");
        // create string builder
        StringBuilder crossrefs = new StringBuilder("");
        // iterate string array
        for (String ml : manualLinksAsString) {
            String title = "";
            try {
                title = dataObj.getZettelTitle(Integer.parseInt(ml));
                title = title.replace("\"", "").replace("'", "");
                title = title.trim();
            } catch (NumberFormatException ex) {
            }
            crossrefs.append("<a href=\"#cr_").append(ml).append("\" title=\"").append(title).append("\" alt=\"").append(title).append("\">").append(ml).append("</a>");
            crossrefs.append(" &middot; ");
        }
        // append string, but delete last 10 chars, which are " &middot; "
        htmlrating.append(crossrefs.toString().substring(0, crossrefs.length() - 10));
        htmlrating.append("</td></tr>").append(System.lineSeparator());
    }

    htmlrating.append("</table></div>").append(System.lineSeparator());
    // return result
    return htmlrating.toString();
}

Refactored Method:

/**
 * Creates an HTML layer ({@code div}-tag) containing the graphical elements for the rating of an entry.
 * Returns an HTML snippet as a string which can be inserted inside a {@code body}-element.
 *
 * @param dataObj a reference to the {@code Daten} class, needed for accessing methods that retrieve rating values from entries.
 * @param entrynr the entry number of the requested entry whose rating should be created.
 * @param sourceframe a reference to the frame from where this function call came, needed for HTML formatting.
 * @return an HTML snippet as a string which can be inserted inside a {@code body}-element.
 */
private static String getEntryHeadline(Daten dataObj, int entrynr, int sourceframe) {
    float ratingValue = dataObj.getZettelRating(entrynr);
    int wordCount = calculateWordCount(dataObj, entrynr);
    StringBuilder htmlRating = new StringBuilder();

    appendInitialHtml(htmlRating);
    appendEntryHeading(htmlRating, dataObj, entrynr, sourceframe, wordCount);
    appendEntryTimestamp(htmlRating, dataObj, entrynr);
    appendRatingHtml(htmlRating, entrynr, ratingValue);
    appendManualLinks(htmlRating, dataObj, entrynr);

    htmlRating.append("</table></div>").append(System

.lineSeparator());

    return htmlRating.toString();
}

private static int calculateWordCount(Daten dataObj, int entrynr) {
    String content = dataObj.getZettelTitle(entrynr) + " " + dataObj.getCleanZettelContent(entrynr);
    String[] words = content.toLowerCase()
                            .replace("ä", "ae")
                            .replace("ö", "oe")
                            .replace("ü", "ue")
                            .replace("ß", "ss")
                            .split("\\W+");
    int wordCount = 0;
    for (String word : words) {
        word = word.replaceAll("[^A-Za-z0-9]+", "").trim();
        if (!word.isEmpty()) {
            wordCount++;
        }
    }
    return wordCount;
}

private static void appendInitialHtml(StringBuilder htmlRating) {
    htmlRating.append(System.lineSeparator()).append("<div class=\"entryrating\">")
              .append("<table ")
              .append(PlatformUtil.isJava7OnMac() || PlatformUtil.isJava7OnWindows() ? "cellspacing=\"0\" " : "")
              .append("class=\"tabentryrating\"><tr>")
              .append(System.lineSeparator());
}

private static void appendEntryHeading(StringBuilder htmlRating, Daten dataObj, int entrynr, int sourceframe, int wordCount) {
    htmlRating.append("<td colspan=\"2\" class=\"leftcellentryrating\">")
              .append(resourceMap.getString("zettelDesc")).append(" ");
    if (entrynr != dataObj.getActivatedEntryNumber() && sourceframe != Constants.FRAME_SEARCH) {
        htmlRating.append("<a class=\"elink\" href=\"#activatedEntry\">")
                  .append(dataObj.getActivatedEntryNumber())
                  .append("&nbsp;</a>&raquo;&nbsp;<a class=\"elink\" href=\"#cr_")
                  .append(entrynr).append("\">")
                  .append(entrynr)
                  .append("&nbsp;</a>(")
                  .append(wordCount)
                  .append(" ")
                  .append(resourceMap.getString("activatedZettelWordCount"))
                  .append(")");
    } else {
        htmlRating.append(entrynr)
                  .append(" (")
                  .append(wordCount)
                  .append(" ")
                  .append(resourceMap.getString("activatedZettelWordCount"))
                  .append(")");
    }
    htmlRating.append("</td>");
}

private static void appendEntryTimestamp(StringBuilder htmlRating, Daten dataObj, int entrynr) {
    htmlRating.append("<td class=\"midcellentryrating\">")
              .append(getEntryTimestamp(dataObj, entrynr))
              .append("</td>");
}

private static void appendRatingHtml(StringBuilder htmlRating, int entrynr, float ratingValue) {
    htmlRating.append("<td class=\"rightcellentryrating\">")
              .append("<a class=\"rlink\" href=\"#rateentry").append(entrynr).append("\">")
              .append(resourceMap.getString("ratingText")).append(": ");
    for (int cnt = 5; cnt > 0; cnt--) {
        if (ratingValue >= 1.0) {
            htmlRating.append(getRatingSymbol(RATING_VALUE_FULL));
        } else if (ratingValue >= 0.5) {
            htmlRating.append(getRatingSymbol(RATING_VALUE_HALF));
        } else {
            htmlRating.append(getRatingSymbol(RATING_VALUE_NONE));
        }
        ratingValue--;
    }
    htmlRating.append("</a>")
              .append("</td></tr>")
              .append(System.lineSeparator());
}

private static void appendManualLinks(StringBuilder htmlRating, Daten dataObj, int entrynr) {
    String[] manualLinks = Daten.getManualLinksAsString(entrynr);
    if (manualLinks != null && manualLinks.length > 0) {
        htmlRating.append("<tr><td class=\"crtitle\" valign=\"top\"><a href=\"#crt\">")
                  .append(resourceMap.getString("crossRefText"))
                  .append(":</a>&nbsp;</td><td class=\"mlink\" colspan=\"3\">");

        StringBuilder crossRefs = new StringBuilder();
        for (String ml : manualLinks) {
            String title = "";
            try {
                title = dataObj.getZettelTitle(Integer.parseInt(ml)).replace("\"", "").replace("'", "").trim();
            } catch (NumberFormatException e) {
                // Log error or handle appropriately
            }
            crossRefs.append("<a href=\"#cr_")
                     .append(ml)
                     .append("\" title=\"")
                     .append(title)
                     .append("\" alt=\"")
                     .append(title)
                     .append("\">")
                     .append(ml)
                     .append("</a> &middot; ");
        }
        htmlRating.append(crossRefs.substring(0, crossRefs.length() - 10))
                  .append("</td></tr>")
                  .append(System.lineSeparator());
    }
}

Labels: refactoring, enhancement, code-quality

Assignee: [Assign to appropriate developer]

Comments: Feel free to add any additional information or concerns regarding this issue.

github-actions[bot] commented 1 week ago

This issue is stale because it has been open for 30 days with no activity.