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

ljacqu / wordeval / 14603510580

22 Apr 2025 07:59PM UTC coverage: 60.152% (+0.3%) from 59.867%
14603510580

push

github

ljacqu
Hunspell: Parse needaffix class and continuation classes

344 of 676 branches covered (50.89%)

67 of 93 new or added lines in 5 files covered. (72.04%)

1 existing line in 1 file now uncovered.

951 of 1581 relevant lines covered (60.15%)

3.37 hits per line

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

63.41
/src/main/java/ch/jalu/wordeval/dictionary/hunspell/HunspellUnmuncherService.java
1
package ch.jalu.wordeval.dictionary.hunspell;
2

3
import org.apache.commons.lang3.StringUtils;
4
import org.springframework.stereotype.Service;
5

6
import java.util.ArrayList;
7
import java.util.List;
8
import java.util.stream.Stream;
9

10
/**
11
 * Service to "unmunch" words (i.e. expand) based on Hunspell affix rules.
12
 */
13
@Service
14
public class HunspellUnmuncherService {
3✔
15

16
  /**
17
   * Processes the given dictionary lines and expands them based on the specified affix rules.
18
   *
19
   * @param lines the lines of the dictionary file (including affix information)
20
   * @param affixDefinition the dictionary's affix definitions
21
   * @return all words isolated and expanded
22
   */
23
  public Stream<String> unmunch(Stream<String> lines, HunspellAffixes affixDefinition) {
24
    return lines.flatMap(line -> unmunch(line, affixDefinition));
11✔
25
  }
26

27
  private Stream<String> unmunch(String line, HunspellAffixes affixDefinition) {
28
    int indexOfSlash = line.indexOf('/');
4✔
29
    if (indexOfSlash <= 0) {
2✔
30
      // We don't support empty strings as base
31
      return Stream.of(line);
3✔
32
    }
33

34
    String baseWord = line.substring(0, indexOfSlash);
5✔
35
    String affixFlagList = StringUtils.substringBefore(line.substring(indexOfSlash + 1), " ");
8✔
36
    List<String> affixFlags = affixDefinition.getFlagType().split(affixFlagList);
5✔
37

38
    List<String> results = new ArrayList<>();
4✔
39
    boolean includeBaseWord = affixDefinition.getNeedAffixFlag() == null
5✔
40
        || !affixFlags.contains(affixDefinition.getNeedAffixFlag());
7!
41
    if (includeBaseWord) {
2✔
42
      results.add(baseWord);
4✔
43
    }
44

45
    for (String affixFlag : affixFlags) {
10✔
46
      applyAffixRule(baseWord, affixFlag, results, affixDefinition);
6✔
47
    }
1✔
48
    return results.stream();
3✔
49
  }
50

51
  // TODO: crossproduct flag is not considered.
52

53
  private void applyAffixRule(String baseWord, String affixFlag,
54
                              List<String> results, HunspellAffixes affixDefinition) {
55
    List<AffixClass.AffixRule> rules = affixDefinition.streamThroughMatchingRules(baseWord, affixFlag).toList();
6✔
56
    for (AffixClass.AffixRule rule : rules) {
10✔
57
      String newWord = rule.applyRule(baseWord);
4✔
58
      results.add(newWord);
4✔
59
      for (String continuationClass : rule.getContinuationClasses()) {
11✔
60
        applyAffixRule(newWord, continuationClass, results, affixDefinition);
6✔
61
      }
1✔
62
    }
1✔
63
  }
1✔
64

65
  // TODO: clean up
66
  // ChatGPT's opinion is to call this with (…, true, true) in the beginning, but this produces even fewer forms
67
  // than what the documentation suggests...
68
  private void applyAffixRule(String baseWord, String affixFlag,
69
                              List<String> results, HunspellAffixes affixDefinition,
70
                              boolean allowPrefixes, boolean allowSuffixes) {
NEW
71
    List<AffixClass.AffixRule> rules = affixDefinition.streamThroughMatchingRules(baseWord, affixFlag).toList();
×
72

NEW
73
    for (AffixClass.AffixRule rule : rules) {
×
NEW
74
      boolean isPrefix = rule.getType() == AffixType.PFX;
×
NEW
75
      boolean isSuffix = rule.getType() == AffixType.SFX;
×
76

77
      // Only apply if allowed in this stage
NEW
78
      if ((isPrefix && !allowPrefixes) || (isSuffix && !allowSuffixes)) {
×
NEW
79
        continue;
×
80
      }
81

NEW
82
      String newWord = rule.applyRule(baseWord);
×
NEW
83
      results.add(newWord);
×
84

85
      // Prefix before suffix: pass allowPrefixes = false when doing suffix pass
NEW
86
      for (String continuationClass : rule.getContinuationClasses()) {
×
NEW
87
        applyAffixRule(newWord, continuationClass, results, affixDefinition,
×
88
            // Once we've applied a prefix, only allow suffixes afterward
NEW
89
            isPrefix ? false : allowPrefixes,
×
NEW
90
            isSuffix ? false : allowSuffixes
×
91
        );
NEW
92
      }
×
NEW
93
    }
×
UNCOV
94
  }
×
95
}
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