001package org.xbib.standardnumber; 002 003import java.util.regex.Matcher; 004import java.util.regex.Pattern; 005 006/** 007 * ISO 21047 International Standard Text Code (ISTC) 008 * 009 * The International Standard Text Code (ISTC) is a numbering system for the unique identification 010 * of text-based works; the term “work” can refer to any content appearing in conventional 011 * printed books, audio-books, static e-books or enhanced digital books, as well as content 012 * which might appear in a newspaper or journal. 013 * 014 * The ISTC provides sales analysis systems, retail websites, library catalogs and other 015 * bibliographic systems with a method of automatically linking together publications 016 * of the “same content” and/or “related content”, thus improving discoverability of 017 * products and efficiencies. 018 * 019 * An ISTC number is the link between a user’s search for a piece of content and the 020 * ultimate sale or loan of a publication. 021 * 022 * The standard was formally published in March 2009 023 * 024 * Checksum algorithm is ISO 7064 MOD 16/3 025 * 026 */ 027public class ISTC extends AbstractStandardNumber implements Comparable<ISTC>, StandardNumber { 028 029 private static final Pattern PATTERN = Pattern.compile("[\\p{Alnum}\\-\\s]{12,24}"); 030 031 private String value; 032 033 private String formatted; 034 035 private boolean createWithChecksum; 036 037 @Override 038 public String type() { 039 return "istc"; 040 } 041 042 @Override 043 public int compareTo(ISTC istc) { 044 return istc != null ? normalizedValue().compareTo(istc.normalizedValue()) : -1; 045 } 046 047 @Override 048 public ISTC set(CharSequence value) { 049 this.value = value != null ? value.toString() : null; 050 return this; 051 } 052 053 @Override 054 public ISTC createChecksum(boolean createWithChecksum) { 055 this.createWithChecksum = createWithChecksum; 056 return this; 057 } 058 059 @Override 060 public ISTC normalize() { 061 Matcher m = PATTERN.matcher(value); 062 if (m.find()) { 063 this.value = clean(value.substring(m.start(), m.end())); 064 } 065 return this; 066 } 067 068 @Override 069 public boolean isValid() { 070 return value != null && !value.isEmpty() && check(); 071 } 072 073 @Override 074 public ISTC verify() throws NumberFormatException { 075 if (value == null || value.isEmpty()) { 076 throw new NumberFormatException("invalid"); 077 } 078 if (!check()) { 079 throw new NumberFormatException("bad createChecksum"); 080 } 081 return this; 082 } 083 084 @Override 085 public String normalizedValue() { 086 return value; 087 } 088 089 @Override 090 public String format() { 091 return formatted; 092 } 093 094 @Override 095 public ISTC reset() { 096 this.value = null; 097 this.formatted = null; 098 this.createWithChecksum = false; 099 return this; 100 } 101 102 private boolean check() { 103 int l = value.length() - 1; 104 int checksum = 0; 105 int weight; 106 int factor; 107 int val; 108 for (int i = 0; i < l; i++) { 109 val = value.charAt(i); 110 if (val >= 'A' && val <= 'Z') { 111 val = 10 + (val - 'A'); 112 } 113 if (val >= '0' && val <= '9') { 114 val = val - '0'; 115 } 116 factor = i % 4 < 2 ? 1 : 5; 117 weight = (12 - 2 * (i % 4)) - factor; // --> 11,9,3,1 118 checksum += val * weight; 119 } 120 int chk = checksum % 16; 121 if (createWithChecksum) { 122 char ch = 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 >= '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 i = sb.indexOf("-"); 136 while (i >= 0) { 137 sb.deleteCharAt(i); 138 i = sb.indexOf("-"); 139 } 140 i = sb.indexOf(" "); 141 while (i >= 0) { 142 sb.deleteCharAt(i); 143 i = sb.indexOf(" "); 144 } 145 if (sb.indexOf("ISTC") == 0) { 146 sb = new StringBuilder(sb.substring(4)); 147 } 148 if (sb.length() > 15) { 149 this.formatted = "ISTC " 150 + sb.substring(0,3) + "-" 151 + sb.substring(3,7) + "-" 152 + sb.substring(7,15) + "-" 153 + sb.substring(15); 154 } 155 return sb.toString(); 156 } 157}