| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| #include "RuntimeLibraryPch.h" |
| |
| namespace Js |
| { |
| |
| void |
| JSONStringBuilder::AppendCharacter(char16 character) |
| { |
| AssertOrFailFast(this->currentLocation < endLocation); |
| *this->currentLocation = character; |
| ++this->currentLocation; |
| } |
| |
| void |
| JSONStringBuilder::AppendBuffer(_In_ const char16* buffer, charcount_t length) |
| { |
| AssertOrFailFast(this->currentLocation + length <= endLocation); |
| wmemcpy_s(this->currentLocation, length, buffer, length); |
| this->currentLocation += length; |
| } |
| |
| void |
| JSONStringBuilder::AppendString(_In_ JavascriptString* str) |
| { |
| AppendBuffer(str->GetString(), str->GetLength()); |
| } |
| |
| void |
| JSONStringBuilder::AppendEscapeSequence(_In_ const char16 character) |
| { |
| // Convert character into a 4 digit hex code (e.g. \u0010) |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(_u('u')); |
| { |
| char16 buf[5]; |
| // Get hex value |
| _ltow_s(character, buf, _countof(buf), 16); |
| |
| // Append leading zeros if necessary before the hex value |
| charcount_t count = static_cast<charcount_t>(wcslen(buf)); |
| switch (count) |
| { |
| case 1: |
| this->AppendCharacter(_u('0')); |
| case 2: |
| this->AppendCharacter(_u('0')); |
| case 3: |
| this->AppendCharacter(_u('0')); |
| default: |
| this->AppendBuffer(buf, count); |
| break; |
| } |
| } |
| } |
| |
| void |
| JSONStringBuilder::EscapeAndAppendString(_In_ JavascriptString* str) |
| { |
| const charcount_t strLength = str->GetLength(); |
| |
| // Strings should be surrounded by double quotes |
| this->AppendCharacter(_u('"')); |
| const char16* bufferStart = str->GetString(); |
| for (const char16* index = bufferStart; index < bufferStart + strLength; ++index) |
| { |
| char16 currentCharacter = *index; |
| switch (currentCharacter) |
| { |
| case _u('"'): |
| case _u('\\'): |
| // Special characters are escaped with a backslash |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(currentCharacter); |
| break; |
| case _u('\b'): |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(_u('b')); |
| break; |
| case _u('\f'): |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(_u('f')); |
| break; |
| case _u('\n'): |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(_u('n')); |
| break; |
| case _u('\r'): |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(_u('r')); |
| break; |
| case _u('\t'): |
| this->AppendCharacter(_u('\\')); |
| this->AppendCharacter(_u('t')); |
| break; |
| default: |
| if (currentCharacter < _u(' ') || utf8::IsLowSurrogateChar(currentCharacter)) |
| { |
| this->AppendEscapeSequence(currentCharacter); |
| } |
| else if (utf8::IsHighSurrogateChar(currentCharacter)) |
| { |
| if (index + 1 < bufferStart + strLength && utf8::IsLowSurrogateChar(*(index + 1))) |
| { |
| // Append surrogate pair normally |
| this->AppendCharacter(currentCharacter); |
| this->AppendCharacter(*(index + 1)); |
| |
| // Skip the trailing-surrogate code unit |
| index++; |
| } |
| else |
| { |
| // High-surrogate code unit not followed by a trailing-surrogate code unit should be escaped. |
| this->AppendEscapeSequence(currentCharacter); |
| } |
| } |
| else |
| { |
| this->AppendCharacter(currentCharacter); |
| } |
| break; |
| } |
| } |
| |
| this->AppendCharacter(_u('"')); |
| } |
| |
| void |
| JSONStringBuilder::AppendGap(uint32 count) |
| { |
| for (uint i = 0; i < count; ++i) |
| { |
| this->AppendBuffer(this->gap, this->gapLength); |
| } |
| } |
| |
| void |
| JSONStringBuilder::AppendObjectString(_In_ JSONObject* valueList) |
| { |
| const uint elementCount = valueList->Count(); |
| if (elementCount == 0) |
| { |
| this->AppendCharacter(_u('{')); |
| this->AppendCharacter(_u('}')); |
| return; |
| } |
| |
| const uint32 stepbackLevel = this->indentLevel; |
| ++this->indentLevel; |
| |
| this->AppendCharacter(_u('{')); |
| if (this->gap != nullptr) |
| { |
| this->AppendCharacter(_u('\n')); |
| this->AppendGap(indentLevel); |
| } |
| |
| bool isFirstMember = true; |
| FOREACH_SLISTCOUNTED_ENTRY(JSONObjectProperty, entry, valueList) |
| { |
| if (!isFirstMember) |
| { |
| if (this->gap == nullptr) |
| { |
| this->AppendCharacter(_u(',')); |
| } |
| else |
| { |
| this->AppendCharacter(_u(',')); |
| this->AppendCharacter(_u('\n')); |
| this->AppendGap(indentLevel); |
| } |
| } |
| this->EscapeAndAppendString(entry.propertyName); |
| this->AppendCharacter(_u(':')); |
| if (this->gap != nullptr) |
| { |
| this->AppendCharacter(_u(' ')); |
| } |
| |
| this->AppendJSONPropertyString(&entry.propertyValue); |
| |
| isFirstMember = false; |
| } |
| NEXT_SLISTCOUNTED_ENTRY; |
| |
| if (this->gap != nullptr) |
| { |
| this->AppendCharacter(_u('\n')); |
| this->AppendGap(stepbackLevel); |
| } |
| |
| this->AppendCharacter(_u('}')); |
| |
| this->indentLevel = stepbackLevel; |
| } |
| |
| void |
| JSONStringBuilder::AppendArrayString(_In_ JSONArray* valueArray) |
| { |
| uint32 length = valueArray->length; |
| if (length == 0) |
| { |
| this->AppendCharacter(_u('[')); |
| this->AppendCharacter(_u(']')); |
| return; |
| } |
| |
| const uint32 stepbackLevel = this->indentLevel; |
| ++this->indentLevel; |
| |
| JSONProperty* arr = valueArray->arr; |
| |
| this->AppendCharacter(_u('[')); |
| |
| if (this->gap != nullptr) |
| { |
| this->AppendCharacter(_u('\n')); |
| this->AppendGap(indentLevel); |
| } |
| |
| this->AppendJSONPropertyString(&arr[0]); |
| |
| for (uint32 i = 1; i < length; ++i) |
| { |
| if (this->gap == nullptr) |
| { |
| this->AppendCharacter(_u(',')); |
| } |
| else |
| { |
| this->AppendCharacter(_u(',')); |
| this->AppendCharacter(_u('\n')); |
| this->AppendGap(indentLevel); |
| } |
| AppendJSONPropertyString(&arr[i]); |
| } |
| |
| if (this->gap != nullptr) |
| { |
| this->AppendCharacter(_u('\n')); |
| this->AppendGap(stepbackLevel); |
| } |
| this->AppendCharacter(_u(']')); |
| |
| this->indentLevel = stepbackLevel; |
| } |
| |
| void |
| JSONStringBuilder::AppendJSONPropertyString(_In_ JSONProperty* prop) |
| { |
| switch (prop->type) |
| { |
| case JSONContentType::False: |
| this->AppendString(this->scriptContext->GetLibrary()->GetFalseDisplayString()); |
| return; |
| case JSONContentType::True: |
| this->AppendString(this->scriptContext->GetLibrary()->GetTrueDisplayString()); |
| return; |
| case JSONContentType::Null: |
| this->AppendString(this->scriptContext->GetLibrary()->GetNullDisplayString()); |
| return; |
| case JSONContentType::Number: |
| this->AppendString(prop->numericValue.string); |
| return; |
| case JSONContentType::Object: |
| this->AppendObjectString(prop->obj); |
| return; |
| case JSONContentType::Array: |
| this->AppendArrayString(prop->arr); |
| return; |
| case JSONContentType::String: |
| this->EscapeAndAppendString(prop->stringValue); |
| return; |
| default: |
| Assume(UNREACHED); |
| } |
| } |
| |
| void |
| JSONStringBuilder::Build() |
| { |
| this->AppendJSONPropertyString(this->jsonContent); |
| // Null terminate the string |
| AssertOrFailFast(this->currentLocation == endLocation); |
| *this->currentLocation = _u('\0'); |
| } |
| |
| JSONStringBuilder::JSONStringBuilder( |
| _In_ ScriptContext* scriptContext, |
| _In_ JSONProperty* jsonContent, |
| _In_ char16* buffer, |
| charcount_t bufferLength, |
| _In_opt_ const char16* gap, |
| charcount_t gapLength) : |
| scriptContext(scriptContext), |
| endLocation(buffer + bufferLength - 1), |
| currentLocation(buffer), |
| jsonContent(jsonContent), |
| gap(gap), |
| gapLength(gapLength), |
| indentLevel(0) |
| { |
| } |
| |
| } //namespace Js |