001/*
002Copyright (c) 2011+, HL7, Inc
003All rights reserved.
004
005Redistribution and use in source and binary forms, with or without modification, 
006are permitted provided that the following conditions are met:
007
008 * Redistributions of source code must retain the above copyright notice, this 
009   list of conditions and the following disclaimer.
010 * Redistributions in binary form must reproduce the above copyright notice, 
011   this list of conditions and the following disclaimer in the documentation 
012   and/or other materials provided with the distribution.
013 * Neither the name of HL7 nor the names of its contributors may be used to 
014   endorse or promote products derived from this software without specific 
015   prior written permission.
016
017THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
018ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
019WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
020IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
021INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
022NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
023PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
024WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
025ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
026POSSIBILITY OF SUCH DAMAGE.
027
028*/
029package org.hl7.fhir.utilities;
030
031/*-
032 * #%L
033 * org.hl7.fhir.utilities
034 * %%
035 * Copyright (C) 2014 - 2019 Health Level 7
036 * %%
037 * Licensed under the Apache License, Version 2.0 (the "License");
038 * you may not use this file except in compliance with the License.
039 * You may obtain a copy of the License at
040 * 
041 *      http://www.apache.org/licenses/LICENSE-2.0
042 * 
043 * Unless required by applicable law or agreed to in writing, software
044 * distributed under the License is distributed on an "AS IS" BASIS,
045 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
046 * See the License for the specific language governing permissions and
047 * limitations under the License.
048 * #L%
049 */
050
051
052import java.io.IOException;
053import java.io.InputStream;
054import java.io.InputStreamReader;
055import java.io.UnsupportedEncodingException;
056import java.util.ArrayList;
057import java.util.List;
058
059import org.hl7.fhir.exceptions.FHIRException;
060
061/**
062 * Baseclass for readers that read data from files in comma separated file format
063 * @author Ewout
064 *
065 */
066public class CSVReader extends InputStreamReader {
067        
068        public CSVReader(InputStream in) throws FHIRException, IOException {
069                super(in, "UTF-8");
070                checkBOM();
071        }
072
073        private void checkBOM() throws FHIRException, IOException {
074    if (peek() == '\uFEFF')
075      next();
076    
077  }
078
079  private String[] cols;
080  private String[] cells;
081  
082        public void readHeaders() throws IOException, FHIRException {
083    cols = parseLine();  
084        }
085        
086
087  public boolean line() throws IOException, FHIRException {
088    if (ready()) {
089      cells = parseLine();
090      return true;
091    }  else
092      return false;
093  }
094
095  public boolean has(String name) {
096    return cell(name) != null;
097  }
098  
099  public String cell(String name) {
100    int index = -1;
101    for (int i = 0; i < cols.length; i++) {
102      if (name.equals(cols[i].trim()))
103        index = i;
104    }
105    if (index == -1)
106      throw new Error("no cell "+name);
107    String s = cells.length >= index ? cells[index] : null;
108    if (Utilities.noString(s))
109      return null;
110    return s;
111  }
112    
113        protected boolean parseBoolean(String column) {
114                if (column == null)
115                        return false;
116                else if (column.equalsIgnoreCase("y") || column.equalsIgnoreCase("yes") || column.equalsIgnoreCase("true") || column.equalsIgnoreCase("1"))
117                        return true;
118                else
119                        return false;
120        }
121
122        protected static String getColumn(String[] titles, String[] values, String column)  {
123                int c = -1;
124        //      String s = "";
125                for (int i = 0; i < titles.length; i++) {
126                //      s = s + ","+titles[i];
127                        if (titles[i].equalsIgnoreCase(column))
128                                c = i;
129                }
130                if (c == -1)
131                        return ""; // throw new Exception("unable to find column "+column+" in "+s.substring(1));
132                else if (values.length <= c)
133                        return "";
134                else
135                        return values[c];
136        }
137
138        
139        /**
140         * Split one line in a CSV file into its rows. Comma's appearing in double quoted strings will
141         * not be seen as a separator.
142         * @return
143         * @throws IOException 
144         * @throws FHIRException 
145         * @
146         */
147        public String[] parseLine() throws IOException, FHIRException  {
148                List<String> res = new ArrayList<String>();
149                StringBuilder b = new StringBuilder();
150                boolean inQuote = false;
151
152                while (inQuote || (peek() != '\r' && peek() != '\n')) {
153                        char c = peek();
154                        next();
155                        if (c == '"') {
156                                if (ready() && peek() == '"') {
157                b.append(c);
158          next();
159                                } else {
160                            inQuote = !inQuote;
161                                }
162                        }
163                        else if (!inQuote && c == ',') {
164                                res.add(b.toString().trim());
165                                b = new StringBuilder();
166                        }
167                        else 
168                                b.append(c);
169                }
170                res.add(b.toString().trim());
171                while (ready() && (peek() == '\r' || peek() == '\n')) {
172                        next();
173                }
174                
175                String[] r = new String[] {};
176                r = res.toArray(r);
177                return r;
178                
179        }
180
181        private int state = 0;
182        private char pc;
183        
184        private char peek() throws FHIRException, IOException 
185        {
186          if (state == 0)
187                  next();
188          if (state == 1)
189                  return pc;
190          else
191                  throw new FHIRException("read past end of source");
192        }
193        
194        private void next() throws FHIRException, IOException 
195        {
196                  if (state == 2)
197                          throw new FHIRException("read past end of source");
198          state = 1;
199                  int i = read();
200                  if (i == -1)
201                          state = 2;
202                  else 
203                          pc = (char) i;
204        }
205
206
207  public void checkColumn(int i, String name, String desc) throws FHIRException {
208    if (cols.length < i)
209      throw new FHIRException("Error parsing "+desc+": expected column "+name+" at col "+i+" but only found "+cols.length+" cols");
210    if (!cols[i-1].equals(name))
211      throw new FHIRException("Error parsing "+desc+": expected column "+name+" at col "+i+" but found '"+cols[i-1]+"'");
212  }
213
214
215  public String value(int i) {
216    if (i > cells.length)
217      return null;
218    if (Utilities.noString(cells[i-1]))
219      return null;
220    return cells[i-1];
221  }
222
223
224}