Skip to content

Commit efe85ac

Browse files
authored
Merge pull request #1 from aplazo/feature/improve-rfc-generation
feat: enhance RFC code generation to support special characters
2 parents b2ca19e + 67c0733 commit efe85ac

File tree

5 files changed

+151
-76
lines changed

5 files changed

+151
-76
lines changed

build.gradle

+39-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
apply plugin: 'java'
22
apply plugin: 'me.tatarka.retrolambda'
3+
apply plugin: 'maven-publish'
34

45
sourceCompatibility = 1.8
5-
version = '2.0.3-SNAPSHOT'
6+
version = '2.1.0'
67

78
buildscript {
89
repositories {
910
mavenCentral()
1011
}
1112

1213
dependencies {
13-
classpath 'me.tatarka:gradle-retrolambda:3.2.4'
14+
classpath 'me.tatarka:gradle-retrolambda:3.7.1'
1415
}
1516
}
1617

@@ -23,11 +24,46 @@ dependencies {
2324
compile 'org.apache.commons:commons-lang3:3.0'
2425
compile 'net.sourceforge.streamsupport:streamsupport:1.4.1'
2526

26-
testCompile 'junit:junit:4.12'
27+
testCompile 'junit:junit:4.13.1'
2728
testCompile 'org.hamcrest:hamcrest-core:1.3'
2829
testCompile 'org.hamcrest:hamcrest-library:1.3'
2930
}
3031

32+
publishing {
33+
publications {
34+
maven(MavenPublication) {
35+
groupId = 'com.josketres'
36+
artifactId = 'rfcfacil'
37+
version = "2.1.0"
38+
from components.java
39+
40+
pom {
41+
name = 'rfcfacil'
42+
description = 'Libreria para calcular el Registro Federal de Contribuyentes en México (RFC) en Java.'
43+
}
44+
}
45+
}
46+
repositories {
47+
maven {
48+
credentials {
49+
username = "<nexus-maven-username>"
50+
password = "<nexus-maven-password>"
51+
}
52+
url = "https://nexus.aplazo.dev/repository/maven-releases"
53+
}
54+
}
55+
}
56+
57+
tasks.register('sourcesJar', Jar) {
58+
dependsOn classes
59+
classifier = 'sources'
60+
from sourceSets.main.allSource
61+
}
62+
63+
artifacts {
64+
archives sourcesJar
65+
}
66+
3167
// http://zserge.com/blog/gradle-maven-publish.html
3268
// export JAVA6_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/
3369
// ./gradlew uploadArchives

src/main/java/com/josketres/rfcfacil/HomoclaveCalculator.java

+16-28
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import java.util.HashMap;
66
import java.util.Map;
77

8+
import static java.lang.String.valueOf;
9+
810
/**
911
* Calculates a two-digits code known as "homoclave".
1012
*/
1113
class HomoclaveCalculator {
1214

1315
private static final String HOMOCLAVE_DIGITS = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ";
14-
private static final Map<String, String> FULL_NAME_MAPPING = new HashMap<String, String>();
16+
private static final Map<String, String> FULL_NAME_MAPPING = new HashMap<>();
1517

1618
static {
1719
FULL_NAME_MAPPING.put(" ", "00");
@@ -68,26 +70,22 @@ public HomoclaveCalculator(HomoclavePerson person) {
6870
}
6971

7072
public String calculate() {
71-
7273
normalizeFullName();
7374
mapFullNameToDigitsCode();
7475
sumPairsOfDigits();
7576
buildHomoclave();
76-
7777
return homoclave;
7878
}
7979

8080
private void buildHomoclave() {
81-
8281
int lastThreeDigits = pairsOfDigitsSum % 1000;
8382
int quo = lastThreeDigits / 34;
8483
int reminder = lastThreeDigits % 34;
8584
homoclave = String.valueOf(HOMOCLAVE_DIGITS.charAt(quo))
86-
+ String.valueOf(HOMOCLAVE_DIGITS.charAt(reminder));
85+
+ HOMOCLAVE_DIGITS.charAt(reminder);
8786
}
8887

8988
private void sumPairsOfDigits() {
90-
9189
pairsOfDigitsSum = 0;
9290
for (int i = 0; i < mappedFullName.length() - 1; i++) {
9391
int intNum1 = Integer.parseInt(mappedFullName.substring(i, i + 2));
@@ -97,15 +95,14 @@ private void sumPairsOfDigits() {
9795
}
9896

9997
private void mapFullNameToDigitsCode() {
100-
101-
mappedFullName = "0";
98+
StringBuilder mappedFullNameBuilder = new StringBuilder("0");
10299
for (int i = 0; i < fullName.length(); i++) {
103-
mappedFullName += mapCharacterToTwoDigitCode(String.valueOf(fullName.charAt(i)));
100+
mappedFullNameBuilder.append(mapCharacterToTwoDigitCode(valueOf(fullName.charAt(i))));
104101
}
102+
mappedFullName = mappedFullNameBuilder.toString();
105103
}
106104

107105
private String mapCharacterToTwoDigitCode(String c) {
108-
109106
if (!FULL_NAME_MAPPING.containsKey(c)) {
110107
throw new IllegalArgumentException("No two-digit-code mapping for char: " + c);
111108
} else {
@@ -114,27 +111,18 @@ private String mapCharacterToTwoDigitCode(String c) {
114111
}
115112

116113
private void normalizeFullName() {
117-
118114
String rawFullName = person.getFullNameForHomoclave().toUpperCase();
119-
120-
fullName = StringUtils.stripAccents(rawFullName);
121-
fullName = fullName.replaceAll("[\\-\\.',]", ""); // remove .'-,
122-
fullName = addMissingCharToFullName(rawFullName, 'Ñ');
123-
115+
fullName = stripAccentsExcludingNTilde(rawFullName);
116+
fullName = fullName.replaceAll("[^A-Z0-9&Ñ ]", "");
124117
}
125118

126-
private String addMissingCharToFullName(String rawFullName, char missingChar) {
119+
private String stripAccentsExcludingNTilde(String input) {
120+
if (StringUtils.isEmpty(input))
121+
return input;
127122

128-
int index = rawFullName.indexOf(missingChar);
129-
if (index == -1) {
130-
return fullName;
131-
}
132-
133-
StringBuilder newFullName = new StringBuilder(fullName);
134-
while (index >= 0) {
135-
newFullName.setCharAt(index, missingChar);
136-
index = rawFullName.indexOf(missingChar, index + 1);
137-
}
138-
return newFullName.toString();
123+
input = input.toUpperCase();
124+
input = input.replace("Ñ", "$");
125+
input = StringUtils.stripAccents(input);
126+
return input.replace("$", "Ñ");
139127
}
140128
}

src/main/java/com/josketres/rfcfacil/NaturalPersonTenDigitsCodeCalculator.java

+35-44
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,21 @@ class NaturalPersonTenDigitsCodeCalculator {
1616
private final NaturalPerson person;
1717

1818
private static final String[] SPECIAL_PARTICLES =
19-
{"DE", "LA", "LAS", "MC", "VON", "DEL", "LOS", "Y", "MAC", "VAN", "MI"};
20-
21-
private static final String[] FORBIDDEN_WORDS = { "BUEI", "BUEY", "CACA", "CACO", "CAGA", "CAGO", "CAKA", "CAKO", "COGE", "COJA", "COJE", "COJI", "COJO", "CULO", "FETO", "GUEY", "JOTO", "KACA", "KACO", "KAGA", "KAGO", "KOGE", "KOJO", "KAKA", "KULO", "MAME", "MAMO", "MEAR", "MEAS", "MEON", "MION", "MOCO", "MULA", "PEDA", "PEDO", "PENE", "PUTA", "PUTO", "QULO", "RATA", "RUIN" };
19+
{"DAS", "DA", "DEL", "DER", "DE", "DIE", "DI", "DD", "EL", "LES", "LA", "LOS", "LAS", "LES", "LE", "MAC", "MC", "VAN", "VON", "Y"};
2220

21+
private static final String[] FORBIDDEN_WORDS = {"BACA", "BAKA", "BUEI", "BUEY", "CACA", "CACO", "CAGA", "CAGO", "CAKA", "CAKO", "COGE", "COGI", "COJA", "COJE", "COJI", "COJO", "COLA", "CULO", "FALO", "FETO", "GETA", "GUEI", "GUEY", "JETA", "JOTO", "KACA", "KACO", "KAGA", "KAGO", "KAKA", "KAKO", "KOGE", "KOGI", "KOJA", "KOJE", "KOJI", "KOJO", "KOLA", "KULO", "LILO", "LOCA", "LOCO", "LOKA", "LOKO", "MAME", "MAMO", "MEAR", "MEAS", "MEON", "MIAR", "MION", "MOCO", "MOKO", "MULA", "MULO", "NACA", "NACO", "PEDA", "PEDO", "PENE", "PIPI", "PITO", "POPO", "PUTA", "PUTO", "QULO", "RATA", "ROBA", "ROBE", "ROBO", "RUIN", "SENO", "TETA", "VACA", "VAGA", "VAGO", "VAKA", "VUEI", "VUEY", "WUEI", "WUEY"};
2322

2423
NaturalPersonTenDigitsCodeCalculator(NaturalPerson person) {
25-
2624
this.person = person;
2725
}
2826

2927
public String calculate() {
30-
31-
return obfuscateForbiddenWords(nameCode()) + birthdayCode();
28+
String a = nameCode();
29+
String b = StringUtils.stripAccents(a);
30+
return obfuscateForbiddenWords(b) + birthdayCode();
3231
}
3332

3433
private String obfuscateForbiddenWords(String nameCode) {
35-
3634
for (String forbidden : FORBIDDEN_WORDS) {
3735
if (forbidden.equals(nameCode)) {
3836
return nameCode.substring(0, 3) + "X";
@@ -42,7 +40,6 @@ private String obfuscateForbiddenWords(String nameCode) {
4240
}
4341

4442
private String nameCode() {
45-
4643
if (isFirstLastNameEmpty()) {
4744
return firstLastNameEmptyForm();
4845
} else if (isSecondLastNameEmpty()) {
@@ -55,114 +52,108 @@ private String nameCode() {
5552
}
5653

5754
private String secondLastNameEmptyForm() {
58-
5955
return firstTwoLettersOf(person.firstLastName)
6056
+ firstTwoLettersOf(filterName(person.name));
6157
}
6258

6359
private String birthdayCode() {
64-
6560
return lastTwoDigitsOf(person.year)
6661
+ formattedInTwoDigits(person.month)
6762
+ formattedInTwoDigits(person.day);
6863
}
6964

7065
private boolean isSecondLastNameEmpty() {
71-
7266
return StringUtils.isEmpty(normalize(person.secondLastName));
7367
}
7468

7569
private String firstLastNameEmptyForm() {
76-
7770
return firstTwoLettersOf(person.secondLastName)
7871
+ firstTwoLettersOf(filterName(person.name));
7972
}
8073

8174
private boolean isFirstLastNameEmpty() {
82-
8375
return StringUtils.isEmpty(normalize(person.firstLastName));
8476
}
8577

8678
private String firstLastNameTooShortForm() {
87-
8879
return firstLetterOf(person.firstLastName)
8980
+ firstLetterOf(person.secondLastName)
9081
+ firstTwoLettersOf(filterName(person.name));
9182
}
9283

9384
private String firstTwoLettersOf(String word) {
94-
95-
String normalizedWord = normalize(word);
96-
return normalizedWord.substring(0, 2);
85+
String normalizedWord = normalize(word).replace(" ", "");
86+
return normalizedWord.length() > 1 ? normalizedWord.substring(0, 2) : normalizedWord.concat("X");
9787
}
9888

9989
private boolean isFirstLastNameIsTooShort() {
100-
10190
return normalize(person.firstLastName).length() <= 2;
10291
}
10392

10493
private String normalForm() {
105-
10694
return firstLetterOf(person.firstLastName)
10795
+ firstVowelExcludingFirstCharacterOf(person.firstLastName)
10896
+ firstLetterOf(person.secondLastName)
10997
+ firstLetterOf(filterName(person.name));
11098
}
11199

112100
private String filterName(String name) {
113-
114-
return normalize(name)
115-
.trim()
116-
.replaceFirst("^(MA|MA.|MARIA|JOSE)\\s+", "");
101+
return normalize(name).trim()
102+
.replaceFirst("^(MARIA|MA\\.|MA|M\\.|M|JOSE|J|J\\.|DA|DAS|DE|DEL|DER|DI|DIE|DD|EL|LA|LAS|LOS|LE|LES|MAC|MC|VAN|VON|Y)\\s+", "");
117103
}
118104

119105
private String formattedInTwoDigits(int number) {
120-
121106
return String.format(Locale.getDefault(), "%02d", number);
122107
}
123108

124109
private String lastTwoDigitsOf(int number) {
125-
126110
return formattedInTwoDigits(number % 100);
127111
}
128112

129113
private String firstLetterOf(String word) {
130-
131114
String normalizedWord = normalize(word);
132115
return String.valueOf(normalizedWord.charAt(0));
133116
}
134117

135118
private String normalize(String word) {
136-
137-
if (StringUtils.isEmpty(word)) {
119+
if (StringUtils.isEmpty(word))
138120
return word;
139-
} else {
140-
String normalizedWord = StringUtils.stripAccents(word).toUpperCase();
141-
return removeSpecialParticles(normalizedWord, SPECIAL_PARTICLES);
121+
122+
String cleanedWord = word.replaceAll("[\\-.',´`’\\\\/]", "");
123+
if (StringUtils.isEmpty(cleanedWord)) {
124+
return cleanedWord;
142125
}
143-
}
144126

145-
private String removeSpecialParticles(String word, String[] specialParticles) {
127+
String normalizedWord = stripAccentsExcludingNTilde(cleanedWord);
146128

147-
StringBuilder newWord = new StringBuilder(word);
148-
for (String particle : specialParticles) {
149-
String[] particlePositions = {particle + " ", " " + particle};
150-
for (String p : particlePositions)
151-
while (newWord.toString().contains(p)) {
152-
int i = newWord.toString().indexOf(p);
153-
newWord.delete(i, i + p.length());
154-
}
155-
}
156-
return newWord.toString();
129+
return removeSpecialParticles(normalizedWord);
157130
}
158131

159-
private String firstVowelExcludingFirstCharacterOf(String word) {
132+
private String removeSpecialParticles(String word) {
133+
String particlesRegex = String.join("|", SPECIAL_PARTICLES); // convert the array to a regex OR sequence
134+
Pattern pattern = Pattern.compile("\\b(" + particlesRegex + ")\\b", Pattern.CASE_INSENSITIVE); // match the particles only if they are whole words
135+
Matcher matcher = pattern.matcher(word);
136+
String result = matcher.replaceAll(" "); // replace all special particles with space
137+
return result.trim(); // remove any leading and trailing spaces
138+
}
160139

140+
private String firstVowelExcludingFirstCharacterOf(String word) {
161141
String normalizedWord = normalize(word).substring(1);
162142
Matcher m = VOWEL_PATTERN.matcher(normalizedWord);
163143
if (!m.find()) {
164144
return "X";
165145
}
166146
return String.valueOf(normalizedWord.charAt(m.start()));
167147
}
148+
149+
private String stripAccentsExcludingNTilde(String input) {
150+
if (StringUtils.isEmpty(input))
151+
return input;
152+
153+
input = input.toUpperCase();
154+
input = input.replace("Ñ", "$");
155+
input = StringUtils.stripAccents(input);
156+
input = input.replace("$", "Ñ");
157+
return input.replaceAll("[^A-Z0-9&Ñ ]", "");
158+
}
168159
}

src/test/java/com/josketres/rfcfacil/NaturalPersonTenDigitsCodeCalculatorTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void should_use_second_name_if_person_has_multiple_names_and_first_name_i
7777
@Test
7878
public void should_use_second_name_if_person_has_multiple_names_and_first_name_is_maria() {
7979

80-
assertThat(tenDigitsCode("María Luisa", "Ramírez", "Sánchez", 13, 12, 1970), equalTo("RASL701213"));
80+
assertThat(tenDigitsCode("María del Luisa", "Ramírez", "Sánchez", 13, 12, 1970), equalTo("RASL701213"));
8181
}
8282

8383
@Test
@@ -117,6 +117,11 @@ public void should_use_ma_when_first_name_is_not_Maria() {
117117
assertThat(tenDigitsCode("Marco Antonio", "Cano", "Barraza", 13, 12, 1970), equalTo("CABM701213"));
118118
}
119119

120+
@Test
121+
public void should_not_fail_for_names_with_special_characters(){
122+
assertThat(tenDigitsCode("Jesus Antonio","López", "Ventura", 17,8,2004),equalTo("LOVJ040817"));
123+
}
124+
120125
private String tenDigitsCode(String name,
121126
String firstLastName,
122127
String secondLastName,

0 commit comments

Comments
 (0)