1   /* Copyright 2002-2024 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.data;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import org.hipparchus.exception.DummyLocalizable;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.time.TimeStamped;
33  
34  /**
35   * Parser for simple tables containing {@link TimeStamped time stamped} data.
36   * @param <T> the type of time stamped data (i.e. parsed table rows)
37   * @author Luc Maisonobe
38   * @since 6.1
39   */
40  public class SimpleTimeStampedTableParser<T extends TimeStamped> {
41  
42      /** Interface for converting a table row into time-stamped data.
43       * @param <S> the type of time stamped data (i.e. parsed table rows)
44       */
45      public interface RowConverter<S extends TimeStamped> {
46  
47          /** Convert a row.
48           * @param rawFields raw row fields, as read from the file
49           * @return converted row
50           */
51          S convert(double[] rawFields);
52      }
53  
54      /** Pattern for fields with real type. */
55      private static final String  REAL_TYPE_PATTERN =
56              "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";
57  
58      /** Number of columns. */
59      private final int columns;
60  
61      /** Converter for rows. */
62      private final RowConverter<T> converter;
63  
64      /** Simple constructor.
65       * @param columns number of columns
66       * @param converter converter for rows
67       */
68      public SimpleTimeStampedTableParser(final int columns, final RowConverter<T> converter) {
69          this.columns   = columns;
70          this.converter = converter;
71      }
72  
73      /** Parse a stream.
74       * @param stream stream containing the table
75       * @param name name of the resource file (for error messages only)
76       * @return parsed table
77       */
78      public List<T> parse(final InputStream stream, final String name) {
79  
80          if (stream == null) {
81              throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
82          }
83  
84          // regular lines are simply a space separated list of numbers
85          final StringBuilder builder = new StringBuilder("^\\p{Space}*");
86          for (int i = 0; i < columns; ++i) {
87              builder.append("(");
88              builder.append(REAL_TYPE_PATTERN);
89              builder.append(")");
90              builder.append((i < columns - 1) ? "\\p{Space}+" : "\\p{Space}*$");
91          }
92          final Pattern regularLinePattern = Pattern.compile(builder.toString());
93  
94          // setup the reader
95          try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
96  
97              final List<T> table = new ArrayList<T>();
98  
99              for (String line = reader.readLine(); line != null; line = reader.readLine()) {
100 
101                 // replace unicode minus sign ('−') by regular hyphen ('-') for parsing
102                 // such unicode characters occur in tables that are copy-pasted from PDF files
103                 line = line.replace('\u2212', '-');
104 
105                 final Matcher regularMatcher = regularLinePattern.matcher(line);
106                 if (regularMatcher.matches()) {
107                     // we have found a regular data line
108 
109                     final double[] rawFields = new double[columns];
110                     for (int i = 0; i < columns; ++i) {
111                         rawFields[i] = Double.parseDouble(regularMatcher.group(i + 1));
112                     }
113 
114                     table.add(converter.convert(rawFields));
115 
116                 }
117 
118             }
119 
120             if (table.isEmpty()) {
121                 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
122             }
123 
124             return table;
125 
126         } catch (IOException ioe) {
127             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
128         }
129 
130     }
131 
132 }