// // Copyright 2016 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // // // FileIO_Common.cpp #include "pxr/pxr.h" #include "pxr/base/tf/stringUtils.h" #include "pxr/usd/sdf/fileIO_Common.h" #include using std::map; using std::ostream; using std::string; using std::vector; PXR_NAMESPACE_OPEN_SCOPE static const char *_IndentString = " "; // Helper for creating string representation of an asset path static string _StringFromAssetPath(const string& assetPath) { // See Sdf_EvalAssetPath for the code that reads asset paths at parse time. // We want to avoid writing asset paths with escape sequences in them // so that it's easy for users to copy and paste these paths into other // apps without having to clean up those escape sequences. // // We use "@"s as delimiters so that asset paths are easily identifiable. // but use "@@@" if the path already has an "@" in it rather than escaping // it. If the path has a "@@@", then we'll escape that, but hopefully that's // a rarer case. We'll also strip out non-printable characters. so we don't // have to escape those. static const string singleDelim = "@"; static const string tripleDelim = "@@@"; const string* delim = (assetPath.find('@') == std::string::npos) ? &singleDelim : &tripleDelim; string s = assetPath; s.erase(std::remove_if(s.begin(), s.end(), [](char s) { return !std::isprint(s); }), s.end()); if (delim == &tripleDelim) { s = TfStringReplace(s, tripleDelim, "\\@@@"); } return *delim + s + *delim; } static string _StringFromValue(const string& s) { return Sdf_FileIOUtility::Quote(s); } static string _StringFromValue(const TfToken& s) { return Sdf_FileIOUtility::Quote(s); } static string _StringFromValue(const SdfAssetPath& assetPath) { return _StringFromAssetPath(assetPath.GetAssetPath()); } template static void _StringFromVtArray( string *valueStr, const VtArray &valArray) { valueStr->append("["); if (typename VtArray::const_pointer d = valArray.cdata()) { if (const size_t n = valArray.size()) { valueStr->append(_StringFromValue(d[0])); for (size_t i = 1; i != n; ++i) { valueStr->append(", "); valueStr->append(_StringFromValue(d[i])); } } } valueStr->append("]"); } // Helper for creating strings for VtValues holding certain types // that can't use TfStringify, and arrays of those types. template static bool _StringFromVtValueHelper(string* valueStr, const VtValue& value) { if (value.IsHolding()) { *valueStr = _StringFromValue(value.UncheckedGet()); return true; } else if (value.IsHolding >()) { const VtArray& valArray = value.UncheckedGet >(); _StringFromVtArray(valueStr,valArray); return true; } return false; } // ------------------------------------------------------------ // Helpers functions for writing SdfListOp. Consumers can // specialize the _ListOpWriter struct for custom behavior based // on the element type of the list op. namespace { template struct _ListOpWriter { static constexpr bool ItemPerLine = false; static constexpr bool SingleItemRequiresBrackets(const T& item) { return true; } static void Write(ostream& out, size_t indent, const T& item) { Sdf_FileIOUtility::Write(out, indent, "%s", TfStringify(item).c_str()); } }; template <> struct _ListOpWriter { static constexpr bool ItemPerLine = false; static constexpr bool SingleItemRequiresBrackets(const string& s) { return true; } static void Write(ostream& out, size_t indent, const string& s) { Sdf_FileIOUtility::WriteQuotedString(out, indent, s); } }; template <> struct _ListOpWriter { static constexpr bool ItemPerLine = false; static constexpr bool SingleItemRequiresBrackets(const TfToken& s) { return true; } static void Write(ostream& out, size_t indent, const TfToken& s) { Sdf_FileIOUtility::WriteQuotedString(out, indent, s.GetString()); } }; template <> struct _ListOpWriter { static constexpr bool ItemPerLine = true; static constexpr bool SingleItemRequiresBrackets(const SdfPath& path) { return false; } static void Write(ostream& out, size_t indent, const SdfPath& path) { Sdf_FileIOUtility::WriteSdfPath(out, indent, path); } }; template <> struct _ListOpWriter { static constexpr bool ItemPerLine = true; static bool SingleItemRequiresBrackets(const SdfReference& ref) { return !ref.GetCustomData().empty(); } static void Write(ostream& out, size_t indent, const SdfReference& ref) { bool multiLineRefMetaData = !ref.GetCustomData().empty(); Sdf_FileIOUtility::Write(out, indent, ""); if (!ref.GetAssetPath().empty()) { Sdf_FileIOUtility::WriteAssetPath(out, 0, ref.GetAssetPath()); if (!ref.GetPrimPath().IsEmpty()) Sdf_FileIOUtility::WriteSdfPath(out, 0, ref.GetPrimPath()); } else { // If this is an internal reference, we always have to write // out a path, even if it's empty since that encodes a reference // to the default prim. Sdf_FileIOUtility::WriteSdfPath(out, 0, ref.GetPrimPath()); } if (multiLineRefMetaData) { Sdf_FileIOUtility::Puts(out, 0, " (\n"); } Sdf_FileIOUtility::WriteLayerOffset( out, indent+1, multiLineRefMetaData, ref.GetLayerOffset()); if (!ref.GetCustomData().empty()) { Sdf_FileIOUtility::Puts(out, indent+1, "customData = "); Sdf_FileIOUtility::WriteDictionary( out, indent+1, /* multiline = */ true, ref.GetCustomData()); } if (multiLineRefMetaData) { Sdf_FileIOUtility::Puts(out, indent, ")"); } } }; template <> struct _ListOpWriter { static constexpr bool ItemPerLine = true; static bool SingleItemRequiresBrackets(const SdfPayload& payload) { return false; } static void Write(ostream& out, size_t indent, const SdfPayload& payload) { Sdf_FileIOUtility::Write(out, indent, ""); if (!payload.GetAssetPath().empty()) { Sdf_FileIOUtility::WriteAssetPath(out, 0, payload.GetAssetPath()); if (!payload.GetPrimPath().IsEmpty()) Sdf_FileIOUtility::WriteSdfPath(out, 0, payload.GetPrimPath()); } else { // If this is an internal payload, we always have to write // out a path, even if it's empty since that encodes a payload // to the default prim. Sdf_FileIOUtility::WriteSdfPath(out, 0, payload.GetPrimPath()); } Sdf_FileIOUtility::WriteLayerOffset( out, indent+1, false /*multiLineMetaData*/, payload.GetLayerOffset()); } }; template void _WriteListOpList( ostream& out, size_t indent, const string& name, const ListOpList& listOpList, const string& op = string()) { typedef _ListOpWriter _Writer; Sdf_FileIOUtility::Write(out, indent, "%s%s%s = ", op.c_str(), op.empty() ? "" : " ", name.c_str()); if (listOpList.empty()) { Sdf_FileIOUtility::Puts(out, 0, "None\n"); } else if (listOpList.size() == 1 && !_Writer::SingleItemRequiresBrackets(listOpList.front())) { _Writer::Write(out, 0, listOpList.front()); Sdf_FileIOUtility::Puts(out, 0, "\n"); } else { const bool itemPerLine = _Writer::ItemPerLine; Sdf_FileIOUtility::Puts(out, 0, itemPerLine ? "[\n" : "["); TF_FOR_ALL(it, listOpList) { _Writer::Write(out, itemPerLine ? indent + 1 : 0, *it); if (it.GetNext()) { Sdf_FileIOUtility::Puts(out, 0, itemPerLine ? ",\n" : ", "); } else { Sdf_FileIOUtility::Puts(out, 0, itemPerLine ? "\n" : ""); } } Sdf_FileIOUtility::Puts(out, itemPerLine ? indent : 0, "]\n"); } } template void _WriteListOp( ostream &out, size_t indent, const std::string& name, const ListOp& listOp) { if (listOp.IsExplicit()) { _WriteListOpList(out, indent, name, listOp.GetExplicitItems()); } else { if (!listOp.GetDeletedItems().empty()) { _WriteListOpList(out, indent, name, listOp.GetDeletedItems(), "delete"); } if (!listOp.GetAddedItems().empty()) { _WriteListOpList(out, indent, name, listOp.GetAddedItems(), "add"); } if (!listOp.GetPrependedItems().empty()) { _WriteListOpList(out, indent, name, listOp.GetPrependedItems(), "prepend"); } if (!listOp.GetAppendedItems().empty()) { _WriteListOpList(out, indent, name, listOp.GetAppendedItems(), "append"); } if (!listOp.GetOrderedItems().empty()) { _WriteListOpList(out, indent, name, listOp.GetOrderedItems(), "reorder"); } } } } // end anonymous namespace // ------------------------------------------------------------ void Sdf_FileIOUtility::Puts(ostream &out, size_t indent, const std::string &str) { for (size_t i=0; i < indent; ++i) out << _IndentString; out << str; } void Sdf_FileIOUtility::Write(ostream &out, size_t indent, const char *fmt, ...) { for (size_t i=0; i < indent; ++i) out << _IndentString; va_list ap; va_start(ap, fmt); out << TfVStringPrintf(fmt, ap); va_end(ap); } bool Sdf_FileIOUtility::OpenParensIfNeeded(ostream &out, bool didParens, bool multiLine) { if (!didParens) { Puts(out, 0, multiLine ? " (\n" : " ("); } else if (!multiLine) { Puts(out, 0, "; "); } return true; } void Sdf_FileIOUtility::CloseParensIfNeeded(ostream &out, size_t indent, bool didParens, bool multiLine) { if (didParens) { Puts(out, multiLine ? indent : 0, ")"); } } void Sdf_FileIOUtility::WriteQuotedString(ostream &out, size_t indent, const string &str) { Puts(out, indent, Quote(str)); } void Sdf_FileIOUtility::WriteAssetPath( ostream &out, size_t indent, const string &assetPath) { Puts(out, indent, _StringFromAssetPath(assetPath)); } void Sdf_FileIOUtility::WriteDefaultValue( std::ostream &out, size_t indent, VtValue value) { // --- // Special case for SdfPath value types // --- if (value.IsHolding()) { WriteSdfPath(out, indent, value.Get() ); return; } // --- // General case value to string conversion and write-out. // --- std::string valueString = Sdf_FileIOUtility::StringFromVtValue(value); Sdf_FileIOUtility::Write(out, 0, " = %s", valueString.c_str()); } void Sdf_FileIOUtility::WriteSdfPath(ostream &out, size_t indent, const SdfPath &path) { Write(out, indent, "<%s>", path.GetString().c_str()); } template static bool _WriteNameVector(ostream &out, size_t indent, const vector &vec) { size_t i, c = vec.size(); if (c>1) { Sdf_FileIOUtility::Puts(out, 0, "["); } for (i=0; i 0) { Sdf_FileIOUtility::Puts(out, 0, ", "); } Sdf_FileIOUtility::WriteQuotedString(out, 0, vec[i]); } if (c>1) { Sdf_FileIOUtility::Puts(out, 0, "]"); } return true; } bool Sdf_FileIOUtility::WriteNameVector(ostream &out, size_t indent, const vector &vec) { return _WriteNameVector(out, indent, vec); } bool Sdf_FileIOUtility::WriteNameVector(ostream &out, size_t indent, const vector &vec) { return _WriteNameVector(out, indent, vec); } bool Sdf_FileIOUtility::WriteTimeSamples(ostream &out, size_t indent, const SdfPropertySpec &prop) { VtValue timeSamplesVal = prop.GetField(SdfFieldKeys->TimeSamples); if (timeSamplesVal.IsHolding()) { SdfTimeSampleMap samples = timeSamplesVal.UncheckedGet(); TF_FOR_ALL(i, samples) { Write(out, indent+1, "%s: ", TfStringify(i->first).c_str()); if (i->second.IsHolding()) { WriteSdfPath(out, 0, i->second.Get() ); } else { Puts(out, 0, StringFromVtValue( i->second )); } out << ",\n"; } } else if (timeSamplesVal.IsHolding()) { Write(out, indent+1, "%s\n", TfStringify(timeSamplesVal. UncheckedGet()).c_str()); } return true; } bool Sdf_FileIOUtility::WriteRelocates(ostream &out, size_t indent, bool multiLine, const SdfRelocatesMap &reloMap) { Write(out, indent, "relocates = %s", multiLine ? "{\n" : "{ "); size_t itemCount = reloMap.size(); TF_FOR_ALL(it, reloMap) { WriteSdfPath(out, indent+1, it->first); Puts(out, 0, ": "); WriteSdfPath(out, 0, it->second); if (--itemCount > 0) { Puts(out, 0, ", "); } if (multiLine) { Puts(out, 0, "\n"); } } if (multiLine) { Puts(out, indent, "}\n"); } else { Puts(out, 0, " }"); } return true; } void Sdf_FileIOUtility::_WriteDictionary(ostream &out, size_t indent, bool multiLine, Sdf_FileIOUtility::_OrderedDictionary &dictionary, bool stringValuesOnly) { Puts(out, 0, multiLine ? "{\n" : "{ "); size_t counter = dictionary.size(); TF_FOR_ALL(i, dictionary) { counter--; const VtValue &value = *i->second; if (stringValuesOnly) { if (value.IsHolding()) { WriteQuotedString(out, multiLine ? indent+1 : 0, *(i->first)); Write(out, 0, ": "); WriteQuotedString(out, 0, value.Get()); if (counter > 0) { Puts(out, 0, ", "); } if (multiLine) { Puts(out, 0, "\n"); } } else { // CODE_COVERAGE_OFF // This is not possible to hit with the current public API. TF_RUNTIME_ERROR("Dictionary has a non-string value under key " "\"%s\"; skipping", i->first->c_str()); // CODE_COVERAGE_ON } } else { // Put quotes around the keyName if it is not a valid identifier string keyName = *(i->first); if (!TfIsValidIdentifier(keyName)) { keyName = "\"" + keyName + "\""; } if (value.IsHolding()) { Write(out, multiLine ? indent+1 : 0, "dictionary %s = ", keyName.c_str()); const VtDictionary &nestedDictionary = value.Get(); Sdf_FileIOUtility::_OrderedDictionary newDictionary; TF_FOR_ALL(it, nestedDictionary) { newDictionary[&it->first] = &it->second; } _WriteDictionary(out, indent+1, multiLine, newDictionary, /* stringValuesOnly = */ false ); } else { const TfToken& typeName = SdfValueTypeNames->GetSerializationName(value); Write(out, multiLine ? indent+1 : 0, "%s %s = ", typeName.GetText(), keyName.c_str()); // XXX: The logic here is very similar to that in // WriteDefaultValue. WBN to refactor. string str; if (_StringFromVtValueHelper(&str, value) || _StringFromVtValueHelper(&str, value) || _StringFromVtValueHelper(&str, value)) { Puts(out, 0, str); } else { Puts(out, 0, TfStringify(value)); } if (multiLine) { Puts(out, 0, "\n"); } } } if (!multiLine && counter > 0) { // CODE_COVERAGE_OFF // See multiLine comment below. Puts(out, 0, "; "); // CODE_COVERAGE_ON } } if (multiLine) { Puts(out, indent, "}\n"); } else { // CODE_COVERAGE_OFF // Not currently hittable from public API. Puts(out, 0, " }"); // CODE_COVERAGE_ON } } void Sdf_FileIOUtility::WriteDictionary(ostream &out, size_t indent, bool multiLine, const VtDictionary &dictionary, bool stringValuesOnly) { // Make sure the dictionary keys are written out in order. _OrderedDictionary newDictionary; TF_FOR_ALL(it, dictionary) { newDictionary[&it->first] = &it->second; } _WriteDictionary(out, indent, multiLine, newDictionary, stringValuesOnly); } template void Sdf_FileIOUtility::WriteListOp(std::ostream &out, size_t indent, const TfToken& fieldName, const SdfListOp& listOp) { _WriteListOp(out, indent, fieldName, listOp); } template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfPathListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfPayloadListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfReferenceListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfIntListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfInt64ListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfUIntListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfUInt64ListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfStringListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfTokenListOp&); template void Sdf_FileIOUtility::WriteListOp(std::ostream &, size_t, const TfToken&, const SdfUnregisteredValueListOp&); void Sdf_FileIOUtility::WriteLayerOffset(ostream &out, size_t indent, bool multiLine, const SdfLayerOffset& layerOffset) { // If there's anything interesting to write, write it. if (layerOffset != SdfLayerOffset()) { if (!multiLine) { Write(out, 0, " ("); } double offset = layerOffset.GetOffset(); double scale = layerOffset.GetScale(); if (offset != 0.0) { Write(out, multiLine ? indent : 0, "offset = %s%s", TfStringify(offset).c_str(), multiLine ? "\n" : ""); } if (scale != 1.0) { if (!multiLine && offset != 0) { Write(out, 0, "; "); } Write(out, multiLine ? indent : 0, "scale = %s%s", TfStringify(scale).c_str(), multiLine ? "\n" : ""); } if (!multiLine) { Write(out, 0, ")"); } } } string Sdf_FileIOUtility::Quote(const string &str) { static const char* hexdigit = "0123456789abcedf"; static const bool allowTripleQuotes = true; string result; // Choose quotes, double quote preferred. char quote = '"'; if (str.find('"') != string::npos && str.find('\'') == string::npos) { quote = '\''; } // Open quote. Choose single or triple quotes. bool tripleQuotes = false; if (allowTripleQuotes) { if (str.find('\n') != string::npos) { tripleQuotes = true; result += quote; result += quote; } } result += quote; // Escape string. TF_FOR_ALL(i, str) { switch (*i) { case '\n': // Pass newline as-is if using triple quotes, otherwise escape. if (tripleQuotes) { result += *i; } else { result += "\\n"; } break; case '\r': result += "\\r"; break; case '\t': result += "\\t"; break; case '\\': result += "\\\\"; break; default: if (*i == quote) { // Always escape the character we're using for quoting. result += '\\'; result += quote; } else if (!std::isprint(*i)) { // Non-printable; use two digit hex form. result += "\\x"; result += hexdigit[(*i >> 4) & 15]; result += hexdigit[*i & 15]; } else { // Printable, non-special. result += *i; } break; } } // End quote. result += quote; if (tripleQuotes) { result += quote; result += quote; } return result; } string Sdf_FileIOUtility::Quote(const TfToken &token) { return Quote(token.GetString()); } string Sdf_FileIOUtility::StringFromVtValue(const VtValue &value) { string s; if (_StringFromVtValueHelper(&s, value) || _StringFromVtValueHelper(&s, value) || _StringFromVtValueHelper(&s, value)) { return s; } if (value.IsHolding()) { return TfStringify(static_cast(value.UncheckedGet())); } else if (value.IsHolding()) { return TfStringify( static_cast(value.UncheckedGet())); } else if (value.IsHolding()) { return TfStringify( static_cast(value.UncheckedGet())); } return TfStringify(value); } const char* Sdf_FileIOUtility::Stringify( SdfPermission val ) { switch(val) { case SdfPermissionPublic: return "public"; case SdfPermissionPrivate: return "private"; default: TF_CODING_ERROR("unknown value"); return ""; } } const char* Sdf_FileIOUtility::Stringify( SdfSpecifier val ) { switch(val) { case SdfSpecifierDef: return "def"; case SdfSpecifierOver: return "over"; case SdfSpecifierClass: return "class"; default: TF_CODING_ERROR("unknown value"); return ""; } } const char* Sdf_FileIOUtility::Stringify( SdfVariability val ) { switch(val) { case SdfVariabilityVarying: // Empty string implies SdfVariabilityVarying return ""; case SdfVariabilityUniform: return "uniform"; default: TF_CODING_ERROR("unknown value"); return ""; } } PXR_NAMESPACE_CLOSE_SCOPE