فهرست منبع

Added support of comments in JSON input (issue #88)

Benoit Blanchon 10 سال پیش
والد
کامیت
e31d667bec

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+*.sh text eol=lf

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@ v5.0 (currently in beta)
 * Added `JsonBuffer::strdup()` to make a copy of a string (issues #10, #57)
 * Implicitly call `strdup()` for `String` but not for `char*` (issues #84, #87)
 * Added support of non standard JSON input (issue #44)
+* Added support of comments in JSON input (issue #88)
 * Redesigned `JsonVariant` to leverage converting constructors instead of assignment operators (issue #66)
 * Switched to new the library layout (requires Arduino 1.0.6 or above)
 

+ 13 - 0
include/ArduinoJson/Internals/Comments.hpp

@@ -0,0 +1,13 @@
+// Copyright Benoit Blanchon 2014-2015
+// MIT License
+//
+// Arduino JSON library
+// https://github.com/bblanchon/ArduinoJson
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+const char *skipSpacesAndComments(const char *ptr);
+}
+}

+ 21 - 22
scripts/build-arduino-package.sh

@@ -1,22 +1,21 @@
-#!/bin/bash
-
-ZIP="C:\Program Files\7-Zip\7z.exe"
-TAG=$(git describe)
-OUTPUT="ArduinoJson-$TAG.zip"
-
-cd ../..
-
-# remove existing file
-rm -f $OUTPUT
-
-# create zip
-"$ZIP" a $OUTPUT \
-	ArduinoJson/CHANGELOG.md \
-	ArduinoJson/examples \
-	ArduinoJson/include \
-	ArduinoJson/keywords.txt \
-	ArduinoJson/library.properties \
-	ArduinoJson/LICENSE.md \
-	ArduinoJson/README.md \
-	ArduinoJson/src	\
-	-x!ArduinoJson/src/CMakeLists.txt
+#!/bin/bash
+
+TAG=$(git describe)
+OUTPUT="ArduinoJson-$TAG.zip"
+
+cd $(dirname $0)/../..
+
+# remove existing file
+rm -f $OUTPUT
+
+# create zip
+7z a $OUTPUT \
+	ArduinoJson/CHANGELOG.md \
+	ArduinoJson/examples \
+	ArduinoJson/include \
+	ArduinoJson/keywords.txt \
+	ArduinoJson/library.properties \
+	ArduinoJson/LICENSE.md \
+	ArduinoJson/README.md \
+	ArduinoJson/src	\
+	-x!ArduinoJson/src/CMakeLists.txt

+ 6 - 0
src/Arduino/Print.cpp

@@ -11,6 +11,12 @@
 #include <math.h>   // for isnan() and isinf()
 #include <stdio.h>  // for sprintf()
 
+// only for GCC 4.9+
+#if defined(__GNUC__) && \
+    (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))
+#pragma GCC diagnostic ignored "-Wfloat-conversion"
+#endif
+
 size_t Print::print(const char s[]) {
   size_t n = 0;
   while (*s) {

+ 51 - 0
src/Internals/Comments.cpp

@@ -0,0 +1,51 @@
+// Copyright Benoit Blanchon 2014-2015
+// MIT License
+//
+// Arduino JSON library
+// https://github.com/bblanchon/ArduinoJson
+
+#include "../../include/ArduinoJson/Internals/Comments.hpp"
+
+inline static const char *skipCStyleComment(const char *ptr) {
+  ptr += 2;
+  for (;;) {
+    if (ptr[0] == '\0') return ptr;
+    if (ptr[0] == '*' && ptr[1] == '/') return ptr + 2;
+    ptr++;
+  }
+}
+
+inline static const char *skipCppStyleComment(const char *ptr) {
+  ptr += 2;
+  for (;;) {
+    if (ptr[0] == '\0' || ptr[0] == '\n') return ptr;
+    ptr++;
+  }
+}
+
+const char *ArduinoJson::Internals::skipSpacesAndComments(const char *ptr) {
+  for (;;) {
+    switch (ptr[0]) {
+      case ' ':
+      case '\t':
+      case '\r':
+      case '\n':
+        ptr++;
+        continue;
+      case '/':
+        switch (ptr[1]) {
+          case '*':
+            ptr = skipCStyleComment(ptr);
+            break;
+          case '/':
+            ptr = skipCppStyleComment(ptr);
+            break;
+          default:
+            return ptr;
+        }
+        break;
+      default:
+        return ptr;
+    }
+  }
+}

+ 14 - 13
src/Internals/JsonParser.cpp

@@ -9,6 +9,7 @@
 #include <stdlib.h>  // for strtol, strtod
 #include <ctype.h>
 
+#include "../../include/ArduinoJson/Internals/Comments.hpp"
 #include "../../include/ArduinoJson/Internals/Encoding.hpp"
 #include "../../include/ArduinoJson/JsonArray.hpp"
 #include "../../include/ArduinoJson/JsonBuffer.hpp"
@@ -17,16 +18,11 @@
 using namespace ArduinoJson;
 using namespace ArduinoJson::Internals;
 
-static const char *skipSpaces(const char *ptr) {
-  while (isspace(*ptr)) ptr++;
-  return ptr;
-}
-
 bool JsonParser::skip(char charToSkip) {
-  register const char *ptr = skipSpaces(_readPtr);
+  register const char *ptr = skipSpacesAndComments(_readPtr);
   if (*ptr != charToSkip) return false;
   ptr++;
-  _readPtr = skipSpaces(ptr);
+  _readPtr = skipSpacesAndComments(ptr);
   return true;
 }
 
@@ -50,7 +46,7 @@ bool JsonParser::parseAnythingTo(JsonVariant *destination) {
 }
 
 inline bool JsonParser::parseAnythingToUnsafe(JsonVariant *destination) {
-  _readPtr = skipSpaces(_readPtr);
+  _readPtr = skipSpacesAndComments(_readPtr);
 
   switch (*_readPtr) {
     case '[':
@@ -212,8 +208,13 @@ bool JsonParser::parseNullTo(JsonVariant *destination) {
   return true;
 }
 
-static bool isStopChar(char c) {
-  return c == '\0' || c == ':' || c == '}' || c == ']' || c == ',';
+static inline bool isInRange(char c, char min, char max) {
+  return min <= c && c <= max;
+}
+
+static inline bool isLetterOrNumber(char c) {
+  return isInRange(c, '0', '9') || isInRange(c, 'a', 'z') ||
+         isInRange(c, 'A', 'Z');
 }
 
 const char *JsonParser::parseString() {
@@ -222,7 +223,7 @@ const char *JsonParser::parseString() {
 
   char c = *readPtr;
 
-  if (c == '\'' || c == '\"') {
+  if (c == '\'' || c == '\"') {  // quotes
     char stopChar = c;
     for (;;) {
       c = *++readPtr;
@@ -241,9 +242,9 @@ const char *JsonParser::parseString() {
 
       *writePtr++ = c;
     }
-  } else {
+  } else {  // no quotes
     for (;;) {
-      if (isStopChar(c)) break;
+      if (!isLetterOrNumber(c)) break;
       *writePtr++ = c;
       c = *++readPtr;
     }

+ 135 - 2
test/JsonParser_Array_Tests.cpp

@@ -164,8 +164,26 @@ TEST_F(JsonParser_Array_Tests, MixedTrueFalse) {
   parseMustFail();
 }
 
-TEST_F(JsonParser_Array_Tests, TwoStrings) {
-  whenInputIs("[\"hello\",\"world\"]");
+TEST_F(JsonParser_Array_Tests, TwoStringsDoubleQuotes) {
+  whenInputIs("[ \"hello\" , \"world\" ]");
+
+  parseMustSucceed();
+  sizeMustBe(2);
+  firstElementMustBe("hello");
+  secondElementMustBe("world");
+}
+
+TEST_F(JsonParser_Array_Tests, TwoStringsSingleQuotes) {
+  whenInputIs("[ 'hello' , 'world' ]");
+
+  parseMustSucceed();
+  sizeMustBe(2);
+  firstElementMustBe("hello");
+  secondElementMustBe("world");
+}
+
+TEST_F(JsonParser_Array_Tests, TwoStringsNoQuotes) {
+  whenInputIs("[ hello , world ]");
 
   parseMustSucceed();
   sizeMustBe(2);
@@ -224,3 +242,118 @@ TEST_F(JsonParser_Array_Tests, StringWithUnterminatedEscapeSequence) {
   whenInputIs("\"\\\0\"", 4);
   parseMustFail();
 }
+
+TEST_F(JsonParser_Array_Tests, CCommentBeforeOpeningBracket) {
+  whenInputIs("/*COMMENT*/[\"hello\"]");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CCommentAfterOpeningBracket) {
+  whenInputIs("[/*COMMENT*/\"hello\"]");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CCommentBeforeClosingBracket) {
+  whenInputIs("[\"hello\"/*COMMENT*/]");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CCommentAfterClosingBracket) {
+  whenInputIs("[\"hello\"]/*COMMENT*/");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CCommentBeforeComma) {
+  whenInputIs("[\"hello\"/*COMMENT*/,\"world\"]");
+
+  parseMustSucceed();
+  sizeMustBe(2);
+  firstElementMustBe("hello");
+  secondElementMustBe("world");
+}
+
+TEST_F(JsonParser_Array_Tests, CCommentAfterComma) {
+  whenInputIs("[\"hello\",/*COMMENT*/\"world\"]");
+
+  parseMustSucceed();
+  sizeMustBe(2);
+  firstElementMustBe("hello");
+  secondElementMustBe("world");
+}
+
+TEST_F(JsonParser_Array_Tests, CppCommentBeforeOpeningBracket) {
+  whenInputIs("//COMMENT\n[\"hello\"]");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CppCommentAfterOpeningBracket) {
+  whenInputIs("[//COMMENT\n\"hello\"]");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CppCommentBeforeClosingBracket) {
+  whenInputIs("[\"hello\"//COMMENT\n]");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CppCommentAfterClosingBracket) {
+  whenInputIs("[\"hello\"]//COMMENT\n");
+
+  parseMustSucceed();
+  sizeMustBe(1);
+  firstElementMustBe("hello");
+}
+
+TEST_F(JsonParser_Array_Tests, CppCommentBeforeComma) {
+  whenInputIs("[\"hello\"//COMMENT\n,\"world\"]");
+
+  parseMustSucceed();
+  sizeMustBe(2);
+  firstElementMustBe("hello");
+  secondElementMustBe("world");
+}
+
+TEST_F(JsonParser_Array_Tests, CppCommentAfterComma) {
+  whenInputIs("[\"hello\",//COMMENT\n\"world\"]");
+
+  parseMustSucceed();
+  sizeMustBe(2);
+  firstElementMustBe("hello");
+  secondElementMustBe("world");
+}
+
+TEST_F(JsonParser_Array_Tests, InvalidCppComment) {
+  whenInputIs("[/COMMENT\n]");
+  parseMustFail();
+}
+
+TEST_F(JsonParser_Array_Tests, InvalidComment) {
+  whenInputIs("[/*/\n]");
+  parseMustFail();
+}
+
+TEST_F(JsonParser_Array_Tests, UnfinishedCComment) {
+  whenInputIs("[/*COMMENT]");
+  parseMustFail();
+}