• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

felleslosninger / einnsyn-backend / 23341388835

20 Mar 2026 11:45AM UTC coverage: 85.246% (+0.07%) from 85.174%
23341388835

push

github

web-flow
Merge pull request #616 from felleslosninger/EIN-4857-nedlasting-av-fulltekstdokument

EIN-4857: Fulltext downloads

2637 of 3537 branches covered (74.55%)

Branch coverage included in aggregate %.

7850 of 8765 relevant lines covered (89.56%)

3.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.15
src/main/java/no/einnsyn/backend/utils/SlugGenerator.java
1
package no.einnsyn.backend.utils;
2

3
import java.text.Normalizer;
4
import java.util.Locale;
5
import java.util.Random;
6
import java.util.regex.Pattern;
7

8
/**
9
 * Utility class for generating URL-friendly slugs from text strings. Converts titles and other text
10
 * into clean, lowercase, hyphenated slugs suitable for use in URLs.
11
 */
12
public class SlugGenerator {
13

14
  private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}+");
3✔
15
  private static final Pattern NON_ALPHANUMERIC_PATTERN = Pattern.compile("[^a-z0-9]+");
3✔
16
  private static final Pattern LEADING_TRAILING_HYPHENS_PATTERN = Pattern.compile("^-+|-+$");
4✔
17
  private static final int MAX_SLUG_LENGTH = 75;
18

19
  private SlugGenerator() {}
20

21
  /**
22
   * Generates a URL-friendly slug from the given text.
23
   *
24
   * <p>This method converts input text into a clean, lowercase, hyphenated slug by:
25
   *
26
   * <ul>
27
   *   <li>Converting to lowercase
28
   *   <li>Replacing Scandinavian characters (æ, ø, å) with ASCII equivalents
29
   *   <li>Normalizing Unicode characters and removing diacritics (é→e, è→e, ä→a, ö→o, etc.)
30
   *   <li>Replacing non-alphanumeric characters with hyphens
31
   *   <li>Removing leading and trailing hyphens
32
   * </ul>
33
   *
34
   * @param text the input text (e.g., a web page title) to convert to a slug
35
   * @return a lowercase, hyphenated slug suitable for URLs, or null if input is null or blank
36
   */
37
  public static String generate(String text) {
38
    return generate(text, false);
4✔
39
  }
40

41
  /**
42
   * Generates a URL-friendly slug from the given text with an optional random suffix.
43
   *
44
   * @param text the input text (e.g., a web page title) to convert to a slug
45
   * @param randomSuffix whether to append a random alphanumeric suffix (e.g., "-a1b2c3")
46
   * @return a lowercase, hyphenated slug suitable for URLs, or null if input is null or blank
47
   */
48
  public static String generate(String text, boolean randomSuffix) {
49
    if (text == null || text.isBlank()) {
5✔
50
      return null;
2✔
51
    }
52

53
    var slug = text.toLowerCase(Locale.ROOT);
4✔
54

55
    // Replace Scandinavian characters not handled by Normalizer
56
    slug = transliterate(slug);
3✔
57

58
    // Normalize and remove diacritics
59
    slug = Normalizer.normalize(slug, Normalizer.Form.NFD);
4✔
60
    slug = DIACRITICS_PATTERN.matcher(slug).replaceAll("");
6✔
61

62
    // Remove non-alphanumeric characters and replace with hyphens
63
    slug = NON_ALPHANUMERIC_PATTERN.matcher(slug).replaceAll("-");
6✔
64
    slug = LEADING_TRAILING_HYPHENS_PATTERN.matcher(slug).replaceAll("");
6✔
65

66
    // Clean up multiple consecutive hyphens and leading/trailing hyphens
67
    slug = slug.replaceAll("-+", "-");
5✔
68
    slug = LEADING_TRAILING_HYPHENS_PATTERN.matcher(slug).replaceAll("");
6✔
69

70
    if (slug.isEmpty()) {
3✔
71
      return null;
2✔
72
    }
73

74
    // Truncate if exceeds max length
75
    slug = truncateToMaxLength(slug);
3✔
76

77
    // Add random suffix if applicable
78
    if (randomSuffix) {
2✔
79
      slug = slug + "-" + generateRandomSuffix();
4✔
80
    }
81

82
    return slug;
2✔
83
  }
84

85
  /**
86
   * Truncates the slug to the maximum allowed length, ensuring truncation happens at word
87
   * boundaries (hyphens) to avoid cutting words in the middle.
88
   *
89
   * @param slug the slug to truncate
90
   * @return the truncated slug, or the original if it's within the limit
91
   */
92
  private static String truncateToMaxLength(String slug) {
93
    if (slug.length() <= MAX_SLUG_LENGTH) {
4✔
94
      return slug;
2✔
95
    }
96

97
    // Truncate at the last hyphen before the max length
98
    var truncated = slug.substring(0, MAX_SLUG_LENGTH);
5✔
99
    var lastHyphen = truncated.lastIndexOf('-');
4✔
100

101
    if (lastHyphen > 0) {
2!
102
      return truncated.substring(0, lastHyphen);
5✔
103
    }
104

105
    // If no hyphen found, just truncate at max length
106
    return truncated;
×
107
  }
108

109
  /**
110
   * Generates a random alphanumeric suffix for slug uniqueness.
111
   *
112
   * @return a random 6-character lowercase alphanumeric string
113
   */
114
  private static String generateRandomSuffix() {
115
    var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
2✔
116
    var random = new Random();
4✔
117
    var suffix = new StringBuilder(6);
5✔
118

119
    for (int i = 0; i < 6; i++) {
7✔
120
      suffix.append(chars.charAt(random.nextInt(chars.length())));
9✔
121
    }
122

123
    return suffix.toString();
3✔
124
  }
125

126
  /**
127
   * Transliterates Scandinavian and Germanic characters to their ASCII equivalents. Handles
128
   * characters that cannot be decomposed by Unicode normalization.
129
   *
130
   * @param text the input text
131
   * @return text with Scandinavian/Germanic characters replaced (æ→ae, ø→o, å→aa, ä→a, ö→o)
132
   */
133
  private static String transliterate(String text) {
134
    return text.replace("æ", "ae")
7✔
135
        .replace("ø", "o")
3✔
136
        .replace("å", "aa")
3✔
137
        .replace("ä", "a")
3✔
138
        .replace("ö", "o");
1✔
139
  }
140
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc