001package org.xbib.standardnumber; 002 003import java.util.regex.Matcher; 004import java.util.regex.Pattern; 005 006/** 007 * Z39.56 Serial Item and Contribution Identifier 008 * 009 * The SICI code (Serial Item and Contribution Identifier) is described in the 010 * American standard ANSI/NISO Z39.56. The SICI code is known among 011 * international scientific publishers and reproduction rights agencies. 012 * The SICI even provides for the unambiguous identification of each article 013 * or contribution published in a given issue of a serial publication. 014 * 015 * The SICI contains: 016 * 017 * - the ISSN 018 * 019 * - the date of publication, between brackets and formatted according to the 020 * formula YYYYMM 021 * 022 * - the issue number 023 * 024 * - the version number of the standard, here 1, preceded by a semicolon 025 * 026 * - and lastly a hyphen which precedes the control character calculated 027 * on the basis of all the preceding characters 028 * 029 * Example: 030 * 0095-4403(199502/03)21:3<12:WATIIB>2.0.TX;2-J 031 * 032 */ 033public class SICI extends AbstractStandardNumber implements Comparable<SICI>, StandardNumber { 034 035 private static final Pattern PATTERN = Pattern.compile("[\\p{Graph}\\p{Punct}]{12,64}"); 036 037 private String value; 038 039 private String formatted; 040 041 private boolean createWithChecksum; 042 043 @Override 044 public String type() { 045 return "sici"; 046 } 047 048 @Override 049 public int compareTo(SICI sici) { 050 return sici != null ? normalizedValue().compareTo(sici.normalizedValue()) : -1; 051 } 052 053 @Override 054 public SICI set(CharSequence value) { 055 this.value = value != null ? value.toString() : null; 056 return this; 057 } 058 059 @Override 060 public SICI createChecksum(boolean createWithChecksum) { 061 this.createWithChecksum = createWithChecksum; 062 return this; 063 } 064 065 @Override 066 public SICI normalize() { 067 Matcher m = PATTERN.matcher(value); 068 if (m.find()) { 069 this.value = clean(value.substring(m.start(), m.end())); 070 } 071 return this; 072 } 073 074 @Override 075 public boolean isValid() { 076 return value != null && !value.isEmpty() && check(); 077 } 078 079 @Override 080 public SICI verify() throws NumberFormatException { 081 if (value == null) { 082 throw new NumberFormatException("invalid"); 083 } 084 if (!check()) { 085 throw new NumberFormatException("bad checksum"); 086 } 087 return this; 088 } 089 090 @Override 091 public String normalizedValue() { 092 return value; 093 } 094 095 @Override 096 public String format() { 097 return formatted; 098 } 099 100 @Override 101 public SICI reset() { 102 this.value = null; 103 this.formatted = null; 104 this.createWithChecksum = false; 105 return this; 106 } 107 108 private final static String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#"; 109 110 private final static int modulus = ALPHABET.length(); 111 112 private boolean check() { 113 int l = value.length() - 1; 114 int val; 115 int sum = 0; 116 for (int i = 0; i < l; i++) { 117 val = ALPHABET.indexOf(value.charAt(i)); 118 sum += val * (i %2 == 0 ? 1 : 3); 119 } 120 int chk = modulus - sum % modulus; 121 if (createWithChecksum) { 122 char ch = chk > 35 ? '#' : chk > 9 ? (char)(10 + (chk - 'A')) : (char)('0' + chk); 123 value = value.substring(0, l) + ch; 124 } 125 char digit = value.charAt(l); 126 int chk2 = digit == '#' ? 36 : (digit >= '0' && digit <= '9') ? digit - '0' : digit -'A' + 10; 127 return chk == chk2; 128 } 129 130 private String clean(String raw) { 131 if (raw == null) { 132 return null; 133 } 134 StringBuilder sb = new StringBuilder(raw); 135 int pos = sb.indexOf("SICI "); 136 if (pos >= 0) { 137 sb = new StringBuilder(sb.substring(pos + 5)); 138 } 139 this.formatted = "SICI " + sb; 140 return sb.toString(); 141 } 142}