Files
UnrealEngineUWP/Engine/Source/Runtime/Internationalization/Private/Serialization/JsonInternationalizationArchiveSerializer.cpp

473 lines
18 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "InternationalizationPrivatePCH.h"
#include "Internationalization/InternationalizationArchive.h"
#include "Internationalization/InternationalizationMetadata.h"
#include "JsonInternationalizationArchiveSerializer.h"
#include "JsonInternationalizationMetadataSerializer.h"
DEFINE_LOG_CATEGORY_STATIC(LogInternationalizationArchiveSerializer, Log, All);
const FString FJsonInternationalizationArchiveSerializer::TAG_FORMATVERSION = TEXT("FormatVersion");
const FString FJsonInternationalizationArchiveSerializer::TAG_NAMESPACE = TEXT("Namespace");
const FString FJsonInternationalizationArchiveSerializer::TAG_CHILDREN = TEXT("Children");
const FString FJsonInternationalizationArchiveSerializer::TAG_SUBNAMESPACES = TEXT("Subnamespaces");
const FString FJsonInternationalizationArchiveSerializer::TAG_DEPRECATED_DEFAULTTEXT = TEXT("DefaultText");
const FString FJsonInternationalizationArchiveSerializer::TAG_DEPRECATED_TRANSLATEDTEXT = TEXT("TranslatedText");
const FString FJsonInternationalizationArchiveSerializer::TAG_OPTIONAL = TEXT("Optional");
const FString FJsonInternationalizationArchiveSerializer::TAG_SOURCE = TEXT("Source");
const FString FJsonInternationalizationArchiveSerializer::TAG_SOURCE_TEXT = TEXT("Text");
const FString FJsonInternationalizationArchiveSerializer::TAG_TRANSLATION = TEXT("Translation");
const FString FJsonInternationalizationArchiveSerializer::TAG_TRANSLATION_TEXT = FJsonInternationalizationArchiveSerializer::TAG_SOURCE_TEXT;
const FString FJsonInternationalizationArchiveSerializer::TAG_METADATA_KEY = TEXT("Key");
const FString FJsonInternationalizationArchiveSerializer::NAMESPACE_DELIMITER = TEXT(".");
struct FCompareArchiveEntryBySourceAndKey
{
FORCEINLINE bool operator()( TSharedPtr< FArchiveEntry > A, TSharedPtr< FArchiveEntry > B ) const
{
bool bResult = false;
if( A->Source < B->Source )
{
bResult = true;
}
else if( A->Source == B->Source )
{
if( A->KeyMetadataObj.IsValid() != B->KeyMetadataObj.IsValid() )
{
bResult = B->KeyMetadataObj.IsValid();
}
else if( A->KeyMetadataObj.IsValid() && B->KeyMetadataObj.IsValid() )
{
bResult = (*(A->KeyMetadataObj) < *(B->KeyMetadataObj));
}
}
return bResult;
}
};
struct FCompareStructuredArchiveEntryByNamespace
{
FORCEINLINE bool operator()( TSharedPtr< FStructuredArchiveEntry > A, TSharedPtr< FStructuredArchiveEntry > B ) const
{
return A->Namespace < B->Namespace;
}
};
#if 0 // @todo Json: Serializing from FArchive is currently broken
bool FJsonInternationalizationArchiveSerializer::DeserializeArchive( FArchive& Archive, TSharedRef< FInternationalizationArchive > InternationalizationArchive )
{
TSharedPtr< FJsonObject > JsonArchiveObj;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( &Archive );
bool bExecSuccessful = FJsonSerializer::Deserialize( Reader, JsonArchiveObj );
if (bExecSuccessful && JsonArchiveObj.IsValid())
{
bExecSuccessful = DeserializeInternal( JsonArchiveObj.ToSharedRef(), InternationalizationArchive );
}
return bExecSuccessful;
}
#endif
bool FJsonInternationalizationArchiveSerializer::DeserializeArchive( const FString& InStr, TSharedRef< FInternationalizationArchive > InternationalizationArchive )
{
TSharedPtr< FJsonObject > JsonArchiveObj;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InStr );
bool bExecSuccessful = FJsonSerializer::Deserialize( Reader, JsonArchiveObj );
if ( bExecSuccessful && JsonArchiveObj.IsValid() )
{
bExecSuccessful = DeserializeInternal( JsonArchiveObj.ToSharedRef(), InternationalizationArchive );
}
return bExecSuccessful;
}
bool FJsonInternationalizationArchiveSerializer::DeserializeArchive( TSharedRef< FJsonObject > InJsonObj, TSharedRef< FInternationalizationArchive > InternationalizationArchive )
{
return DeserializeInternal( InJsonObj, InternationalizationArchive );
}
#if 0 // @todo Json: Serializing from FArchive is currently broken
bool FJsonInternationalizationArchiveSerializer::SerializeArchive( TSharedRef< const FInternationalizationArchive > InternationalizationArchive, FArchive& Archive )
{
TSharedRef< FJsonObject > JsonArchiveObj = MakeShareable( new FJsonObject );
bool bExecSuccessful = SerializeInternal( InternationalizationArchive, JsonArchiveObj );
if( bExecSuccessful )
{
TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create( &Archive );
bExecSuccessful = FJsonSerializer::Serialize( JsonArchiveObj, Writer );
Writer->Close();
}
return bExecSuccessful;
}
#endif
bool FJsonInternationalizationArchiveSerializer::SerializeArchive( TSharedRef< const FInternationalizationArchive > InternationalizationArchive, FString& Str )
{
TSharedRef< FJsonObject > JsonArchiveObj = MakeShareable( new FJsonObject );
bool bExecSuccessful = SerializeInternal( InternationalizationArchive, JsonArchiveObj );
if( bExecSuccessful )
{
TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create( &Str );
bExecSuccessful = FJsonSerializer::Serialize( JsonArchiveObj, Writer );
Writer->Close();
}
return bExecSuccessful;
}
bool FJsonInternationalizationArchiveSerializer::SerializeArchive( TSharedRef< const FInternationalizationArchive > InternationalizationArchive, TSharedRef< FJsonObject > JsonObj )
{
return SerializeInternal( InternationalizationArchive, JsonObj );
}
bool FJsonInternationalizationArchiveSerializer::DeserializeInternal( TSharedRef< FJsonObject > InJsonObj, TSharedRef< FInternationalizationArchive > InternationalizationArchive )
{
if( InJsonObj->HasField( TAG_FORMATVERSION ) )
{
const int32 FormatVersion = static_cast<int>(InJsonObj->GetNumberField( TAG_FORMATVERSION ));
InternationalizationArchive->SetFormatVersion(static_cast<FInternationalizationArchive::EFormatVersion>(FormatVersion));
}
else
{
InternationalizationArchive->SetFormatVersion(FInternationalizationArchive::EFormatVersion::Initial);
}
return JsonObjToArchive( InJsonObj, TEXT(""), InternationalizationArchive );
}
bool FJsonInternationalizationArchiveSerializer::SerializeInternal( TSharedRef< const FInternationalizationArchive > InInternationalizationArchive, TSharedRef< FJsonObject > JsonObj )
{
TSharedPtr< FStructuredArchiveEntry > RootElement = MakeShareable( new FStructuredArchiveEntry( TEXT("") ) );
// Condition the data so that it exists in a structured hierarchy for easy population of the JSON object.
GenerateStructuredData( InInternationalizationArchive, RootElement );
SortStructuredData( RootElement );
// Clear anything that may be in the JSON object
JsonObj->Values.Empty();
// Set format version.
JsonObj->SetNumberField(TAG_FORMATVERSION, static_cast<double>(InInternationalizationArchive->GetFormatVersion()));
// Setup the JSON object using the structured data created
StructuredDataToJsonObj( RootElement, JsonObj );
return true;
}
bool FJsonInternationalizationArchiveSerializer::JsonObjToArchive( TSharedRef< FJsonObject > InJsonObj, FString ParentNamespace, TSharedRef< FInternationalizationArchive > InternationalizationArchive )
{
bool bConvertSuccess = true;
FString AccumulatedNamespace = ParentNamespace;
if( InJsonObj->HasField( TAG_NAMESPACE) )
{
if( !( AccumulatedNamespace.IsEmpty() ) )
{
AccumulatedNamespace += NAMESPACE_DELIMITER;
}
AccumulatedNamespace += InJsonObj->GetStringField( TAG_NAMESPACE );
}
else
{
UE_LOG( LogInternationalizationArchiveSerializer, Warning,TEXT("Encountered an object with a missing namespace while converting to Internationalization archive.") );
bConvertSuccess = false;
}
// Process all the child objects
if( bConvertSuccess && InJsonObj->HasField( TAG_CHILDREN ) )
{
const TArray< TSharedPtr<FJsonValue> > ChildrenArray = InJsonObj->GetArrayField( TAG_CHILDREN );
for( TArray< TSharedPtr< FJsonValue > >::TConstIterator ChildIter( ChildrenArray.CreateConstIterator() ); ChildIter; ++ChildIter )
{
const TSharedPtr< FJsonValue > ChildEntry = *ChildIter;
const TSharedPtr< FJsonObject > ChildJSONObject = ChildEntry->AsObject();
FString SourceText;
TSharedPtr< FLocMetadataObject > SourceMetadata;
if( ChildJSONObject->HasTypedField< EJson::String >( TAG_DEPRECATED_DEFAULTTEXT ) )
{
SourceText = ChildJSONObject->GetStringField( TAG_DEPRECATED_DEFAULTTEXT );
}
else if( ChildJSONObject->HasTypedField< EJson::Object >( TAG_SOURCE ) )
{
const TSharedPtr< FJsonObject > SourceJSONObject = ChildJSONObject->GetObjectField( TAG_SOURCE );
if( SourceJSONObject->HasTypedField< EJson::String >( TAG_SOURCE_TEXT ) )
{
SourceText = SourceJSONObject->GetStringField( TAG_SOURCE_TEXT );
// Source meta data is mixed in with the source text, we'll process metadata if the source json object has more than one entry
if( SourceJSONObject->Values.Num() > 1 )
{
// We load in the entire source object as metadata and just remove the source text.
FJsonInternationalizationMetaDataSerializer::DeserializeMetadata( SourceJSONObject.ToSharedRef(), SourceMetadata );
if( SourceMetadata.IsValid() )
{
SourceMetadata->Values.Remove( TAG_SOURCE_TEXT );
}
}
}
else
{
bConvertSuccess = false;
}
}
else
{
bConvertSuccess = false;
}
FString TranslationText;
TSharedPtr< FLocMetadataObject > TranslationMetadata;
if( ChildJSONObject->HasTypedField< EJson::String >( TAG_DEPRECATED_TRANSLATEDTEXT ) )
{
TranslationText = ChildJSONObject->GetStringField( TAG_DEPRECATED_TRANSLATEDTEXT );
}
else if( ChildJSONObject->HasTypedField< EJson::Object >( TAG_TRANSLATION ) )
{
const TSharedPtr< FJsonObject > TranslationJSONObject = ChildJSONObject->GetObjectField( TAG_TRANSLATION );
if( TranslationJSONObject->HasTypedField< EJson::String >( TAG_TRANSLATION_TEXT ) )
{
TranslationText = TranslationJSONObject->GetStringField( TAG_TRANSLATION_TEXT );
// Source meta data is mixed in with the source text, we'll process metadata if the source json object has more than one entry
if( TranslationJSONObject->Values.Num() > 1 )
{
// We load in the entire source object as metadata and remove the source text
FJsonInternationalizationMetaDataSerializer::DeserializeMetadata( TranslationJSONObject.ToSharedRef(), TranslationMetadata );
if( TranslationJSONObject.IsValid() )
{
TranslationJSONObject->Values.Remove( TAG_TRANSLATION_TEXT );
}
}
}
else
{
bConvertSuccess = false;
}
}
else
{
bConvertSuccess = false;
}
if( bConvertSuccess )
{
FLocItem Source( SourceText );
Source.MetadataObj = SourceMetadata;
FLocItem Translation( TranslationText );
Translation.MetadataObj = TranslationMetadata;
bool bIsOptional = false;
if( ChildJSONObject->HasTypedField< EJson::Boolean >( TAG_OPTIONAL ) )
{
bIsOptional = ChildJSONObject->GetBoolField( TAG_OPTIONAL );
}
TSharedPtr< FLocMetadataObject > MetadataNode;
if( ChildJSONObject->HasTypedField< EJson::Object >( TAG_METADATA_KEY ) )
{
const TSharedPtr< FJsonObject > MetaDataKeyJSONObject = ChildJSONObject->GetObjectField( TAG_METADATA_KEY );
FJsonInternationalizationMetaDataSerializer::DeserializeMetadata( MetaDataKeyJSONObject.ToSharedRef(), MetadataNode );
}
bool bAddSuccessful = InternationalizationArchive->AddEntry( AccumulatedNamespace, Source, Translation, MetadataNode, bIsOptional );
if( !bAddSuccessful )
{
UE_LOG( LogInternationalizationArchiveSerializer, Warning,TEXT("Could not add JSON entry to the Internationalization archive: Namespace:%s DefaultText:%s"), *AccumulatedNamespace, *SourceText );
}
}
}
}
if( bConvertSuccess && InJsonObj->HasField( TAG_SUBNAMESPACES ) )
{
const TArray< TSharedPtr<FJsonValue> > SubnamespaceArray = InJsonObj->GetArrayField( TAG_SUBNAMESPACES );
for(TArray< TSharedPtr< FJsonValue > >::TConstIterator SubnamespaceIter( SubnamespaceArray.CreateConstIterator() ); SubnamespaceIter; ++SubnamespaceIter )
{
const TSharedPtr< FJsonValue > SubnamespaceEntry = *SubnamespaceIter;
const TSharedPtr< FJsonObject > SubnamespaceJSONObject = SubnamespaceEntry->AsObject();
if( !JsonObjToArchive( SubnamespaceJSONObject.ToSharedRef(), AccumulatedNamespace, InternationalizationArchive ) )
{
bConvertSuccess = false;
break;
}
}
}
return bConvertSuccess;
}
void FJsonInternationalizationArchiveSerializer::GenerateStructuredData( TSharedRef< const FInternationalizationArchive > InInternationalizationArchive, TSharedPtr<FStructuredArchiveEntry> RootElement )
{
//Loop through all the unstructured archive entries and build up our structured hierarchy
for(TArchiveEntryContainer::TConstIterator It( InInternationalizationArchive->GetEntryIterator() ); It; ++It)
{
const TSharedRef< FArchiveEntry > UnstructuredArchiveEntry = It.Value();
TArray< FString > NamespaceTokens;
// Tokenize the namespace by using '.' as a delimiter
int32 NamespaceTokenCount = UnstructuredArchiveEntry->Namespace.ParseIntoArray( NamespaceTokens, *NAMESPACE_DELIMITER, true );
TSharedPtr< FStructuredArchiveEntry > StructuredArchiveEntry = RootElement;
//Loop through all the namespace tokens and find the appropriate structured entry, if it does not exist add it. At the end StructuredArchiveEntry
// will point to the correct hierarchy entry for a given namespace
for( int32 TokenIndex = 0; TokenIndex < NamespaceTokenCount; ++TokenIndex )
{
TSharedPtr<FStructuredArchiveEntry> FoundNamespaceEntry;
for( int SubNamespaceIndex = 0; SubNamespaceIndex < StructuredArchiveEntry->SubNamespaces.Num(); SubNamespaceIndex++ )
{
if( StructuredArchiveEntry->SubNamespaces[SubNamespaceIndex]->Namespace == NamespaceTokens[TokenIndex] )
{
FoundNamespaceEntry = StructuredArchiveEntry->SubNamespaces[SubNamespaceIndex];
break;
}
}
if( !FoundNamespaceEntry.IsValid() )
{
int32 index = StructuredArchiveEntry->SubNamespaces.Add( MakeShareable( new FStructuredArchiveEntry( NamespaceTokens[TokenIndex] ) ) );
FoundNamespaceEntry = StructuredArchiveEntry->SubNamespaces[index];
}
StructuredArchiveEntry = FoundNamespaceEntry;
}
// We add the unstructured Archive entry to the hierarchy
StructuredArchiveEntry->ArchiveEntries.AddUnique( UnstructuredArchiveEntry );
}
}
void FJsonInternationalizationArchiveSerializer::SortStructuredData( TSharedPtr< FStructuredArchiveEntry > InElement )
{
if( !InElement.IsValid() )
{
return;
}
// Sort the manifest entries by source text.
InElement->ArchiveEntries.Sort( FCompareArchiveEntryBySourceAndKey() );
// Sort the subnamespaces by namespace string
InElement->SubNamespaces.Sort( FCompareStructuredArchiveEntryByNamespace() );
// Do the sorting for each of the subnamespaces
for( TArray< TSharedPtr< FStructuredArchiveEntry > >::TIterator Iter( InElement->SubNamespaces.CreateIterator() ); Iter; ++Iter )
{
TSharedPtr< FStructuredArchiveEntry > SubElement = *Iter;
SortStructuredData( SubElement );
}
}
void FJsonInternationalizationArchiveSerializer::StructuredDataToJsonObj( TSharedPtr< const FStructuredArchiveEntry > InElement, TSharedRef< FJsonObject > OutJsonObj )
{
OutJsonObj->SetStringField( TAG_NAMESPACE, InElement->Namespace );
TArray< TSharedPtr< FJsonValue > > NamespaceArray;
TArray< TSharedPtr< FJsonValue > > EntryArray;
//Write namespace content entries
for( TArray< TSharedPtr< FArchiveEntry > >::TConstIterator Iter( InElement->ArchiveEntries ); Iter; ++Iter )
{
const TSharedPtr< FArchiveEntry > Entry = *Iter;
TSharedPtr< FJsonObject > EntryNode = MakeShareable( new FJsonObject );
FString ProcessedSourceText = Entry->Source.Text;
FString ProcessedTranslation = Entry->Translation.Text;
TSharedPtr< FJsonObject > SourceNode;
if( Entry->Source.MetadataObj.IsValid() )
{
FJsonInternationalizationMetaDataSerializer::SerializeMetadata( Entry->Source.MetadataObj.ToSharedRef(), SourceNode );
}
if( !SourceNode.IsValid() )
{
SourceNode = MakeShareable( new FJsonObject );
}
SourceNode->SetStringField( TAG_SOURCE_TEXT, ProcessedSourceText );
EntryNode->SetObjectField( TAG_SOURCE, SourceNode );
TSharedPtr< FJsonObject > TranslationNode;
if( Entry->Translation.MetadataObj.IsValid() )
{
FJsonInternationalizationMetaDataSerializer::SerializeMetadata( Entry->Translation.MetadataObj.ToSharedRef(), TranslationNode );
}
if( !TranslationNode.IsValid() )
{
TranslationNode = MakeShareable( new FJsonObject );
}
TranslationNode->SetStringField( TAG_TRANSLATION_TEXT, ProcessedTranslation );
EntryNode->SetObjectField( TAG_TRANSLATION, TranslationNode );
if( Entry->KeyMetadataObj.IsValid() )
{
TSharedPtr< FJsonObject > KeyDataNode;
FJsonInternationalizationMetaDataSerializer::SerializeMetadata( Entry->KeyMetadataObj.ToSharedRef(), KeyDataNode );
if( KeyDataNode.IsValid() )
{
EntryNode->SetObjectField( TAG_METADATA_KEY, KeyDataNode );
}
}
// We only add the optional field if it is true, it is assumed to be false otherwise.
if( Entry->bIsOptional == true )
{
EntryNode->SetBoolField( TAG_OPTIONAL, Entry->bIsOptional );
}
EntryArray.Add( MakeShareable( new FJsonValueObject( EntryNode ) ) );
}
//Write the subnamespaces
for( TArray< TSharedPtr< FStructuredArchiveEntry > >::TConstIterator Iter( InElement->SubNamespaces ); Iter; ++Iter )
{
const TSharedPtr<FStructuredArchiveEntry> SubElement = *Iter;
if( SubElement.IsValid() )
{
TSharedRef<FJsonObject> SubObject = MakeShareable( new FJsonObject );
StructuredDataToJsonObj( SubElement, SubObject );
NamespaceArray.Add( MakeShareable( new FJsonValueObject( SubObject ) ) );
}
}
if( EntryArray.Num() > 0 )
{
OutJsonObj->SetArrayField( TAG_CHILDREN, EntryArray );
}
if( NamespaceArray.Num() > 0 )
{
OutJsonObj->SetArrayField( TAG_SUBNAMESPACES, NamespaceArray );
}
}