blob: 9f9f846dbc668c0b0fa5fa7df4072cbc025b97ce [file]
//-------------------------------------------------------------------------------------------------------
// 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