| /* |
| * Copyright (C) 2010 ZXing authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * These authors would like to acknowledge the Spanish Ministry of Industry, |
| * Tourism and Trade, for the support in the project TSI020301-2008-2 |
| * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled |
| * Mobile Dynamic Environments", led by Treelogic |
| * ( http://www.treelogic.com/ ): |
| * |
| * http://www.piramidepse.com/ |
| */ |
| |
| package com.google.zxing.oned.rss.expanded.decoders; |
| |
| import com.google.zxing.NotFoundException; |
| import com.google.zxing.common.BitArray; |
| |
| /** |
| * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) |
| * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) |
| */ |
| final class GeneralAppIdDecoder { |
| |
| private final BitArray information; |
| private final CurrentParsingState current = new CurrentParsingState(); |
| private final StringBuffer buffer = new StringBuffer(); |
| |
| GeneralAppIdDecoder(BitArray information){ |
| this.information = information; |
| } |
| |
| String decodeAllCodes(StringBuffer buff, int initialPosition) throws NotFoundException { |
| int currentPosition = initialPosition; |
| String remaining = null; |
| do{ |
| DecodedInformation info = this.decodeGeneralPurposeField(currentPosition, remaining); |
| String parsedFields = FieldParser.parseFieldsInGeneralPurpose(info.getNewString()); |
| buff.append(parsedFields); |
| if(info.isRemaining()) { |
| remaining = String.valueOf(info.getRemainingValue()); |
| } else { |
| remaining = null; |
| } |
| |
| if(currentPosition == info.getNewPosition()) {// No step forward! |
| break; |
| } |
| currentPosition = info.getNewPosition(); |
| }while(true); |
| |
| return buff.toString(); |
| } |
| |
| private boolean isStillNumeric(int pos) { |
| // It's numeric if it still has 7 positions |
| // and one of the first 4 bits is "1". |
| if(pos + 7 > this.information.size){ |
| return pos + 4 <= this.information.size; |
| } |
| |
| for(int i = pos; i < pos + 3; ++i) { |
| if (this.information.get(i)) { |
| return true; |
| } |
| } |
| |
| return this.information.get(pos + 3); |
| } |
| |
| private DecodedNumeric decodeNumeric(int pos) { |
| if(pos + 7 > this.information.size){ |
| int numeric = extractNumericValueFromBitArray(pos, 4); |
| if(numeric == 0) { |
| return new DecodedNumeric(this.information.size, DecodedNumeric.FNC1, DecodedNumeric.FNC1); |
| } |
| return new DecodedNumeric(this.information.size, numeric - 1, DecodedNumeric.FNC1); |
| } |
| int numeric = extractNumericValueFromBitArray(pos, 7); |
| |
| int digit1 = (numeric - 8) / 11; |
| int digit2 = (numeric - 8) % 11; |
| |
| return new DecodedNumeric(pos + 7, digit1, digit2); |
| } |
| |
| int extractNumericValueFromBitArray(int pos, int bits){ |
| return extractNumericValueFromBitArray(this.information, pos, bits); |
| } |
| |
| static int extractNumericValueFromBitArray(BitArray information, int pos, int bits) { |
| if(bits > 32) { |
| throw new IllegalArgumentException("extractNumberValueFromBitArray can't handle more than 32 bits"); |
| } |
| |
| int value = 0; |
| for(int i = 0; i < bits; ++i) { |
| if (information.get(pos + i)) { |
| value |= (1 << (bits - i - 1)); |
| } |
| } |
| |
| return value; |
| } |
| |
| DecodedInformation decodeGeneralPurposeField(int pos, String remaining) { |
| this.buffer.setLength(0); |
| |
| if(remaining != null) { |
| this.buffer.append(remaining); |
| } |
| |
| this.current.position = pos; |
| |
| DecodedInformation lastDecoded = parseBlocks(); |
| if(lastDecoded != null && lastDecoded.isRemaining()) { |
| return new DecodedInformation(this.current.position, this.buffer.toString(), lastDecoded.getRemainingValue()); |
| } |
| return new DecodedInformation(this.current.position, this.buffer.toString()); |
| } |
| |
| private DecodedInformation parseBlocks() { |
| boolean isFinished; |
| BlockParsedResult result; |
| do{ |
| int initialPosition = current.position; |
| |
| if (current.isAlpha()){ |
| result = parseAlphaBlock(); |
| isFinished = result.isFinished(); |
| }else if (current.isIsoIec646()){ |
| result = parseIsoIec646Block(); |
| isFinished = result.isFinished(); |
| }else{ // it must be numeric |
| result = parseNumericBlock(); |
| isFinished = result.isFinished(); |
| } |
| |
| boolean positionChanged = initialPosition != current.position; |
| if(!positionChanged && !isFinished) { |
| break; |
| } |
| } while (!isFinished); |
| |
| return result.getDecodedInformation(); |
| } |
| |
| private BlockParsedResult parseNumericBlock() { |
| while(isStillNumeric(current.position)){ |
| DecodedNumeric numeric = decodeNumeric(current.position); |
| current.position = numeric.getNewPosition(); |
| |
| if(numeric.isFirstDigitFNC1()){ |
| DecodedInformation information; |
| if (numeric.isSecondDigitFNC1()) { |
| information = new DecodedInformation(current.position, buffer.toString()); |
| } else { |
| information = new DecodedInformation(current.position, buffer.toString(), numeric.getSecondDigit()); |
| } |
| return new BlockParsedResult(information, true); |
| } |
| buffer.append(numeric.getFirstDigit()); |
| |
| if(numeric.isSecondDigitFNC1()){ |
| DecodedInformation information = new DecodedInformation(current.position, buffer.toString()); |
| return new BlockParsedResult(information, true); |
| } |
| buffer.append(numeric.getSecondDigit()); |
| } |
| |
| if(isNumericToAlphaNumericLatch(current.position)){ |
| current.setAlpha(); |
| current.position += 4; |
| } |
| return new BlockParsedResult(false); |
| } |
| |
| private BlockParsedResult parseIsoIec646Block() { |
| while (isStillIsoIec646(current.position)) { |
| DecodedChar iso = decodeIsoIec646(current.position); |
| current.position = iso.getNewPosition(); |
| |
| if (iso.isFNC1()) { |
| DecodedInformation information = new DecodedInformation(current.position, buffer.toString()); |
| return new BlockParsedResult(information, true); |
| } |
| buffer.append(iso.getValue()); |
| } |
| |
| if (isAlphaOr646ToNumericLatch(current.position)) { |
| current.position += 3; |
| current.setNumeric(); |
| } else if (isAlphaTo646ToAlphaLatch(current.position)) { |
| if (current.position + 5 < this.information.size) { |
| current.position += 5; |
| } else { |
| current.position = this.information.size; |
| } |
| |
| current.setAlpha(); |
| } |
| return new BlockParsedResult(false); |
| } |
| |
| private BlockParsedResult parseAlphaBlock() { |
| while (isStillAlpha(current.position)) { |
| DecodedChar alpha = decodeAlphanumeric(current.position); |
| current.position = alpha.getNewPosition(); |
| |
| if(alpha.isFNC1()) { |
| DecodedInformation information = new DecodedInformation(current.position, buffer.toString()); |
| return new BlockParsedResult(information, true); //end of the char block |
| } |
| |
| buffer.append(alpha.getValue()); |
| } |
| |
| if (isAlphaOr646ToNumericLatch(current.position)) { |
| current.position += 3; |
| current.setNumeric(); |
| } else if (isAlphaTo646ToAlphaLatch(current.position)) { |
| if (current.position + 5 < this.information.size) { |
| current.position += 5; |
| } else { |
| current.position = this.information.size; |
| } |
| |
| current.setIsoIec646(); |
| } |
| return new BlockParsedResult(false); |
| } |
| |
| private boolean isStillIsoIec646(int pos) { |
| if(pos + 5 > this.information.size) { |
| return false; |
| } |
| |
| int fiveBitValue = extractNumericValueFromBitArray(pos, 5); |
| if(fiveBitValue >= 5 && fiveBitValue < 16) { |
| return true; |
| } |
| |
| if(pos + 7 > this.information.size) { |
| return false; |
| } |
| |
| int sevenBitValue = extractNumericValueFromBitArray(pos, 7); |
| if(sevenBitValue >= 64 && sevenBitValue < 116) { |
| return true; |
| } |
| |
| if(pos + 8 > this.information.size) { |
| return false; |
| } |
| |
| int eightBitValue = extractNumericValueFromBitArray(pos, 8); |
| return eightBitValue >= 232 && eightBitValue < 253; |
| |
| } |
| |
| private DecodedChar decodeIsoIec646(int pos) { |
| int fiveBitValue = extractNumericValueFromBitArray(pos, 5); |
| if(fiveBitValue == 15) { |
| return new DecodedChar(pos + 5, DecodedChar.FNC1); |
| } |
| |
| if(fiveBitValue >= 5 && fiveBitValue < 15) { |
| return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5)); |
| } |
| |
| int sevenBitValue = extractNumericValueFromBitArray(pos, 7); |
| |
| if(sevenBitValue >= 64 && sevenBitValue < 90) { |
| return new DecodedChar(pos + 7, (char) (sevenBitValue + 1)); |
| } |
| |
| if(sevenBitValue >= 90 && sevenBitValue < 116) { |
| return new DecodedChar(pos + 7, (char) (sevenBitValue + 7)); |
| } |
| |
| int eightBitValue = extractNumericValueFromBitArray(pos, 8); |
| switch (eightBitValue){ |
| case 232: return new DecodedChar(pos + 8, '!'); |
| case 233: return new DecodedChar(pos + 8, '"'); |
| case 234: return new DecodedChar(pos + 8, '%'); |
| case 235: return new DecodedChar(pos + 8, '&'); |
| case 236: return new DecodedChar(pos + 8, '\''); |
| case 237: return new DecodedChar(pos + 8, '('); |
| case 238: return new DecodedChar(pos + 8, ')'); |
| case 239: return new DecodedChar(pos + 8, '*'); |
| case 240: return new DecodedChar(pos + 8, '+'); |
| case 241: return new DecodedChar(pos + 8, ','); |
| case 242: return new DecodedChar(pos + 8, '-'); |
| case 243: return new DecodedChar(pos + 8, '.'); |
| case 244: return new DecodedChar(pos + 8, '/'); |
| case 245: return new DecodedChar(pos + 8, ':'); |
| case 246: return new DecodedChar(pos + 8, ';'); |
| case 247: return new DecodedChar(pos + 8, '<'); |
| case 248: return new DecodedChar(pos + 8, '='); |
| case 249: return new DecodedChar(pos + 8, '>'); |
| case 250: return new DecodedChar(pos + 8, '?'); |
| case 251: return new DecodedChar(pos + 8, '_'); |
| case 252: return new DecodedChar(pos + 8, ' '); |
| } |
| |
| throw new RuntimeException("Decoding invalid ISO/IEC 646 value: " + eightBitValue); |
| } |
| |
| private boolean isStillAlpha(int pos) { |
| if(pos + 5 > this.information.size) { |
| return false; |
| } |
| |
| // We now check if it's a valid 5-bit value (0..9 and FNC1) |
| int fiveBitValue = extractNumericValueFromBitArray(pos, 5); |
| if(fiveBitValue >= 5 && fiveBitValue < 16) { |
| return true; |
| } |
| |
| if(pos + 6 > this.information.size) { |
| return false; |
| } |
| |
| int sixBitValue = extractNumericValueFromBitArray(pos, 6); |
| return sixBitValue >= 16 && sixBitValue < 63; // 63 not included |
| } |
| |
| private DecodedChar decodeAlphanumeric(int pos) { |
| int fiveBitValue = extractNumericValueFromBitArray(pos, 5); |
| if(fiveBitValue == 15) { |
| return new DecodedChar(pos + 5, DecodedChar.FNC1); |
| } |
| |
| if(fiveBitValue >= 5 && fiveBitValue < 15) { |
| return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5)); |
| } |
| |
| int sixBitValue = extractNumericValueFromBitArray(pos, 6); |
| |
| if(sixBitValue >= 32 && sixBitValue < 58) { |
| return new DecodedChar(pos + 6, (char) (sixBitValue + 33)); |
| } |
| |
| switch(sixBitValue){ |
| case 58: return new DecodedChar(pos + 6, '*'); |
| case 59: return new DecodedChar(pos + 6, ','); |
| case 60: return new DecodedChar(pos + 6, '-'); |
| case 61: return new DecodedChar(pos + 6, '.'); |
| case 62: return new DecodedChar(pos + 6, '/'); |
| } |
| |
| throw new RuntimeException("Decoding invalid alphanumeric value: " + sixBitValue); |
| } |
| |
| private boolean isAlphaTo646ToAlphaLatch(int pos) { |
| if(pos + 1 > this.information.size) { |
| return false; |
| } |
| |
| for(int i = 0; i < 5 && i + pos < this.information.size; ++i){ |
| if(i == 2){ |
| if(!this.information.get(pos + 2)) { |
| return false; |
| } |
| } else if(this.information.get(pos + i)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private boolean isAlphaOr646ToNumericLatch(int pos) { |
| // Next is alphanumeric if there are 3 positions and they are all zeros |
| if (pos + 3 > this.information.size) { |
| return false; |
| } |
| |
| for (int i = pos; i < pos + 3; ++i) { |
| if (this.information.get(i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean isNumericToAlphaNumericLatch(int pos) { |
| // Next is alphanumeric if there are 4 positions and they are all zeros, or |
| // if there is a subset of this just before the end of the symbol |
| if (pos + 1 > this.information.size) { |
| return false; |
| } |
| |
| for (int i = 0; i < 4 && i + pos < this.information.size; ++i) { |
| if (this.information.get(pos + i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |