package org.hl7.fhir.dstu3.utils; import java.math.BigDecimal; import java.util.Map; import java.util.Stack; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.utilities.Utilities; import com.google.gson.*; /** * This is created to get a json parser that can track line numbers... grr... * * @author Grahame Grieve * */ public class JsonTrackingParser { public enum TokenType { Open, Close, String, Number, Colon, Comma, OpenArray, CloseArray, Eof, Null, Boolean; } public class LocationData { private int line; private int col; protected LocationData(int line, int col) { super(); this.line = line; this.col = col; } public int getLine() { return line; } public int getCol() { return col; } public void newLine() { line++; col = 1; } public LocationData copy() { return new LocationData(line, col); } } private class State { private String name; private boolean isProp; protected State(String name, boolean isProp) { super(); this.name = name; this.isProp = isProp; } public String getName() { return name; } public boolean isProp() { return isProp; } } private class Lexer { private String source; private int cursor; private String peek; private String value; private TokenType type; private Stack<State> states = new Stack<State>(); private LocationData lastLocationBWS; private LocationData lastLocationAWS; private LocationData location; private StringBuilder b = new StringBuilder(); public Lexer(String source) throws FHIRException { this.source = source; cursor = -1; location = new LocationData(1, 1); start(); } private boolean more() { return peek != null || cursor < source.length(); } private String getNext(int length) throws FHIRException { String result = ""; if (peek != null) { if (peek.length() > length) { result = peek.substring(0, length); peek = peek.substring(length); } else { result = peek; peek = null; } } if (result.length() < length) { int len = length - result.length(); if (cursor > source.length() - len) throw error("Attempt to read past end of source"); result = result + source.substring(cursor+1, cursor+len+1); cursor = cursor + len; } for (char ch : result.toCharArray()) if (ch == '\n') location.newLine(); else location.col++; return result; } private char getNextChar() throws FHIRException { if (peek != null) { char ch = peek.charAt(0); peek = peek.length() == 1 ? null : peek.substring(1); return ch; } else { cursor++; if (cursor >= source.length()) return (char) 0; char ch = source.charAt(cursor); if (ch == '\n') { location.newLine(); } else { location.col++; } return ch; } } private void push(char ch){ peek = peek == null ? String.valueOf(ch) : String.valueOf(ch)+peek; } private void parseWord(String word, char ch, TokenType type) throws FHIRException { this.type = type; value = ""+ch+getNext(word.length()-1); if (!value.equals(word)) throw error("Syntax error in json reading special word "+word); } private FHIRException error(String msg) { return new FHIRException("Error parsing JSON source: "+msg+" at Line "+Integer.toString(location.line)+" (path=["+path()+"])"); } private String path() { if (states.empty()) return value; else { String result = ""; for (State s : states) result = result + '/'+ s.getName(); result = result + value; return result; } } public void start() throws FHIRException { // char ch = getNextChar(); // if (ch = '\.uEF') // begin // // skip BOM // getNextChar(); // getNextChar(); // end // else // push(ch); next(); } public TokenType getType() { return type; } public String getValue() { return value; } public LocationData getLastLocationBWS() { return lastLocationBWS; } public LocationData getLastLocationAWS() { return lastLocationAWS; } public void next() throws FHIRException { lastLocationBWS = location.copy(); char ch; do { ch = getNextChar(); } while (more() && Utilities.charInSet(ch, ' ', '\r', '\n', '\t')); lastLocationAWS = location.copy(); if (!more()) { type = TokenType.Eof; } else { switch (ch) { case '{' : type = TokenType.Open; break; case '}' : type = TokenType.Close; break; case '"' : type = TokenType.String; b.setLength(0); do { ch = getNextChar(); if (ch == '\\') { ch = getNextChar(); switch (ch) { case '"': b.append('"'); break; case '\\': b.append('\\'); break; case '/': b.append('/'); break; case 'n': b.append('\n'); break; case 'r': b.append('\r'); break; case 't': b.append('\t'); break; case 'u': b.append((char) Integer.parseInt(getNext(4), 16)); break; default : throw error("unknown escape sequence: \\"+ch); } ch = ' '; } else if (ch != '"') b.append(ch); } while (more() && (ch != '"')); if (!more()) throw error("premature termination of json stream during a string"); value = b.toString(); break; case ':' : type = TokenType.Colon; break; case ',' : type = TokenType.Comma; break; case '[' : type = TokenType.OpenArray; break; case ']' : type = TokenType.CloseArray; break; case 't' : parseWord("true", ch, TokenType.Boolean); break; case 'f' : parseWord("false", ch, TokenType.Boolean); break; case 'n' : parseWord("null", ch, TokenType.Null); break; default: if ((ch >= '0' && ch <= '9') || ch == '-') { type = TokenType.Number; b.setLength(0); while (more() && ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.')) { b.append(ch); ch = getNextChar(); } value = b.toString(); push(ch); } else throw error("Unexpected char '"+ch+"' in json stream"); } } } public String consume(TokenType type) throws FHIRException { if (this.type != type) throw error("JSON syntax error - found "+type.toString()+" expecting "+type.toString()); String result = value; next(); return result; } } enum ItemType { Object, String, Number, Boolean, Array, End, Eof, Null; } private Map<JsonElement, LocationData> map; private Lexer lexer; private ItemType itemType = ItemType.Object; private String itemName; private String itemValue; public static JsonObject parse(String source, Map<JsonElement, LocationData> map) throws FHIRException { JsonTrackingParser self = new JsonTrackingParser(); self.map = map; return self.parse(source); } private JsonObject parse(String source) throws FHIRException { lexer = new Lexer(source); JsonObject result = new JsonObject(); LocationData loc = lexer.location.copy(); if (lexer.getType() == TokenType.Open) { lexer.next(); lexer.states.push(new State("", false)); } else throw lexer.error("Unexpected content at start of JSON: "+lexer.getType().toString()); parseProperty(); readObject(result, true); map.put(result, loc); return result; } private void readObject(JsonObject obj, boolean root) throws FHIRException { map.put(obj, lexer.location.copy()); while (!(itemType == ItemType.End) || (root && (itemType == ItemType.Eof))) { if (obj.has(itemName)) throw lexer.error("Duplicated property name: "+itemName); switch (itemType) { case Object: JsonObject child = new JsonObject(); //(obj.path+'.'+ItemName); LocationData loc = lexer.location.copy(); obj.add(itemName, child); next(); readObject(child, false); map.put(obj, loc); break; case Boolean : JsonPrimitive v = new JsonPrimitive(Boolean.valueOf(itemValue)); obj.add(itemName, v); map.put(v, lexer.location.copy()); break; case String: v = new JsonPrimitive(itemValue); obj.add(itemName, v); map.put(v, lexer.location.copy()); break; case Number: v = new JsonPrimitive(new BigDecimal(itemValue)); obj.add(itemName, v); map.put(v, lexer.location.copy()); break; case Null: JsonNull n = new JsonNull(); obj.add(itemName, n); map.put(n, lexer.location.copy()); break; case Array: JsonArray arr = new JsonArray(); // (obj.path+'.'+ItemName); loc = lexer.location.copy(); obj.add(itemName, arr); next(); readArray(arr, false); map.put(arr, loc); break; case Eof : throw lexer.error("Unexpected End of File"); } next(); } } private void readArray(JsonArray arr, boolean root) throws FHIRException { while (!((itemType == ItemType.End) || (root && (itemType == ItemType.Eof)))) { switch (itemType) { case Object: JsonObject obj = new JsonObject(); // (arr.path+'['+inttostr(i)+']'); LocationData loc = lexer.location.copy(); arr.add(obj); next(); readObject(obj, false); map.put(obj, loc); break; case String: JsonPrimitive v = new JsonPrimitive(itemValue); arr.add(v); map.put(v, lexer.location.copy()); break; case Number: v = new JsonPrimitive(new BigDecimal(itemValue)); arr.add(v); map.put(v, lexer.location.copy()); break; case Null : JsonNull n = new JsonNull(); arr.add(n); map.put(n, lexer.location.copy()); break; case Array: JsonArray child = new JsonArray(); // (arr.path+'['+inttostr(i)+']'); loc = lexer.location.copy(); arr.add(child); next(); readArray(child, false); map.put(arr, loc); break; case Eof : throw lexer.error("Unexpected End of File"); } next(); } } private void next() throws FHIRException { switch (itemType) { case Object : lexer.consume(TokenType.Open); lexer.states.push(new State(itemName, false)); if (lexer.getType() == TokenType.Close) { itemType = ItemType.End; lexer.next(); } else parseProperty(); break; case Null: case String: case Number: case End: case Boolean : if (itemType == ItemType.End) lexer.states.pop(); if (lexer.getType() == TokenType.Comma) { lexer.next(); parseProperty(); } else if (lexer.getType() == TokenType.Close) { itemType = ItemType.End; lexer.next(); } else if (lexer.getType() == TokenType.CloseArray) { itemType = ItemType.End; lexer.next(); } else if (lexer.getType() == TokenType.Eof) { itemType = ItemType.Eof; } else throw lexer.error("Unexpected JSON syntax"); break; case Array : lexer.next(); lexer.states.push(new State(itemName+"[]", true)); parseProperty(); break; case Eof : throw lexer.error("JSON Syntax Error - attempt to read past end of json stream"); default: throw lexer.error("not done yet (a): "+itemType.toString()); } } private void parseProperty() throws FHIRException { if (!lexer.states.peek().isProp) { itemName = lexer.consume(TokenType.String); itemValue = null; lexer.consume(TokenType.Colon); } switch (lexer.getType()) { case Null : itemType = ItemType.Null; itemValue = lexer.value; lexer.next(); break; case String : itemType = ItemType.String; itemValue = lexer.value; lexer.next(); break; case Boolean : itemType = ItemType.Boolean; itemValue = lexer.value; lexer.next(); break; case Number : itemType = ItemType.Number; itemValue = lexer.value; lexer.next(); break; case Open : itemType = ItemType.Object; break; case OpenArray : itemType = ItemType.Array; break; case CloseArray : itemType = ItemType.End; break; // case Close, , case Colon, case Comma, case OpenArray, ! default: throw lexer.error("not done yet (b): "+lexer.getType().toString()); } } }