#include "TextNodes.hpp" #include "Model/Workflow/Evaluation.hpp" #include "Model/Workflow/Values/BasicValues.hpp" #include "Utils/Macros.hpp" #include "Utils/RTTI.hpp" #include "Utils/Variant.hpp" #include #include #include #include class TextFormatterNode::Impl { public: template static void ForArguments(std::vector::iterator begin, std::vector::iterator end, const TFunction& func) { for (auto it = begin; it != end; ++it) { auto& elm = *it; if (auto arg = std::get_if(&elm)) { func(*arg); } } } /// Find the pin index that the \c elmIdx -th element should have, based on the elements coming before it. static int FindPinForElement(const std::vector& vec, int elmIdx) { for (int i = elmIdx; i >= 0; --i) { auto& elm = vec[i]; if (auto arg = std::get_if(&elm)) { return arg->PinIdx + 1; } } return 0; } }; BaseValue::Kind TextFormatterNode::ArgumentTypeToValueKind(TextFormatterNode::ArgumentType arg) { switch (arg) { case NumericArgument: return BaseValue::KD_Numeric; case TextArgument: return BaseValue::KD_Text; case DateTimeArgument: return BaseValue::KD_DateTime; default: UNREACHABLE; } } bool TextFormatterNode::IsInstance(const WorkflowNode* node) { return node->GetKind() == KD_TextFormatting; } TextFormatterNode::TextFormatterNode() : WorkflowNode(TransformType, KD_TextFormatting) { } int TextFormatterNode::GetElementCount() const { return mElements.size(); } const TextFormatterNode::Element& TextFormatterNode::GetElement(int idx) const { return mElements[idx]; } void TextFormatterNode::SetElement(int idx, std::string text) { assert(idx >= 0 && idx < mElements.size()); std::visit( Overloaded{ [&](const std::string& original) { mMinOutputChars -= original.size(); }, [&](const Argument& original) { PreRemoveElement(idx); }, }, mElements[idx]); mMinOutputChars += text.size(); mElements[idx] = std::move(text); } void TextFormatterNode::SetElement(int idx, ArgumentType argument) { assert(idx >= 0 && idx < mElements.size()); std::visit( Overloaded{ [&](const std::string& original) { mMinOutputChars -= original.size(); mElements[idx] = Argument{ .ArgumentType = argument, .PinIdx = Impl::FindPinForElement(mElements, idx), }; /* `original` is invalid from this point */ }, [&](const Argument& original) { int pinIdx = original.PinIdx; // Create pin auto& pin = mInputs[pinIdx]; pin.MatchingType = ArgumentTypeToValueKind(argument); // Create element mElements[idx] = Argument{ .ArgumentType = argument, .PinIdx = pinIdx, }; /* `original` is invalid from this point */ }, }, mElements[idx]); } void TextFormatterNode::InsertElement(int idx, std::string text) { assert(idx >= 0); if (idx >= mElements.size()) AppendElement(std::move(text)); mMinOutputChars += text.size(); mElements.insert(mElements.begin() + idx, std::move(text)); } void TextFormatterNode::InsertElement(int idx, ArgumentType argument) { assert(idx >= 0); if (idx >= mElements.size()) AppendElement(argument); int pinIdx = Impl::FindPinForElement(mElements, idx); // Create pin auto& pin = InsertInputPin(pinIdx); pin.MatchingType = ArgumentTypeToValueKind(argument); // Create element mElements.insert( mElements.begin() + idx, Argument{ .ArgumentType = argument, .PinIdx = pinIdx, }); } void TextFormatterNode::AppendElement(std::string text) { mMinOutputChars += text.size(); mElements.push_back(std::move(text)); } void TextFormatterNode::AppendElement(ArgumentType argument) { int pinIdx = mInputs.size(); // Create pin mInputs.push_back(InputPin{}); mInputs.back().MatchingType = ArgumentTypeToValueKind(argument); // Creat eelement mElements.push_back(Argument{ .ArgumentType = argument, .PinIdx = pinIdx, }); } void TextFormatterNode::RemoveElement(int idx) { assert(idx >= 0 && idx < mElements.size()); PreRemoveElement(idx); if (auto arg = std::get_if(&mElements[idx])) { RemoveInputPin(arg->PinIdx); } mElements.erase(mElements.begin() + idx); } void TextFormatterNode::Evaluate(WorkflowEvaluationContext& ctx) { std::string result; result.reserve((size_t)(mMinOutputChars * 1.5f)); auto HandleText = [&](const std::string& str) { result += str; }; auto HandleArgument = [&](const Argument& arg) { switch (arg.ArgumentType) { case NumericArgument: { if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { result += val->GetString(); } else { // TODO localize ctx.ReportError("Non-numeric value connected to a numeric text format parameter.", *this); } } break; case TextArgument: { if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { result += val->GetValue(); } else { // TODO localize ctx.ReportError("Non-text value connected to a textual text format parameter.", *this); } } break; case DateTimeArgument: { if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { result += val->GetString(); } else { // TODO localize ctx.ReportError("Non-date/time value connected to a date/time text format parameter.", *this); } } break; } }; for (auto& elm : mElements) { std::visit(Overloaded{ HandleText, HandleArgument }, elm); } } void TextFormatterNode::PreRemoveElement(int idx) { auto& elm = mElements[idx]; if (auto arg = std::get_if(&elm)) { RemoveInputPin(arg->PinIdx); Impl::ForArguments( mElements.begin() + idx + 1, mElements.end(), [&](Argument& arg) { arg.PinIdx--; }); } }